https://ipfyx.fr/post/visual-studio-code-tunnel/ ipfyx blog Home About [ ] Blocking Visual Studio Code embedded reverse shell before it's too late Sep 17, 2023 * 10 min read * blueteam defense Windows Splunk SOC Powershell [vscode] Overview * Introduction * Mitigation + Domains blacklist + Applocker + GPO * Detection + Process + File creation + Web traffic monitoring * Conclusion Visual studio code tunnel Introduction Since July 2023, Microsoft is offering the perfect reverse shell, embedded inside Visual Studio Code, a widely used development tool. With just a few clicks, any user with a github account can share their visual studio desktop on the web. VS code tunnel is almost considered a lolbin (Living Of the Land Binary). I am so glad that my users now have the ability to expose their computer with highly sensitive data right on the web, through an authentication I nor control, nor supervise. My internal network is now accessible from anywhere ! The worse part is that this tunnel can be triggered from the cmdline with the portable version of code.exe. An attacker just has to upload the binary, which won't be detected by any anti-virus since it is legitimate and singed windows binary. It is therefore something to watch out for. Execute it, open the link mentioned, log in to your github account and there is your reverse shell. 1.\code.exe tunnel 2* 3* Visual Studio Code Server 4* 5* By using the software, you agree to 6* the Visual Studio Code Server License Terms (https://aka.ms/vscode-server-license) and 7* the Microsoft Privacy Statement (https://privacy.microsoft.com/en-US/privacystatement). 8* 9 10Open this link in your browser https://vscode.dev/tunnel/sauceTomate/C:/Users/ipfyx/Downloads/vscode_cli_win32_x64_cli The binary is definitely signed : 1Get-AppLockerFileInformation 'C:\Users\ipfyx\AppData\Local\Programs\Microsoft VS Code\bin\code-tunnel.exe'|Select-Object -ExpandProperty Publisher 2PublisherName : O=MICROSOFT CORPORATION, L=REDMOND, S=WASHINGTON, C=US 3ProductName : 4BinaryName : 5BinaryVersion : 0.0.0.0 6HasPublisherName : True 7HasProductName : False 8HasBinaryName : False Notice how there are nor ProductName nor BinaryName value, we will use them later. pfiatde blog post already did a great job at describing what an attacker can do so I won't say much more. Go check his awesome blog post. Mitigation But now, as a defender, how do we block or detect its usage ? Domains blacklist Microsoft documentation states : 1If you're part of an organization who wants to control access to Remote Tunnels, you can do so by allowing or denying access to the domain global.rel.tunnels.api.visualstudio.com. Yeah it's not that simple. It will indeed block most users but, from my testing, it won't block an attacker that has already established a tunnel once. Meaning the tunnel can be kept active or restarted at will despite this domain being blacklisted. From my understanding, VScode contacts global.rel.tunnels.api.visualstudio.com to get its "clusters" : https://global.rel.tunnels.api.visualstudio.com/api/v1/clusters Here is a sample of the resulting json : 1[ 2 { 3 "clusterId": "auc1", 4 "uri": "https://auc1.rel.tunnels.api.visualstudio.com", 5 "azureLocation": "AustraliaCentral" 6 }, 7 { 8 "clusterId": "aue", 9 "uri": "https://aue.rel.tunnels.api.visualstudio.com", 10 "azureLocation": "AustraliaEast" 11 }, 12 etc. 13] Those domains are also mentioned in another Microsoft documentation : 1- Dev Tunnels 2 - global.rel.tunnels.api.visualstudio.com 3 - [clusterId].rel.tunnels.api.visualstudio.com 4 - [clusterId]-data.rel.tunnels.api.visualstudio.com 5 - *.[clusterId].devtunnels.ms 6 - *.devtunnels.ms Blocking those domains will block and cut off any VS code tunnel : 1*.tunnels.api.visualstudio.com 2*.devtunnels.ms Applocker Applocker is Microsoft application whitelisting technology. When enabled and configured, with default rules for example, everything is blocked by default except for the executables and scripts defined in the preceding rules. Let's pretend we use Microsoft default rules for demo purpose. To generate them, in Group Policy Management Editor, go to Computer Configuration -> Windows Settings -> Security Settings -> Application Control Policies -> AppLocker. Right Click on Create Default Rules. Then right click on AppLocker and Export Policy. MS Applocker default rules generation MS Applocker default rules generation Here is the resulting xml : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 When using those rules, everything that is in Program Files and Windows is allowed, everything else is blocked. Administrators are allowed to execute anything, VSCode for instance. But if you were to define a rule that blocks VSCode, this rule would apply first, since "Deny rules" are applied before "Allowed rules". Let's build a rule to block VScode altogether. The rule applies to Everyone (SID S-1-1-0), therefore to Administrators (SID S-1-5-32-544). 1Get-AppLockerFileInformation 'C:\Users\ipfyx\AppData\Local\Programs\Microsoft VS Code\Code.exe'| New-AppLockerPolicy -RuleType Publisher -User S-1-1-0 -Optimize -Xml In the resulting xml, I replaced Action="Allow" with Action="Deny". I also removed my current VSCode version from BinaryVersionRange to match any VScode version. Warning : Do not import the following xml in Applocker. Imported alone, this rule will completely block your system. Add a Allow * rule beforehand. 1 2 3 4 5 6 7 8 9 10 11 Included in MS default rules, here is the resulting xml (called vscode.xml from now on) : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 Unfortunately, this rule won't apply to code tunnel because this binary doesn't have a product name (thanks Microsoft !) : 1Get-AppLockerFileInformation 'C:\Users\ipfyx\AppData\Local\Programs\Microsoft VS Code\bin\code-tunnel.exe' | New-AppLockerPolicy -RuleType Publisher -User S-1-1-0 -Optimize -Xml 2New-AppLockerPolicy : Les regles ne peuvent pas etre creees. Les informations de fichier requises sont absentes dans le fichier suivant: 3%OSDRIVE%\USERS\ipfyx\DOWNLOADS\VSCODE_CLI_WIN32_X64_CLI\CODE.EXE To be sure, let's test the previously generated vscode rule. 1# Portable code tunnel is blocked but not by the rule 2Test-AppLockerPolicy -XmlPolicy .\vscode.xml -Path .\code.exe -User S-1-1-0|fl 3FilePath : C:\Users\ipfyx\Downloads\vscode_cli_win32_x64_cli\code.exe 4PolicyDecision : DeniedByDefault 5MatchingRule : 6 7# Code tunnel is blocked but not by the rule 8Test-AppLockerPolicy -XmlPolicy .\vscode.xml -Path 'C:\Users\ipfyx\AppData\Local\Programs\Microsoft VS Code\bin\code-tunnel.exe' -User S-1-1-0|fl 9FilePath : C:\Users\ipfyx\AppData\Local\Programs\Microsoft VS Code\bin\code-tunnel.exe 10PolicyDecision : DeniedByDefault 11MatchingRule : 12 13# VScode is blocked by the rule 14Test-AppLockerPolicy -XmlPolicy .\vscode.xml -Path 'C:\Users\ipfyx\AppData\Local\Programs\Microsoft VS Code\Code.exe' -User S-1-1-0|fl 15FilePath : C:\Users\ipfyx\AppData\Local\Programs\Microsoft VS Code\Code.exe 16PolicyDecision : Denied 17MatchingRule : VScode The PolicyDecision is DeniedByDefault, which means the rule we just built from the vscode binary does not apply to code-tunnel. If no rules matches, the default behaviour for applocker is to deny any execution. Code-tunnel is denied there, but it would not be if it was placed in the allowed directories. It could be placed there by an attacker or by a legitimate vscode, installed by an admin. A solution could be to use a Hash Condition. 1# Export 2Get-AppLockerFileInformation .\code.exe | New-AppLockerPolicy -RuleType Hash -User S-1-1-0 -Optimize -Xml > .\vscode-tunnel-hash.xml 3 4# Code tunnel is blocked 5Test-AppLockerPolicy -XmlPolicy .\vscode-tunnel-hash.xml -Path 'C:\Users\ipfyx\AppData\Local\Programs\Microsoft VS Code\bin\code-tunnel.exe' -User S-1-1-0|fl 6FilePath : C:\Users\ipfyx\AppData\Local\Programs\Microsoft VS Code\bin\code-tunnel.exe 7PolicyDecision : Denied 8MatchingRule : code.exe Warning : Do not import the following xml in Applocker. Imported alone, this rule will completely block your system. Add a Allow * rule beforehand 1 2 3 4 5 6 7 8 9 10 11 But this solution is nor resilient nor sustainable since the hash can change at the first update. GPO Visual Studio has the so wanted features : 1- Dev Tunnels - controls test functionality But as of today (1.8.82), VScode doesn't. You can force automatic updates though ! UpdateMode_default would be my goto. 1gc 'C:\Users\ipfyx\AppData\Local\Programs\Microsoft VS Code\policies\en-us\VSCode.adml' 1 2 3 4 5 6 7 Visual Studio Code 8 Visual Studio Code >= 1.67 9 Update 10 UpdateMode 11 Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service. 12 Disable updates. 13 Disable automatic background update checks. Updates will be available if you manually check for updates. 14 Check for updates only on startup. Disable automatic background update checks. 15 Enable automatic update checks. Code will check for updates automatically and periodically. 16 17 18 19 20 21 Detection Process Looking for code-tunnel execution A simple detection could be : 1index=win sourcetype="XmlWinEventLog" EventCode=4688 code tunnel cmdline="*code*.exe*tunnel*" 2| stats min(_time) as time_min max(_time) as time_max count as occurence values(NewProcess) as NewProcess values(ParentProcess) as ParentProcess values(CommandLine) as CommandLine by host, SubjectUser file_name host SubjectUser cmdline file_name time_min time_max occurence NewProcess ParentProcess "c:\Users\ipfyx\AppData\Local C:\Users\ipfyx\ \Programs\Microsoft VS Code\ AppData\Local\ C:\Windows\ sauteTomate ipfyx bin\code-tunnel" tunnel code-tunnel.exe 2023-09-18 2023-09-18 1 Programs\ System32\ --accept-server-license-terms 11:31:54 11:31:54 Microsoft VS cmd.exe --name totallyNotSuspicious Code\ code-tunnel.exe But the binary code.exe could be renamed. However, what an attacker can't change are the tunnel and --accept-server-license-terms options : 1index=win sourcetype="XmlWinEventLog" EventCode=4688 tunnel accept server license terms cmdline="*.exe*tunnel*--accept-server-license-terms*" 2| stats min(_time) as time_min max(_time) as time_max count as occurence values(NewProcess) as NewProcess values(ParentProcess) as ParentProcess values(CommandLine) as CommandLine by host, SubjectUser file_name host SubjectUser cmdline file_name time_min time_max occurence NewProcess ParentProcess "c:\Users\ipfyx\AppData\Local C:\Users\ipfyx\ \Programs\Microsoft VS Code\ 2023-09-18 2023-09-18 AppData\Local\ C:\Windows\ sauteTomate ipfyx bin\c0de.exe" tunnel c0de.exe 11:35:54 11:35:54 1 Programs\ System32\ --accept-server-license-terms Microsoft VS cmd.exe --name totallyNotSuspicious Code\c0de.exe "c:\Users\ipfyx\AppData\Local C:\Users\ipfyx\ \Programs\Microsoft VS Code\ AppData\Local\ C:\Windows\ sauteTomate ipfyx bin\code-tunnel" tunnel code-tunnel.exe 2023-09-18 2023-09-18 1 Programs\ System32\ --accept-server-license-terms 11:31:54 11:31:54 Microsoft VS cmd.exe --name totallyNotSuspicious Code\ code-tunnel.exe If you use sysmon, you can switch to EventCode=1 and Sysmon sourcetype instead of EventCode 4688. Looking for suspicious child process Great idea from lobas : 1IOC: Process tree: code.exe -> cmd.exe -> node.exe -> winpty-agent.exe A straightforward search would be : 1index=win sourcetype="XmlWinEventLog" EventCode=4688 code (cmd OR powershell) ParentProcess="*code-tunnel.exe" NewProcess IN ("*cmd*", "*powershell*") 2| table _time, host, NewProcess, ParentProcess But once again, code.exe could be renamed We could use the search above to look for suspicious child process from our process with accept-server-license-terms option : 1index=win sourcetype="XmlWinEventLog" EventCode=4688 (cmd OR powershell) ParentProcess="*code*.exe" NewProcess IN ("*cmd*", "*powershell*") 2 [search index=win sourcetype="XmlWinEventLog" EventCode=4688 tunnel accept server license terms CommandLine="*.exe*tunnel*--accept-server-license-terms*" 3 | table host NewProcessId 4 | rename NewProcessId as ParentProcessId 5 | format] 6| table _time, host, NewProcess, ParentProcess _time host NewProcess ParentProcess 2023-09-18 C:\Windows\System32\ c:\Users\ipfyx\AppData\ 11:36:21 sauteTomate WindowsPowerShell\ Local\Programs\Microsoft v1.0\powershell.exe VS Code\bin\c0de.exe File creation Another great idea from lobas : IOC: File write of code_tunnel.json which is parametizable, but defaults to: %UserProfile%\.vscode-cli\code_tunnel.json license_consent.json file could also be watched. 1PS > gc C:\Users\ipfyx\.vscode\cli\code_tunnel.json 2{"name":"sauceTomate","id":"9s43zAc9","cluster":"uks1"} 3PS > gc C:\Users\ipfyx\.vscode\cli\license_consent.json 4{"consented":true} I do not have sysmon EventCode 11 yet, so I will leave the SPL search as an exercise for the user. Watching for those files creation inside the UserProfile directory is not enough since it can be changed with the --cli-data-dir option. 1.\code.exe tunnel help 2... 3GLOBAL OPTIONS: 4 --cli-data-dir Directory where CLI metadata should be stored [env: VSCODE_CLI_DATA_DIR=] Web traffic monitoring If you haven't blocked the domains I mentioned earlier, watch out for any HTTP traffic toward those domains. Conclusion A GPO parameter would be awesome but it is yet to be seen. An Applocker hash rule is not sustainable. Moreover, your non-domain-joined computers would not be concerned by neither of them. Therefore, for now, we are only left to blocking those two bad boys : 1*.tunnels.api.visualstudio.com 2*.devtunnels.ms [ ] ipfyx Yet another cybersecurity blog Read More Featured Posts * Blocking Visual Studio Code embedded reverse shell before it's too late Recent Posts * D LINK DNS323 Fan Speed Control * Bluekeep, why would you still be vulnerable ? SHA2 signing Categories DEFENSE 2 ADMIN 1 CYBERSECURITY 1 Tags POWERSHELL 2 WINDOWS 2 BASH 1 BLUEKEEP 1 BLUETEAM 1 CVE 1 DEFENSE 1 LINUX 1 RDP 1 SOC 1 SPLUNK 1 WSUS 1 ipfyx blog Copyright 2019- IPFYX BLOG. All Rights Reserved