前言
曾經有次被 💩 一樣的代碼 🤮 到了 —— 幾百行代碼寫在一個函數裏,邏輯混亂不堪,更要命的是,代碼里居然沒有 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
層有 n²
個縮進字符。可見縮進是呈指數增加的。
對於一個 50,000 層的 JSON,原數據只有 100KB,而格式化後可增加 2,500,000,000 個縮進字符,即 2.5GB!如果縮進使用 4 個空格,甚至可達 10GB,膨脹十萬倍!
這其中還不包含新增的換行符。由於換行符的數量只有層數 * 2,相比縮進符可忽略不計。
層數限制
由此可見,如果沒有層數限制,再多資源也會被輕易耗盡。
例如有些 JSON 在線美化的網站,由於是在後端處理的,並且整個 JSON 在內存中完整生成才返回,而不是邊生成邊返回的流模式,這種服務是極易受到攻擊的,只需少量數據即可打垮。
其他類型的數據或語言,同樣需注意層數限制,否則存在縮進爆炸的風險。