Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86388782

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

適用於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