在2011年的BlackHat DC 2011大會上Ryan Barnett給出了一段關於XSS的示例javascript代碼:
($=[$=[]][(__=!$+$)[_=-~-~-~$]+({}+$)[_/_]+($$=($_=!''+$)[_/_]+$_[+$])])()[__[_/_]+__[_+~$]+$_[_]+$$](_/_)
這是一段完全合法的javascript代碼,效果相當於alert(1)。它可以在大部分瀏覽器上運行。(雖然目前我測試過手頭的瀏覽器都能運行,但理論上不能保證所有瀏覽器都能正確運行,原因見下文)
這段代碼的好處(對於黑客)是,它不包含任何字符或數字,可以逃過某些過濾器的檢查。比如說,如果假定一個AJAX請求將返回一個只包含數字的JSON,於是很可能會簡單判斷了一下其中不含字母就直接eval了,結果給黑客們留下了后門。上面的代碼功能很簡單,只是alert(1),但使用同樣的原理,完全可以干出更復雜的事,例如alert(document.cookie)。更重要的是,這段代碼再一次提醒我,黑客的想象力是無限的……正如Ryan Barnett的演講標題:"XSS:The only rule is no rule"。
那么這段代碼是如何工作的呢?
我們可以把它分為兩個部分來理解:
第一部分:
($=[$=[]][(__=!$+$)[_=-~-~-~$]+({}+$)[_/_]+($$=($_=!''+$)[_/_]+$_[+$])])()
第二部分:
[__[_/_]+__[_+~$]+$_[_]+$$](_/_)
其中第一部分是核心,我們首先對它進行分析,先縮進一下:
($= [$=[]][ (__=!$+$)[_=-~-~-~$] + ({}+$)[_/_] + ($$= ($_=!''+$)[_/_] + $_[+$]) ] )()
顯然,最外層是(...)()形式的函數調用,我們需要看看這里究竟調用了什么函數,返回了什么。下一步,我們把原來代碼中賦值表達式提取出來,將其改寫為以下等價形式:
$ = []; //1 __ = !$+$; //2 _ = -~-~-~$; //3 $_=!''+$; //4 $$ = $_[_/_] + $_[+$]; //5 $= [$][ __[_] + //6 ({}+$)[_/_] + //7 $$ //8 ]; //9 $(); //10
現在來一行行看:
1. $先賦值為一個空數組 (后面會被覆蓋)
2. __ = ![] + [] = false + [] = "false" 這里利用了javascript運算的強制類型轉換特性。首先空數組是一個非null值,因此![]的結果是false(布爾型)。在計算false + []時,由於數組對象無法與其他值相加,在加法之前會先做一個toString的轉換,空數組的toString就是"",因此事實上在計算false + ""。這時false被自動轉換為字符串。最終結果是"false"+"" = "false"。 **換句話說,在$為空數組時,使用 “+$”的方式可以將任何一個值轉為字符串**
3. 在計算~[]時,~需要一個數字操作數,空數組無法直接轉換為數字,則作為0處理。因此~[] = ~0 = -1。
參考: ~3 = -4 ~[3] = -4 ~[3,2] = -1 (無法轉為數字) ~"3" = -4 ~"abc" = -1
因此: _ = -~-~-~[] = -~-~-(-1) = -~-~1 = -~-(-2) = -~2 = -(-3) = 3 理論上,可以用這種方式得出1-9所有數字
4. !''是true,使用+$將其變為字符串 "true"
5. 這里需要注意的是,之前一直用“值+[]”來獲得“值”的字符串形式。而“+[]”則是0(正號導致[]被自動轉換為數值0)。因此:$$ = "true"[3/3] + "true"[+[]] = "true"[1] + "true"[0] = "rt"
6. __[_] = "false"[3] = "s"
7. ({} + [])導致空對象{}被轉換為字符串"[object Object]", 因此({}+$)[_/_] = "[object Object]"[1] = "o"
9. 這里把$覆蓋為 [[]]["s"+"o"+"rt"]。注意這里[[]]本身是一個包含空數組的數組,其實對這一步來說,任何一個數組都沒有關系(不一定要是嵌套數組),但作者巧妙地把$的首次賦值式放在了數組內部,使代碼更為緊湊。最終結果是,$ = [[]]["sort"] = [[]].sort = Array.prototype.sort。
10. 調用$(),作為整個表達式最終的取值。需要注意,$是全局范圍的,是window的一個屬性,相當於window.$。而Array.prototype.sort會返回this。對於window.$來說,this就是window。因此,整個第一部分的值,就是window本身!當然,這個過程的正確運作依賴於當前瀏覽器的Array.prototype.sort實現能對this為window的情況容錯。
通過第一部分,我們已經獲得將任何值轉換為字符串的簡單方法,並能產生任意的數值,理論上就可以從javascript的取值系統中提取出大部分字母(不知道是不是全部,需要考證)。並且,我們獲取到了window的引用。下面就可以開始上下其手,為所欲為了。木哈哈哈哈哈!
可以看出,上面的第10步是與瀏覽器的具體實現相關的,因此也存在着某些瀏覽器下需要對代碼作出修改的可能。
現在看第二部分,事實上已經非常明朗了,唯一需要注意的是,現在$是一個函數,因此~$ = ~0 (無法直接轉換為數字則作為0處理) = -1。
[__[_/_]+__[_+~$]+$_[_]+$$](_/_) = ["false"[1]+"false"[3+(-1)]+"true"[3]+"rt"](1) = ["a"+"l"+"e"+"rt"](1)
所以,整條式子相當於:
window["alert"](1)
最后只想再感慨一次:黑客的想象力是無限的。理解代碼並不難,問題是一開始時他們是怎么能想出來的。。。
轉自:http://www.javaeye.com/topic/947149