VSCode Vulnerability and an “Objective” Look at Git

VSCodeand how attackers can use it for their own purposes. As of today, it is known about 229 million extension installations via the built-in store containing malicious code. Vulnerabilities in code editors have been used for a long time, including APT (Advanced Persistent Threat) using social engineering, as this was and with Visual Studio.

In this article, we will look at the architectural features of VSCode related to code execution security. We will also analyze the vulnerability CVE-2023-46944 and we will explain why, despite the fact that the developer GitKraken patched it in 2023, it can still be potentially dangerous due to the peculiarities of working with Git. In addition, we will tell how exactly this vulnerability was patched and propose a rule for detecting it using the VRL language and the R-Object plugin.

VSCode Security Features

Visual Studio Code (VSCode) is a powerful and popular integrated development environment (IDE) built on the platform Electron. It combines the capabilities of Node.js and the browser Chromiumgiving developers additional flexibility. VSCode inherits the process model from Electron, separating functionality into different processes for increased security. This means that the rendering processes running in the embedded Chromium browser have limited privileges, while the main process running on Node.js has elevated privileges and can interact directly with the operating system. Despite the extensive open source (almost 800 thousand lines of code), official assemblies from Microsoft contain proprietary components.

VSCode Workspace Trust is a security feature introduced in Visual Studio Code in 2021. It helps prevent malicious code and potential vulnerabilities from running from an untrusted environment. An untrusted environment is a directory that has been opened for the first time or for which no trust type has been defined. You can select it in the pop-up window:

Workspace Trust popup when opening a new directory

Workspace Trust popup when opening a new directory

In a trusted workspace, all application features and extensions are available, while in an untrusted workspace, some features and extensions may not be available. The restrictions apply to extensions that interact with the file system, run scripts, or access network resources. To prevent unintentional execution of potentially dangerous code, debugging, task launching, and terminal commands will be restricted in untrusted workspaces. The idea is that by marking an area as untrusted, you can inspect all the code in it and set the value to trusted after inspection.

Sounds a bit idealistic, doesn't it?

Let's take a closer look at how our vulnerability relates to this.

GitLens Vulnerability (CVE-2023-46944)

A vulnerability was discovered in 2023 CVE-2023-46944which uses the GitLens extension to run malicious code from an untrusted environment. But how does this happen? documentation we see that VSCode extensions can have three types of work with environments – true, false And limited:

capabilities:
  untrustedWorkspaces:
    { supported: true } |
    { supported: false, description: string } |
    { supported: 'limited', description: string, restrictedConfigurations?: string[] }

The first two types of settings either allow the use of extensions in untrusted environments or block their operation. The third type of settings provides the ability to limit the use of extensions. This type is specified in the project file package.json. In this case, certain parameters can be automatically limited using the property RestrictedConfigurationswhere in the array you can specify Setting IDand this parameter will be restricted in an untrusted environment:

Setting ID Example

Setting ID Example

The GitLens extension does not have a definition of such properties, but the screenshot shows that the type is supported limited, and this means that some functionality will work in untrusted environments:

Gitlens repository package.json file

File package.json Gitlens repository

GitLens versions prior to 14.0.0 lacked a check for being in an untrusted environment. When opening a repository, the extension would contact Git to initiate the check, which allowed malicious code to be run. Starting with Git 2.37.0, an interesting feature was added core.fsmonitor. If we turn to documentation using Web Archive and looking at the function description for 2021, we will see the following:

git-config page in web.archive

git-config page in web.archive

It is assumed that in the variable fsmonitor may contain commands.

What is the function for? core.fsmonitor and how to use it? To do this, let's figure out what Git config is, since it contains the function fsmonitor.

Git config – is a configuration file for Git that stores settings specific to a particular repository. It is created when the repository is initialized. git initit can be edited manually or with commands. Git supports configuration from three sources, and each level replaces the previous one: system /etc/gitconfigglobal ~/.gitconfig and local repository .git/config. At this stage we are interested in the file .git/config.

Function fsmonitor allows for calls git status, git add, git diff and other commands that require information about files in a local repository, use indexing to avoid going through all the files. This significantly speeds up Git's work with large repositories. fsmonitor how can i write the value of the type boolean to enable or disable the function, and specify the executable file. To run an arbitrary file (the calculator in the example below), .git/config the file should look like this:

[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    fsmonitor = calc.exe

Let's see how our vulnerability works.

Exploitation of vulnerability

Let's check what happens if we run illegitimate code in VSCode 19.0 using the GitLens 13.6.0 extension as described in PoC for vulnerability CVE-2023-46944:

Demonstration of running calculator in VSCode. Vulnerability CVE-2023-46944.

Demonstration of running calculator in VSCode. Vulnerability CVE-2023-46944.

The question arises, how does the command launch from fsmonitor? When you open a workspace with files and folders in VSCode, Git tries to determine whether the workspace is a repository. And if it finds familiar folders and files in it, such as .git, HEAD or configthen it tries to initialize it and check if there are changes in the open repository. That is, when opening a directory in VSCode (in our case, clicking on the file README.md) commands for synchronization with the remote repository are launched.

Let's see how this would look in events using malicious commands, not just running a calculator. For this, we'll take a payload like Fetch Payloadsince loads that do not use commands to start do not work in this case. In the file .git/config it looks like this:

[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    fsmonitor = "powershell -w hidden -nop -e <payload> #

IN <payload> the utility launch is used curl (command line tool for transferring data over various network protocols).

Event Analysis

When you open a file, a series of commands are executed to check for changes in the open repository.

Let's analyze the sequence of events using R-Vision SIEM:

Event from R-Vision SIEM interface

Event from R-Vision SIEM interface

Here we are interested in the dedicated team ls-files -- README.mdits execution triggers the function call fsmonitor. Similar events occur in Windows.

We see that the parent process for reading README.md from Git is a process code:

Parent process for Git

Parent process for Git

The payload launch event looks like this:

Payload launch

Payload launch

In field spid (means parent process) the value is found dpid (PID of the current process) from the Git startup event, so the startup /bin/sh is a child process of Git. Now we understand that this looks like a chain of processes.

This is a 2023 vulnerability, and you might think that since all extensions are updated by default in VSCode, there is nothing to worry about. However, this is not entirely true. Let's see how things are going with fixing this vulnerability.

Patching

The patch for the vulnerability can be found here commit. Changes to the project were made in build 14.0.0. To eliminate the vulnerability, additional checks for being in an untrusted environment were added – Untrusted Workspace. Earlier in the article, we found out that Gitlens had no environment type check, and the extension launched Git to initialize the repository and perform file indexing operations.

To file src/git/errors.ts exported class was added WorkspaceUntrustedError. It is an error type that can be thrown and caught specifically to handle cases where Git operations are not allowed due to an untrusted workspace:

Adding error handling to close the vulnerability

Adding error handling to close the vulnerability

Class WorkspaceUntrustedError also used in src/env/node/git/git.ts. This file is responsible for the interaction of the GitLens extension with Git itself. In the class Gitwhich is used to process and run commands, a check for the type of workspace has been added. The check is performed using the VSCode API function isTrustedwhich is used in extensions with the value limited. At the beginning of the article we learned that the meaning limited allows you to implement the operation of individual extension functions in an untrusted environment (more details in documentation).

Added workspace type check

Added workspace type check

File src/git/gitProviderService.ts abstracts interaction with Git, providing a consistent API for the rest of the GitLens extension and interaction with repositories, regardless of the specific implementation or environment. The following checks have been added:

Added checks for changing the workspace type to trusted

Added checks for changing the workspace type to trusted

The checking algorithm is as follows:

  1. Initially, any workspace is untrusted unless previously selected otherwise.

  2. Using the expression !workspace.isTrusted a check is performed to determine whether the workspace belongs to an untrusted area.

  3. Function onDidGrantWorkspaceTrust Waiting for the workspace trust state to change.

  4. If the workspace is trusted and contains folders to initialize the repository, it initiates repository discovery.

  5. If the workspace is trusted and there are no folders in the workspace to initialize the repository, it returns emptyDisposablewhich does not call any operations and is a stub.

Empty function emptyDisposable

Empty function emptyDisposable

So, GitLens added a check for the type of active Workspace. Well, it seems like all the checks are added. But how are things now?

If we try to open our repository with malicious code in the updated GitLens, the code will indeed not run, but only if we select an untrusted environment. If we select the environment as trusted, the code will run successfully.

Let's be honest, how often do you check all the files in a repository for suspicious strings when you download it? If you use git clone <repository>you will not download .git/config file and you probably won't encounter this particular vulnerability. But what if it's a repository that was obtained from another source?

Let's open a malicious repository in VSCode without the GitLens plugin, but in a trusted environment:

Demonstration of Reverse-Shell launch

Demonstration of Reverse-Shell launch

We have malicious code running again:

  • on the left you can see that the launch occurs when you select an untrusted environment in the pop-up window;

  • after opening the file README.md in the lower right window the launch is taking place sh.exe and command line with powershell.exe;

  • in the upper right window the reverse-shell is obtained, in this case cmd/windows/http/x64/shell_reverse_tcp.

The demonstrated PoC uses a similar command to run the code using PowerShell.

File contents .git/config:

[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    fsmonitor = "powershell -w hidden -nop -e <payload> #"

Detection

Since there is no simple solution to prevent this activity, you can use the correlation rules for Windows and Linux, which are built on the detection of the chain of events given earlier.

Let's build a detection of the following chain of events:

  1. The first event is the launch of VSCode. It is the parent of the Git process.

  2. The second event is running Git with the file listing command ls-files.

  3. The third event is the launch of the shell. sh with execution of commands through -c.

Using R-Vision SIEM as an example, let's see how our detection rules detect such malicious activity:

Windows

Correlation rule for Windows written in R-Object language in R-Vision SIEM:

Correlation Rule for Windows
id: e65f4f66-e0ee-4eef-8d98-513d184fae84
name: Эксплуатация уязвимости CVE-2023-46944 в расширении GitLens для VSCode на Windows
version: 1.0.0
date: 2024-05-29
author: Vladislav Kormishkin, R-Vision
status: stable

type: correlation_rule
severity: high
description: Правило срабатывает при открытии Git репозитория в VSCode, как в доверенной, так и в недоверенной среде, где в файле .git/config в функции fsmonitor указана вредоносная команда. Данная активность относится к уязвимой версии расширения GitLens до 13.6.0 включительно, ей присвоен номер CVE-2023-46944. Также данная активность может возникнуть при открытии вредоносного Git репозитория в доверенной среде, но с пропатченой версией GitLens или вообще без установленного расширения, в версиях редактора старше 1.63.1.

reference:
  - https://packetstormsecurity.com/files/178227/GitLens-Git-Local-Configuration-Execution.html

tags:
  - Execution
  - attack.T1203

data_source:
  - Windows
  - Security
    - EventID_4688
  - Sysmon_Operational
    - EventID_1

known_false_positives:
  - "Пока неизвестно"

group_by:
  - dvchost

filter: !vrl |
  .dvendor == "Microsoft" && 
  includes(["1", "4688"], .externalId) 

aliases:
  event_code:
    filter: !vrl |
      oldFileName = downcase(to_string(.oldFileName) ?? "-")
      sproc = downcase(to_string(.sproc) ?? "-")
      flag = false

      if ends_with(sproc, "\\code.exe") ||
          oldFileName == "electron.exe" {
              flag = true 
      }
      flag

  event_git:
    filter: !vrl |
      cmd = downcase(to_string(.cmd) ?? "-")
      oldFileName = downcase(to_string(.oldFileName) ?? "-")
      dproc = downcase(to_string(.dproc) ?? "-")
      sproc = downcase(to_string(.sproc) ?? "-")
      flag = false

      if (ends_with(sproc, "\\git.exe") ||
             oldFileName == "git.exe") &&
              contains(cmd, "ls-files")  {
                  flag = true
      }
      flag

  event_sh:
    filter: !vrl |
      cmd = downcase(to_string(.cmd) ?? "-")
      oldFileName = downcase(to_string(.oldFileName) ?? "-")
      dproc = downcase(to_string(.dproc) ?? "-")
      sproc = downcase(to_string(.sproc) ?? "-")
      flag = false

      if ((ends_with(sproc, "\\git.exe") ||
           oldFileName == "git.exe") &&
              ends_with(dproc, "\\sh.exe") &&
              starts_with(cmd, "sh -c"))  {
                  flag = true 
      }
      flag
  
select:
    alias: event_code
    join:
      alias: event_git
      on:
        - eq: {event_code: .dpid, event_git: .spid}
      join:
          alias: event_sh
          on:
            - eq: {event_git: .dpid, event_sh: .spid}

ttl: 60

on_correlate: !vrl |
  . |= compact({
    "rt" : %event_sh.rt,
    "dvendor" : %event_sh.dvendor,
    "dversion" : %event_sh.dversion,
    "dhost" : %event_sh.dhost,
    "dproc" : %event_sh.dproc,
    "dvchost" : %event_sh.dvchost,
    "oldFilePath" : %event_sh.oldFilePath,
    "duser" : %event_sh.duser,
    "suser" : %event_sh.suser,
    "sntdom" : %event_sh.sntdom,
    "sproc" : %event_sh.sproc,
    "accessMask" : %event_sh.accessMask,
    "externalId" : %event_sh.externalId,
    "oldFileName" : %event_sh.oldFileName,
    "fname": %event_sh.fname,
    "dntdom" : %event_sh.dntdom,
    "cmd" : %event_sh.cmd,
    "sourceServiceName" : %event_sh.sourceServiceName,
    })
    
    .msg = "На узле " + (to_string(.dvchost) ?? "-") + " пользователем " + (to_string(.duser) ?? "-") + " домена " + (to_string(.dntdom) ?? "-") + " от процесса Git с родительским процессом VSCode запущена команда " + (to_string(.cmd) ?? "-") + ", данное поведение может указывать на эксплуатацию уязвимости CVE-2023-46944"

Linux

Correlation rule for Linux written in R-Object language in R-Vision SIEM:

Correlation Rule for Linux
id: 62697bd1-1731-4437-bc31-572815389f29
name: Эксплуатация уязвимости CVE-2023-46944 в расширении GitLens для VSCode на Linux
version: 1.0.0
date: 2024-05-30
author: Vladislav Kormishkin, R-Vision
status: stable

type: correlation_rule
severity: high
description: Правило срабатывает при открытии Git репозитория в VSCode, как в доверенной, так и в недоверенной среде, где файле .git/config в функции fsmonitor указана вредоносная команда. Данная активность относится к уязвимой версии расширения GitLens до 13.6.0 включительно, ей присвоен номер CVE-2023-46944. Также данная активность может возникнуть при открытии вредоносного Git репозитория в доверенной среде, но с пропатченой версией GitLens или вообще без установленного расширения, в версиях редактора старше 1.63.1.

reference:
  - https://packetstormsecurity.com/files/178227/GitLens-Git-Local-Configuration-Execution.html

tags:
  - Execution
  - attack.T1203

data_source:
  - Linux
    - Auditd
      - Execve

known_false_positives:
  - "Пока неизвестно"

group_by:
  - dvchost

filter: !vrl |
  .dvendor == "Linux" && 
  .cs4 == "execve" 

aliases:
  event_code:
    filter: !vrl |
      dproc = downcase(to_string(.dproc) ?? "-")
      flag = false

      if ends_with(dproc, "/code/code") {
            flag = true 
      }
      flag

  event_git:
    filter: !vrl |
      cmd = downcase(to_string(.cmd) ?? "-")
      dproc = downcase(to_string(.dproc) ?? "-")
      flag = false

      if ends_with(dproc, "/bin/git") &&
          contains(cmd, "ls-files")  { #перечисление файлов
              flag = true 
      }
      flag
  
  event_sh:
    filter: !vrl |
      filePath = downcase(to_string(.filePath) ?? "-")
      dproc = downcase(to_string(.dproc) ?? "-")
      cmd = downcase(to_string(.cmd) ?? "-")
      flag = false

      if contains(filePath, "/bin/sh") &&
          contains(cmd, "-c"){
              flag = true 
      }
      flag

select:
    alias: event_code
    join:
      alias: event_git
      on:
        - eq: {event_code: .spid, event_git: .dvcpid}
      join:
          alias: event_sh
          on:
            - eq: {event_git: .spid, event_sh: .dvcpid}

ttl: 60

on_correlate: !vrl |
  . |= compact({
    "rt" : %event_sh.rt,
    "dvendor" : %event_sh.dvendor,
    "dversion" : %event_sh.dversion,
    "dhost" : %event_sh.dhost,
    "dproc" : %event_sh.dproc,
    "dvchost" : %event_sh.dvchost,
    "oldFilePath" : %event_sh.oldFilePath,
    "duser" : %event_sh.duser,
    "suser" : %event_sh.suser,
    "sntdom" : %event_sh.sntdom,
    "sproc" : %event_sh.sproc,
    "accessMask" : %event_sh.accessMask,
    "externalId" : %event_sh.externalId,
    "oldFileName" : %event_sh.oldFileName,
    "fname": %event_sh.fname,
    "dntdom" : %event_sh.dntdom,
    "cmd" : %event_sh.cmd,
    "sourceServiceName" : %event_sh.sourceServiceName,
    })
    
    .msg = "На узле " + (to_string(.dvchost) ?? "-") + " пользователем " + (to_string(.suser) ?? "-") + " от процесса Git с родительским процессом VSCode запущена команда " + (to_string(.cmd) ?? "-") + ", данное поведение может указывать на эксплуатацию уязвимости CVE-2023-46944"

Conclusion

In this article, we looked at the capabilities of Workspace Trust and ways to prevent execution of potentially malicious code. We also discussed the causes of the CVE-2023-46944 vulnerability.

We found that if malicious repositories are opened in a trusted environment, the patch will not help, since this activity is related to the functionality of the integrated Git extension. Correlation rules based on the chain of events can be used to track it.

Unfortunately, there is no simple solution to fully protect yourself in this situation. However, we can highlight the main recommendations:

  • check everything you open in a code editor if it was downloaded from an unknown or untrusted source;

  • Disable Git integration by default.

I will be glad if the article was useful for you! Ask questions and write comments.

Author:
Vladislav Kormishkin (@Watislove), cybersecurity threat research analyst at R-Vision.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *