Jump to content

適用於Linux 的Windows 子系統中的Visual Studio Code 服務器使用本地WebSocket WebSocket 連接與遠程WSL 擴展進行通信。網站中的JavaScript 可以連接到該服務器並在目標系統上執行任意命令。目前該漏洞被命名為CVE-2021-43907。

這些漏洞可以被用於:

本地WebSocket 服務器正在監控所有接口。如果允許通過Windows 防火牆,外部應用程序可能會連接到此服務器。

本地WebSocket 服務器不檢查WebSocket 握手中的Origin 標頭或具有任何身份驗證模式。瀏覽器中的JavaScript 可以連接到該服務器。即使服務器正在監控本地主機,也是如此。

我們可以在特定端口上生成一個Node Inspector示例,它還監控所有接口。外部應用程序可以連接到它。

如果外部應用程序或本地網站可以連接到這些服務器中的任何一個,它們就可以在目標計算機上運行任意代碼。

關於源代碼的說明Visual Studio Code 庫是不斷更新的。我將使用一個特定的提交(b3318bc0524af3d74034b8bb8a64df0ccf35549a)。

$gitclonehttps://github.com/microsoft/vscode$gitreset--hardb3318bc0524af3d74034b8bb8a64df0ccf35549a我們可以使用Code (lol) 來導航源代碼。事實上,我已經在WSL 中為這個漏洞創建了具有相同擴展名的概念驗證。

Visual Studio Code在WSL 內以服務器模式運行,並與Windows 上的代碼示例對話(我稱之為代碼客戶端)。這使我們可以在WSL 中編輯文件和運行應用程序,而不需要運行其中的所有內容。

3.png

遠程開發架構

可以通過SSH 和容器在遠程計算機上進行遠程開發。 GitHub Codespaces 使用相同的技術(很可能通過容器)。

在Windows 上使用它的方法:

1.打開一個WSL終端示例,在Windows上的代碼中應該可以看到遠程WSL擴展;

2.在WSL 中運行code /path/to/something;

3.如果未安裝代碼服務器或已過時,則會下載它;

4.VS Code 在Windows 上運行;

5.你可能會收到一個Windows 防火牆彈出窗口,用於執行如下所示的可執行文件:

4.png

服務器的防火牆對話框

這個防火牆對話框是我執行失敗的原因。出現該對話框是因為VS Code 服務器想要監控所有接口。

從我信任的Process Monitor開始:

1.運行進程監控器;

2.在WSL中運行code .

3.Tools Process Tree;

4.我運行代碼(例如,Windows Terminal.exe)的終端示例中運行Add process and children to Include filte。

5.png

Procmon 的進程樹

經過一番挖掘,我發現了VSCODE_WSL_DEBUG_INFO 環境變量。我只是在WSL 中將export VSCODE_WSL_DEBUG_INFO=true 添加到~/.profile 。運行服務器後我們會得到額外的信息。

6.png

VSCODE_WSL_DEBUG_INFO=true

輸出被清理。

7.png

檢查命令行參數。

8.png

可以看到出現了WebSocket詞彙。

運行Wireshark 並捕獲loopback接口上的流量。然後我再次在WSL 中運行代碼。這次可以看到兩個WebSocket 握手。

9.png

在Wireshark 中捕獲的WebSocket 連接

該運行中的服務器端口是63574,我們也可以從日誌中看到。在Windows 上的代碼客戶端中打開命令面板(ctrl+shift+p) 並運行Remote-WSL: Show Log。

10.png

遠程WSL:顯示日誌

最後一行有端口:在http://127.0.0.1:63574/version 上打開本地瀏覽器。我們還可以看到從Windows 上的Code 客戶端到服務器的兩個單獨的WebSocket 連接。

11.png

為什麼要監聽所有接口?服務器是位於/src/vs/server/remoteExtensionHostAgentServer.ts#L207 的RemoteExtensionHostAgentServer 的一個示例。

它被createServer 在同一個文件中使用,我們可以使用Code (lol) 找到它的引用並追踪到remoteExtensionHostAgent.ts(同一目錄)。

12.png

可以根據註釋查看main.js 內部。

13.png

打開文件,看到服務器可以從傳遞給main.js的參數中獲得主機和端口。

14.png

main.js 被server.sh 調用:

15.png

沒有IP 地址傳遞給腳本,我認為這就是為什麼服務器監控所有有趣的事情。 port=0 可能告訴服務器使用臨時端口,此信息來自同一目錄中的wslServer.sh。

本地WebSocket 服務器每次看到本地WebSocket 服務器時,都應該檢查誰可以連接到它。

WebSocket 連接不受同源策略約束,瀏覽器中的JavaScript 可以連接到本地服務器。

WebSockets 從握手開始,在跨源資源共享或CORS 的上下文中它始終是一個“簡單”的GET 請求,因此瀏覽器不需要預先請求就可以發送它。

測試本地WebSocket 服務器可以快速創建一個嘗試連接到特定端口上的本地WebSocket服務器的測試頁面,將它託管在某個遠程位置(例如,S3 存儲桶)並在計算機上打開它。如果連接成功,就可以繼續操作了。

我還檢查了Burp,在Burp Repeater 中創建了WebSocket 握手。將Origin 標頭修改為https://example.net。如果響應具有HTTP/1.1 101 交換協議,那麼就可以繼續了。

16.png

在Burp 中測試

注意,這只對本地主機服務器有影響。這裡的服務器也對外公開,攻擊者不受瀏覽器約束。它們可以直接連接到服務器並提供任何Origin 標頭。

逆向工程協議接下來是查看Wireshark 中的流量,右鍵點擊之前的WebSocket握手GET請求,然後選擇Follow TCP Stream。我們將看到一個帶有一些可讀文本的屏幕。關閉它,只會看到這個進程的數據包,這允許我們只關注這個進程。

你可能會問為什麼我關閉了僅包含消息內容的彈出窗口,因為沒有用。根據RFC6455,從客戶端到服務器的消息必須被屏蔽。這意味著它們與一個4 字節的密鑰(也隨消息一起提供)進行了異或運算。 Wireshark 在選擇時取消屏蔽每個數據包,但有效載荷在初始進程彈出窗口中顯示為屏蔽。所以我們將看到純文本的服務器消息,而客戶端消息被屏蔽並出現亂碼。如果你點擊單個消息,Wireshark 就會顯示有效載荷。

我花了幾天時間對協議進行逆向工程。後來,我意識到只能在/src/vs/base/parts/ipc/common/ipc.net.ts 中看到協議的源代碼。

17.png

協議握手來自服務器的第一條消息是KeepAlive 消息。

18.png

在協議定義中,我們可以看到不同的消息類型。

19.png

在/src/vs/platform/remote/common/remoteAgentConnection.ts 中,它在代碼的其他部分被稱為OKMessage 和heartbeat。

20.png

客戶端在/src/vs/platform/remote/common/remoteAgentConnection.ts的connectToRemoteExtensionHostAgent中處理此問題。客戶端(Windows上的代碼)發送這個包,它是一個KeepAlive和一個單獨的認證消息。

21.png

最初,我認為長度字段是12 個字節而不是4 個字節,因為其餘的字節總是空的。然後我意識到只有常規消息使用消息ID 和ACK 字段,而且我只看到了不規則的握手消息。

22.png

在修復之前,沒有勾選此選項。

23.png

注意:在2021-11-09 更新之前(commit b3318bc0524af3d74034b8bb8a64df0ccf35549a)客戶端沒有發送數據。但是,使用此提交,我們仍然可以在沒有此密鑰的情況下發送消息並且它會起作用。這是我們給服務器簽名的內容,以檢查連接到正確的服務器。

服務器響應一個簽名請求。

24.png

另一個JSON 對象:

25.png

服務器已經簽名了我們在前一條消息中發送的數據,並用它自己的數據請求進行了響應。

客戶端驗證簽名的數據,以檢查它是否是受支持的服務器。當創建我們的客戶端時,可以簡單地跳過。

26.png

DRM使用options.signService.validate 方法,然後就會得到/src/vs/platform/sign/node/signService.ts。

27.png

vsda 是一個用C++ 編寫的Node 原生插件,將Node 原生插件視為共享庫或DLL。該插件位於https://github.com/microsoft/vsda 的私有存儲庫中,根據https://libraries.io/npm/vsda/的說法,直到2019年左右,它都是一個NPM包。

它與VS Code 客戶端和服務器捆綁在一起:

Windows系統:

C:\Program Files\Microsoft VS Code\resources\app\node_modules.asar.unpacked\vsda\build\Release\vsda.node

服務器(WSL):~/.vscode-server/bin/{commit}/node_modules/vsda/build/Release/vsda.node。

我找到了https://github.com/kieferrm/vsda-example,並通過一些實驗找到瞭如何使用它創建和簽名消息。

1.用msg1=validator.createNewMessage('1234')創建一個新消息,輸入至少4個字符。

2.使用signed1=signer.sign(msg1)進行簽名。

3.使用validator.validate(signed1) 對其進行驗證,響應為“ok”。

需要注意的是,如果你創建了新消息,則無法再驗證舊消息。在源代碼中,每條消息都有自己的驗證器。

28.png

Linux 版本有符號,大小約為40 KB。把它放到IDA/Ghidra 中,應該就可以開始了。

我花了一些時間,想出了這個偽代碼。可能不太正確,但可以讓你大致了解此簽名的工作原理。

1.用當前時間+ 2*(msg[0]) 初始化srand,它只會創建0 到9(含)之間的隨機數;

2.從許可證數組中附加兩個隨機字符;

3.從salt 數組中附加一個隨機字符;

4.SHA256;

5.Base64;

6.

7.Profit。

29.png

僅從許可證數組中選擇前10 個位置的字符,它總是rand() % 10 ,但salt 數組翻了一番。

許可證數組的字符串如下所示:

30.png

salt 數組的前32 個字節(查找Handshake:CHandshakeImpl:s_saltArray)是:

31.png

我從來沒有真正檢查過我的分析是否正確,不過這無關緊要,知道如何使用插件簽名消息,這就足夠了。

接下來,客戶端需要簽名來自服務器的數據並將其發送回來,以顯示它是一個“合法”的代碼客戶端。

32.png

服務器響應如下:

33.png

客戶端發送瞭如下消息:

34.png

提交應該匹配服務器的提交哈希。這不是秘密。這可能是最後一個穩定版本提交(或最後幾個之一)。這只是檢查客戶端和服務器是否在同一版本上。它也可以在http://localhost:{port}/version 上找到,你的瀏覽器JavaScript 可能無法看到它,但外部客戶端沒有這樣的限制。

signedData是對我們在前面消息中從服務器獲得的數據進行簽名的結果。

Args是此消息中最重要的部分,它可以告訴服務器在特定端口上啟動一個Node Inspector 示例。

break: 啟動Inspector 示例後中斷。

端口:檢查器示例的端口。

Env:傳遞給檢查器示例進程的環境變量及其值的列表。

Node Inspector 示例可用於調試Node 應用程序。如果攻擊者可以連接到你計算機上的此類示例,那麼攻擊就成功了。 2019 年,Tavis 發現VS Code 默認啟用了遠程調試器。

整個設置旨在允許Windows 上的代碼客戶端在WSL、容器或GitHub

0 Comments

Recommended Comments

There are no comments to display.

Guest
Add a comment...