In my previous post, we took a look at Microsoft Dev Tunnels, how they’re abused by threat actors, and how you can detect they’re usage. In this post we’ll take a look at Microsoft VSCode Remote Tunnels. A different implementation of a similar a capability, which is harder to detect for Blue Teams.
What are Remote Tunnels
Remote Tunnels is a feature built into Microsoft VSCode. One of the most popular IDEs for developers in use today.
The feature allows you to expose your coding environment, externally. Take the following scenario as an example;
You’re a developer, working on a new web application, on your corporate provided laptop. VSCode is your IDE of choice.
You decide to visit family abroad, but the country you’re traveling to isn’t a location that your organisation approves remote work from. You want to work on your web application from the remote country, but can’t take your corporate device with you.
Enter remote tunnels, with this capability, you can expose your coding environment, to any device. You can also share the environment you’re working on with colleagues, so you can all work on the code base together.
How it Works
You have your code base on your corporate laptop, you simply enable Remote Tunnels in VSCode. This then provides you with a URL. You paste that URL into a web browser, authenticate with the Microsoft or GitHub account you created the tunnel with, and you have access to the local directory the repo is hosted on. You can also provision access to the tunnel to colleagues as well, allowing them to connect to your device.
How it’s Being Abused
In recent months the abuse of VSCode remote tunnels has increased, with multiple threat actors utilising the tool in their campaigns. Typically, a malicious script or LNK file is delivered to a user, where the presence of VSCode is checked. If it’s not, the CLI Version of the tool is installed, and a tunnel is setup.
Example Campaign
Let’s mimic an example campaign to highlight how VSCode tunnels are being abused by Threat Actors. We’re going to use a campaign ran by the threat actor Mustang Panda (aka Stately Taurus) as inspiration.
Our attack path is going to look like a below;
Some more details on the attack chain;
- Deliver of a malicious LNK File, update.py.
- This can be done via Phishing, North Korean Threat Actors (Famous Chollima) have been running fake recruitment campaigns, alongside their IT Workers campaign as a means of delivery of malicious payloads. VSCode tunnels has featured in many of their campaigns.
- LNK file contains PowerShell command to download and run a python script from a remote IP address.
- Python script downloads and runs VSCode CLI binary, called code-insiders.exe
- We’re using the CLI version of VSCode, I used the insiders version. This is just a beta version of VSCode. The non-beta version can be download from here.
- Python script creates and authenticates a VSCode tunnel, using the CLI binary against Github.
- As was the case with Dev Tunnels, you authenticate your VSCode tunnel against either a Microsoft or Github account. Github allows for device code authentication, making it perfect for usage in a script. More on that later
- VSCode remote tunnel is established.
- Threat actor performs command execution of python payload, via the tunnel established from a web browser
Attacker Telemetry
We’ll walk through what the attack chain looks like, both in our logging and from our attackers perspective. As always, I’m using Elastic, and their endpoint agent for my lab.
LNK File Execution
Below shows the LNK file execution. Nothing fancy, it contains a simple PowerShell one liner that downloads a Python script from my web server, and executes the script.
Python Script Execution
The Python Script downloads the CLI binary for the Code Insiders version of VSCode. It then creates a tunnel, and generates an authentication link.
Important Flags and Authentication
In the snippet from my Python Script that sets up the tunnel below, we can see we’re parsing the flags tunnel
and --accept-server-license-terms
both of these flags are required establishing a tunnel. They can be used for in a detection opportunity, but more on that later.
def setup_remote_tunnel(vscode_cli_path, timeout=60):
"""Set up a VSCode Remote Tunnel."""
print("Setting up VSCode Remote Tunnel...")
process = subprocess.Popen(
[vscode_cli_path, "tunnel", "--accept-server-license-terms"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE, # Redirect stdin to provide input
text=True
You may also note that we’ve not parsed any flags to force GitHub device authentication. This is because the CLI versions of VSCode use this method of authentication by default.
All we need to do is capture the code, and we can send it back to ourselves via POSTing it to our web server.
Connecting to the tunnel
An attacker needs to take the GitHub device code for authentication generated by the Code CLI, and enter at the below URL. This will need to be done while being authenticated to GitHub with their account.
https://github.com/login/device
We then enter our device code, that was captured by our Python Script, and generated by the VSCode CLI.
The attacker then needs to authorise the tunnel associated and connected with their account. Which I’m sure they’ll have no issue doing 😉
Now they need to access VSCode in their browser. If they’re using the standard version of VSCode, then the domain accessed will be vscode.dev
.
However, as I’m using the Code Insiders version, the domain I’ll be hitting will instead be insiders.vscode.dev
We then hit the connect to tunnel button, and authenticate to VSCode with out attackers GitHub account.
Once authenticated to our account, we should see a list of remote hosts that have a tunnel running, which we can connect to.
And selecting our online victim host will establish a connection to the VSCode remote tunnel running on that host.
We can now traverse directories on our victims remote host.
And even worse, we can create new files/scripts, and execute them remotely.
Below shows a simple Python script to collect some host information from our victims host. We were able to create this script, and run it remotely all through our browser session, connected to our victims host via a tunnel.
And the information collected by the script, we had it write to a txt file on our Victims host.
Attacker Conclusion
Summing up this section, the VSCode remote tunnels feature is extremely powerful and gives the ability to perform full remote code execution. The scariest element, is that we were able to do all this via a simple LNK file delivered to the user. With this in mind, let us look at how we can detect what our attacker has done.
Detecting VSCode Remote Tunnels
We’re going to break down the different elements of the attacker chain into their own hunts and detections.
PowerShell Execution
Firstly, we’ll look at the execution of the LNK file execution one liner to download and run our Python Script. Some EDRs are able to capture the LNK file name in the child process execution event, but it seems Elastic does not 😦.
I looked for cases where explorer is the parent of PowerShell, as this would be the typical relationship when a LNK file is used to launch a PowerShell script.
We can further filter down by looking for certain PowerShell arguments, including Invoke-WebRequest
, OutFile
and python
.
Below is a fairly disgusting KQL query to match on this.
process.parent.name: explorer.exe and process.name : "powershell.exe" and process.args : *Invoke-WebRequest* and process.args : *OutFile* and process.args : *python*
I prefer to use regex and Elasticsearch DSL instead, below is the query to use that method instead.
{
"query": {
"bool": {
"must": [
{ "term": { "host.os.type": "windows" } },
{ "term": { "event.type": "start" } },
{ "term": { "process.name": "powershell.exe" } },
{ "wildcard": { "process.parent.executable": "*explorer.exe" } },
{
"regexp": {
"process.args": ".*(Invoke-WebRequest.*OutFile.*python|Invoke-WebRequest.*python.*OutFile|OutFile.*Invoke-WebRequest.*python|OutFile.*python.*Invoke-WebRequest|python.*Invoke-WebRequest.*OutFile|python.*OutFile.*Invoke-WebRequest).*"
}
}
]
}
}
}
This should show results where PowerShell is reaching out to a remote location, where a file is downloaded, and python is invoked to execute a script.
VSCode Tunnel Setup
Looking for code.exe or code-insiders.exe being written to disk would be pointless. A half decent attacker will rename the binaries, and furthermore, will have them downloaded from their own infrastructure.
Therefore, we must focus on the behaviours the attacker can not change. Firstly, the --accept-server-license-terms
flag parsed to the CLI Code Binary.
process.args: *--accept-server-license-terms*
Focusing on the flag in the process arguments/command line gives us a few interesting results.
process.parent.executable: C:\Windows\System32\cmd.exe
process.parent.command_line: "cmd" /Q /C C:\Users\paul\.vscode-insiders\cli\servers\Insiders-89f808979a5151bd91324e65d4f7ab1b62896983\server\bin\code-server-insiders.cmd --connection-token=REDACTED --accept-server-license-terms --start-server --enable-remote-auto-shutdown --socket-path=\\.\pipe\code-insiders-1aabdd67-c31d-4f2a-8983-018f3d4e74e6
process.command_line: "C:\Users\paul\.vscode-insiders\cli\servers\Insiders-89f808979a5151bd91324e65d4f7ab1b62896983\server\bin\..\node.exe" "C:\Users\paul\.vscode-insiders\cli\servers\Insiders-89f808979a5151bd91324e65d4f7ab1b62896983\server\bin\..\out\server-main.js" --connection-token REDACTED --accept-server-license-terms --start-server --enable-remote-auto-shutdown --socket-path \\.\pipe\code-insiders-1aabdd67-c31d-4f2a-8983-018f3d4e74e6
process.executable: C:\Users\paul\.vscode-insiders\cli\servers\Insiders-89f808979a5151bd91324e65d4f7ab1b62896983\server\node.exe
In the above we can see one set of results, where a new .vscode directory has been created,
.vscode-insiders\cli\servers\Insiders-89f808979a5151bd91324e65d4f7ab1b62896983\server\bin..\
We can see the below flags have been parsed to a node.exe binary, running the JS file, server-main.js:
--connection-token REDACTED
--accept-server-license-terms
--start-server
--enable-remote-auto-shutdown
--socket-path \.\pipe\code-insiders-1aabdd67-c31d-4f2a-8983-018f3d4e74e6
We can also see part of the process tree, where code-insiders has spawned cmd.exe, to spawn node.exe to run the server-main.js.
Using these process relationships, and command line arguments can provide detection and hunt opportunities.
Script Execution
Next, we’ll take a look at our Python Enumeration Script execution, that the TA ran from their web browser on our victims host. Below is an example screenshot of the activity.
And here we have the command line for the parent process, that launches Python. Our TA used a PowerShell terminal, within VSCode to run our Python Script.
[C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe, -noexit, -command, try { . "c:\Users\paul\.vscode-insiders\cli\servers\Insiders-89f808979a5151bd91324e65d4f7ab1b62896983\server\out\vs\workbench\contrib\terminal\common\scripts\shellIntegration.ps1" } catch {}]
The interesting elements from the parent command line, are the path , and the shellIntergation.ps1 script. This script is what provides a PowerShell interface within VSCode.
We can find activity like this in the future with a query like the below. However, bare in mind, the interpreter or terminal the TA uses from within the VSCode session could be something different.
process.parent.name: powershell.exe and process.name.caseless : python.exe and process.parent.command_line.caseless : *shellIntegration.ps1* and process.parent.command_line.caseless: *cli\\servers*
Network Connections
The main domain used by VSCode tunnels is:
global.rel.tunnels.api.visualstudio.com
Blocking this domain in your organisation should block the usage of the tunnels.
Other domains used as part of VSCode tunnels include:
URL/Domain | Description |
https://code.visualstudio.com/sha/download?build=stable&os=cli-alpine-x64 | VSCode CLI Download Domain |
https://code.visualstudio.com/sha/download?build=insider&os=cli-win32-x64 | Code Insiders CLI Download |
vscode.dev | Domain for VSCode browser session |
insiders.vscode.dev | Domain for Code Insiders browser session. |
Forensics/Logging
If the target user already has the full VSCode application installed, and a remote tunnel is established, there’s a useful state table you can load into SQLite Browser.
Located in the below locations
- Windows:
C:\Users\*\AppData\Roaming\Code\User\globalStorage
\state.vscdb - Mac OS:
/Library/Application Support/Code/User/globalStorage/state.vscdb
- Linux:
/.config/code/User/globalStorage/state.vscdb
This table give some useful information about the tunnel sessions, included mode of authentication, the account used to authenticate, and timestamps of tunnel sessions.
remoteTunnelServicePromptedPreview true
remoteTunnelHasUsed true
remoteTunnelSession {"providerId":"github","sessionId":"f69cea964f86159","accountLabel":"REDACTED"}
remoteTunnelServiceUsed {"hostName":"home-pc2","timeStamp":1736796256912}
remoteTunnelIsService false
One of the fields in the logs remoteTunnelIsService: False
, this indicates whether the Remote Tunnel was configured to run as a service. When this is done, it means a tunnel is setup anytime the host is brought online. This therefore, can provide a means of persistent access to an attacker.
This can be done but parsing the service flag on the tunnel install.
code tunnel service install
An example of this with the full tunnel setup can be seen below.
code-tunnel.exe tunnel service install --accept-server-license-terms --log info --name Home-PC
CLI Logging
CLI versions of VSCode do have some logging as well, however they are not as useful as the state table. Regardless, they might be worth a look. They are typically located in:
C:\Users\*\.vscode-server\data\logs
C:\Users\*\.vscode\cli\servers
Mitigations
There are some Group Policy settings which allow restrictions to be implemented on VSCode Remote Tunnels, as well as Dev Tunnels.
Disable anonymous tunnel access: Disallow anonymous tunnel access. This means users cannot connect to an existing tunnel with anonymous access control, host an existing tunnel with anonymous access control, or add anonymous access to existing or new tunnels.
Disable Dev Tunnels: Disallow users from using the Dev Tunnels service. All commands, with few exceptions, should be denied access when this policy is enabled. Exceptions: unset, echo, ping, and user.
Allow only selected Microsoft Entra tenant IDs: Users must authenticate within the given tenant list to access Dev Tunnels.
A template for these settings is available here.
Closing Out
The content covered shows how easy it is for a threat actor to utilise VSCode remote tunnels into their campaigns. Organisations should look to enforce authentication to remote tunnels against their own tenants only.
In cases where this is not possible, tunnel usage should be blocked from the estate, or detections should be implemented on their abuse. Threat Actors are increasingly using these tunnels in their campaigns.