VSCode Vulnerability and an “Objective” Look at Git
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:
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 RestrictedConfigurations
where in the array you can specify Setting IDand this parameter will be restricted in an untrusted environment:
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 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:
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 init
it can be edited manually or with commands. Git supports configuration from three sources, and each level replaces the previous one: system /etc/gitconfig
global ~/.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:
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 config
then 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:
Here we are interested in the dedicated team ls-files -- README.md
its 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
:
The payload launch event looks like this:
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:
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 Git
which 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 isTrusted
which 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).
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:
The checking algorithm is as follows:
Initially, any workspace is untrusted unless previously selected otherwise.
Using the expression
!workspace.isTrusted
a check is performed to determine whether the workspace belongs to an untrusted area.Function
onDidGrantWorkspaceTrust
Waiting for the workspace trust state to change.If the workspace is trusted and contains folders to initialize the repository, it initiates repository discovery.
If the workspace is trusted and there are no folders in the workspace to initialize the repository, it returns
emptyDisposable
which does not call any operations and is a stub.
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:
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 placesh.exe
and command line withpowershell.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:
The first event is the launch of VSCode. It is the parent of the Git process.
The second event is running Git with the file listing command
ls-files
.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.