代碼縮進爆炸攻擊

前言

曾經有次被 💩 一樣的代碼 🤮 到了 —— 幾百行代碼寫在一個函數裏,邏輯混亂不堪,更要命的是,代碼里居然沒有 continue 之類的語句,所有判斷都新增一層縮進,以至於這坨代碼看上去是這樣的:

for (...) {
    if (...) {
        if (...) {
            if (...) {
                if (...) {
                    if (...) {
                        if (...) {
                            if (...) {
                                if (...) {
                                    ...

由於我的筆記本屏幕小,光這縮進就佔據了大半空間,以至於很多代碼都無法完整顯示,不得不關閉 IDE 的側邊欄騰出界面。儘管如此,最深處的代碼還得左右來回滾動才能勉強瀏覽。

事後好一陣子才緩過神來。然而對於腦洞大開的 Geeker 來說,即便是 💩 也能激發想象的靈感:既然這種風格這麼頭疼,那麼能不能反過來用於攻防場合,折磨破解的人呢。

極限縮進

由於壓縮後的腳本是不帶換行、縮進等字符的,而調試時經過格式化會補上這些。因此用少量的字符即可構造超深的層次,從而在調試時產生大量空白字符。

例如,在代碼前後加上一堆語塊:

{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
console.log('Hello World')
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}

調試時,我們對代碼進行格式化:

格式化後的代碼整整有 263,195 個字符,而原代碼僅 540 個字符,膨脹了近 500 倍。

由於嵌套層次過深,代碼着色功能已無法正常運行,只剩黑色。

{ 換成 if 嵌套,可見代碼從 252 層開始變成黑白:

如果將核心代碼放在這裏,調試者看到的是一堆沒有顏色的字符,並且斷點執行時經常需要水平滾動,多少能帶來一些困擾。

當然你可能會說,把文件保存到本地,然後刪除無用的代碼不就可以了。確實可以,不過這就進入了攻防對抗的環節。程序在本地運行有很多特徵,例如文件路徑,甚至可以獲取破解者的隱私信息;即使運行時動態替換腳本,也有很多方案能感知到,例如文件 Hash、函數 Hash 等等。最終陷入混淆和逆向的泥潭。

無縮進

對各種語法進行測試,可發現對象嵌套的效果更好:

var a = {a: {a: {a: {a: ... }}}}

當套娃達到幾百層時,整個腳本都無法格式化了:

相比之前沒有顏色只能干擾調試,代碼不能格式化則幾乎無法調試。畢竟一個腳本少則幾百行,多則幾千幾萬行,被壓縮成一行而無法展開,是完全不可接受的。

演示:https://www.etherdream.com/anti-js-format/indent-bomb.html

不過這個方案只適用於部分瀏覽器,並不通用,例如 FireFox 仍然可以格式化。(當然 FireFox 也有其他的反格式化方案,這裏就不展開討論了)

需要注意的是,該方案實際應用存在一定風險 —— 有些小衆瀏覽器調低了 JS 引擎的語法層數限制,導致整個腳本無法運行。

其他語言

事實上絕大多數的語言都存在類似問題,甚至包括數據,例如 HTML、XML、JSON 等 —— 只要是樹結構,並且支持壓縮和美化。

以 JSON 爲例,[0] 有 3 個字符,[[0]] 有 5 個字符。壓縮狀態下,只需前後添加 [] 兩個字符即可增加一層。

但格式化後長度是非常可觀的,我們數一下:

1 層

[
	0
]

縮進字符:1

2 層

[
  ③ [
  ③   ① 0
  ③ ]
]

縮進字符:1 + 3 = 4

3 層

[
  ⑤ [
  ⑤   ③ [
  ⑤   ③   ① 0
  ⑤   ③ ]
  ⑤ ]
]

縮進字符:1 + 3 + 5 = 9

n 層

規律很明顯,n 層有 個縮進字符。可見縮進是呈指數增加的。

對於一個 50,000 層的 JSON,原數據只有 100KB,而格式化後可增加 2,500,000,000 個縮進字符,即 2.5GB!如果縮進使用 4 個空格,甚至可達 10GB,膨脹十萬倍!

這其中還不包含新增的換行符。由於換行符的數量只有層數 * 2,相比縮進符可忽略不計。

層數限制

由此可見,如果沒有層數限制,再多資源也會被輕易耗盡。

例如有些 JSON 在線美化的網站,由於是在後端處理的,並且整個 JSON 在內存中完整生成才返回,而不是邊生成邊返回的流模式,這種服務是極易受到攻擊的,只需少量數據即可打垮。

其他類型的數據或語言,同樣需注意層數限制,否則存在縮進爆炸的風險。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章