XSS 蠕虫定义与实战分析
上次博客分析了下XSS的分类和基本的实战方式,这次继续来接受一种XSS病毒。
定义
首先我们来分析下什么是XSS蠕虫病毒。
它是一种跨站脚本病毒,大多是用JavaScript编写,突破浏览器的安全限制,它基于社会工程学诱使用户点击访问其发出的恶意邀请链接在网站上感染访问网站的用户,受感染的用户再发送含有蠕虫的内容继续感染安全的用户。
一般原理
- 基于存储型XSS漏洞,攻击者在Web页面植入恶意代码。
- 发送伪装的邀请链接。
- 用户点击链接被感染。
- 新感染用户的向好友发送伪装的邀请链接。
这是一个传十 十传百的 传播方式, 感染效率极高,XSS蠕虫的感染速度是 随Web应用程序的用户访问量几何数递增的
xss蠕虫基于 xss, 所以xss 能利用的功能。蠕虫都能够利用,而且 由于 xss蠕虫比存储型XSS漏洞更强的传播性,危害性 极大。
第一个蠕虫:Samy
当时年仅19岁的 Samy Kamkar 发起了对 MySpace.com (社交网站)的 攻击,Samy Kamkar 的蠕虫病毒在20个小时被超过一百万人执行。在每 个人的自我简介后边加了一句话:“but most of all, Samy is my hero.”
在2005年,Samy kamkar在为Myspace社交网站新注册的账户交不到好友而感到苦恼不已。他起初的想法是让自己账户页面更炫,这样能吸引更多女生来关注他。在对Myspace.com页面的摸索过程中,他发现页面中能够嵌入javascript代码,于是他的想法从“让账户页面更炫”,变成了,每个浏览他账户页面的用户都会把他加为好友,而每个加他好友的用户的账户页面都嵌入同样的代码,朋友的朋友也会加他为好友…在不到一天的时间里,Samy的蠕虫感染100万名用户,这是Web安全史上第一重量级的XSS蠕虫。
生效的前提,MySpace网站分析
好,让我们来更深入的探讨一下,这些问题的产生,它的背景,以及在通常情况下,它是如何生效的。
1)Myspace网站过滤了许多标签。事实上,他们貌似只保留了<a>
标签、<img>
标签和<div>
标签…可能还有其他的。他们禁止了<script>
标签、
<div style="background:url('javascript:alert(1)')">
- 因为我们已经使用了单引号和双引号,所以在该标签里不能再使用引号了。这让JS代码编写起来非常的困难。为了解决这个问题,我们使用一个表达式来存储JS代码,然后通过name查询来执行它:
<div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')">
3) 非常好!现在我们可以在javascript代码中使用单引号了。然而,Myspace网站过滤了“javascript”等敏感词。为了绕开这个限制,我们了解到,一些浏览器会将“java\nscript”解析为“javascript”(即java<换行>script):
<div id="mycode" expr="alert('hah!')" style="background:url('java script:eval(document.all.mycode.expr)')">
4) OK,即使我们让单引号生效了,但有时候我们也需要双引号。我们可以对双引号进行转义,比如:“foo"bar”。但Myspace网站猜到我会这么做…他们过滤了所有转义过的引号,无论是单引号还是双引号。但是,我们在javascript中可以将数值转换成ASCII码来生成引号:
1 | <div id="mycode" expr="alert('double quote: ' + String.fromCharCode(34))" style="background:url('java |
- 为了把代码提交到用户的账户页面中,我们需要得到页面的源码。啊哈,我们可以使用document.body.innerHTML来得到该页面的源码,账户页面会保留浏览过(哪怕一次)该页面的用户的ID。Myspace网站又再一次猜到我的想法,它过滤了所有的“innerHTML”。为了绕开这限制,我们使用eval()来调用两个字符串,将它们拼接到一起,组合成“innerHTML”:
alert(eval('document.body.inne' + 'rHTML'));
6) 是时候访问其他页面了。我们可能会想到用iframe标签,但很多时候(即便是隐藏的时候),iframe都不是那么有效,而且用户也会很明显的觉察到“某些事情”正在发生。所以,我们使用AJAX(XML-HTTP)来构造GET和POST请求。然而,Myspace网站过滤了一个XML-HTTP请求必要的关键字:“onreadystatechange”。再次,我们使用eval来绕开它。选用XML-http的另一原因在于,Myspace网站上执行操作的必要cookie可以很轻松地发送给后台:
eval('xmlhttp.onread' + 'ystatechange = callback');
7) 是时候给用户的账户页面发送GET情况了,这样我们可以获取到他们当前的关注列表。我们并不是要删除任何关注的好友,我们只是想把我们自己添加到他们关注列表中。如果我们GET请求获取到了他们的账户页面,我们可以抓取到他们的英雄并保存起来,方便后面使用。之前讲过了,这对于XML-HTTP来说是很容易办到的,但是我们必须获取到浏览该页面的用户的ID。就像我们上面说道,我们可以通过获取页面源码来实现这一点。然而,现在我们需要在这个页面上对一个特殊的单词进行搜索。于是乎我们开始了搜索,如果这么做了,搜索总会找到我们自己写的代码,因为我们代码里就包含了我们要查找的那个单词…我们告诉电脑“如果这个页面包含了‘foo’单词,那么执行以下操作”,这判断语句会永远返回true,因为它总会在执行搜索的代码里找到这个单词。我们通过eval()和字符串结合的方式来避开这个问题:
var index = html.indexOf('frien' + 'dID');
8) 目前为止,我们获取到了关注列表。接下来,通过构造一个XML-http的POST请求发送到添加朋友页面,把我加为好友。噢,别这样,这竟然不起作用!为什么行不通?我们在profile.myspace.com上,然而添加朋友的POST请求只能在www.myspace.com上请求。没什么大不了的,但是XML-HTTP不予许跨域发送GET/POST请求。为了避开这一限制,我们要跳转到www.myspace.com的同个url上。在www.myspace.com上你依旧能够访问到账户页面,所以将页面重新加载到我们可以进行POST操作的域名上:
if (location.hostname == 'profile.myspace.com') document.location = 'http://www.myspace.com' + location.pathname + location.search;
9) 我们终于可以进行POST请求了!然而,当我们发送POST请求,它却不会成功添加朋友。为什么会这样?Myspace网站给需要进行POST操作的页面生成了一个随机的哈希值(例如,“你确定要添加这位用户为好友么”页面)。如果POST请求中没有附带这个哈希值,该请求便不会生效。为了解决这个问题,我们假装是一个浏览器,在添加好友之前,用GET方式获取到对应页面,解析其源码获取到这段哈希值,然后构造POST请求的时候附上这段哈希值。
当POST请求完成后,我们还要在关注列表上添加一名关注并附上蠕虫代码。代码和新增关注放在同个地方,这样我们只需要发送一个POST请求就可以了。为了获得新的哈希值,我们需要GET请求到该页面,但在此之前,我们必须重新生成POST发送的蠕虫代码。最简单的方式就是抓取我们所在账户页面中的源码,把蠕虫代码解析出来,然后POST发送出去。这能够做到,但现在所有代码都乱码了!啊,我们需要对代码进行URL编码或转义,这样POST请求才可以正确的发送这段代码。奇怪,还是不行。显然,javascript的URL编码和转义函数并不能把所有必要的东西都进行转义,所以我们需要手动来执行一些替换,让关键的数据能够成功转义。我们加上了一小段文字“但最重要的是,samy是我的偶像”。紧随其后,我们加入所有的蠕虫代码。现在,我们有了一个可”自复制“的代码了,如果你愿意,你可以将它称之为蠕虫。
还有一些限制,比如标签属性赋值的最大长度,这会造成其他的问题,而且要求压缩代码,不可以有空格,模糊的变量命名,可复用的函数
源码分析
1 | <div id=mycode style=”BACKGROUND: url('javascript:eval(document.all.mycode.expr)')” expr=” |