DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Using Unblocked to Fix a Service That Nobody Owns
  • Writing Great Code: The Five Principles of Clean Code
  • Oracle NoSQL Database: A Comprehensive Guide for Developers
  • Day in the Life of a Developer With Google's Gemini Code Assist: Part 2

Trending

  • Cloud Security and Privacy: Best Practices to Mitigate the Risks
  • Why Database Migrations Take Months and How to Speed Them Up
  • Beyond Simple Responses: Building Truly Conversational LLM Chatbots
  • MySQL to PostgreSQL Database Migration: A Practical Case Study
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. Securing Developer Tools: Argument Injection in Visual Studio Code

Securing Developer Tools: Argument Injection in Visual Studio Code

This article demonstrates how a vulnerability in one of the VS Code URL handlers could lead to the execution of arbitrary commands on the victim's host.

By 
Thomas Chauchefoin user avatar
Thomas Chauchefoin
·
Updated Dec. 13, 22 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
7.2K Views

Join the DZone community and get the full member experience.

Join For Free

The safety of these applications is crucial to prevent attackers from compromising the computer on which developers are working, as they could use this access to obtain sensitive information, alter source code, and further pivot into the company's internal network.

This time, my team and I dive into a new vulnerability I identified in one of the most popular IDEs: Visual Studio Code. It allowed attackers to craft malicious links that, once interacted with, would trick the IDE into executing unintended commands on the victim's computer. By reporting the issue to Microsoft, who quickly patched it, our researchers helped to secure the developer ecosystem. 

Impact

The vulnerability can be used to target developers that have the Visual Studio Code IDE installed. Upon clicking on a malicious link crafted by an attacker, victims are prompted to clone a Git repository in Visual Studio Code. This operation is genuine and part of the workflow of most users. For instance, this is how GitLab allows easier cloning of projects:

A dropdown menu called "Open in your IDE" on a GitLab instance.

If the developer accepts this operation, attackers can execute arbitrary commands on the victim's computer. 

Interestingly, Workspace Trust, a feature to harden the IDEs and reduce the risk of unintended commands being executed, is not strictly enforced here. If the last Visual Studio Code window with focus is trusted by the current workspace, this security feature will not have the expected effect. 

I disclosed this finding to Microsoft through their Researcher Portal, and the patch was released as part of Visual Studio Code 1.67.1 and higher. Microsoft published limited information about this bug as part of their security bulletin and assigned it CVE-2022-30129. 

In the sections below, I'll first describe how URL handlers are designed in Visual Studio Code and then review the implementation of the one reserved for Git actions to identify an argument injection bug. Further sections will describe how it could be exploited to gain the ability to execute arbitrary commands, as well as the patch implemented by Microsoft. 

Visual Studio Code URL Handlers

Visual Studio Code is most commonly used as a stand-alone desktop application, thanks to Electron. This choice still allows some level of integration with the user's operating system, for instance, by allowing applications to register custom URL protocol handlers. In the case of Visual Studio Code, vscode:// is registered, and vscode-insiders:// for the nightly builds (also called Insiders build). This feature is named Deep Links. 

The IDE allows internal and external extensions to listen to such events and handle them by registering sub-handlers. The main listener will handle such OS-level events and redirect them to the right extension. 

They have to implement a simple interface with a method named handleUri() and announce it to the IDE with window.registerUriHandler():

src/vscode-dts/vscode.d.ts

TypeScript
 
export interface UriHandler {
	handleUri(uri: Uri): ProviderResult<void>; 
}


Finding an Argument Injection in the Git Module

With this design in mind, it is now possible to start looking for URL handlers in the core of Visual Studio Code. At that time, three were available: extensions/github-authentication and extensions/microsoft-authentication to authenticate with third-party services and obtain the resulting access tokens, and extensions/git to allow users to clone remote repositories as shown above in GitLab.

With my prior experience reviewing the code of developer tools, I know that external invocations of version control tools are often riddled with argument injection bugs—you can head to the Related Posts section for a few examples. Let's look at the extensions/git's implementation of handlerUri first!

extensions/git/src/protocolHandler.ts

TypeScript
 
export class GitProtocolHandler implements UriHandler {
    // [...]
    handleUri(uri: Uri): void {
   	 switch (uri.path) {
   		 case '/clone': this.clone(uri);
   	 }
    }

    private clone(uri: Uri): void {
   	 const data = querystring.parse(uri.query);
	 // [...]
   	 commands.executeCommand('git.clone', data.url);
    }


The git.clone command is implemented in extensions/git/src/commands.ts; it is also possible to invoke it manually:

extensions/git/src/commands.ts

TypeScript
 
@command('git.clone')
async clone(url?: string, parentPath?: string): Promise<void> {
  	await this.cloneRepository(url, parentPath); 
}


Let's continue to dig deeper into the code to identify where the external Git binary is invoked: 

extensions/git/src/commands.ts

TypeScript
 
async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean } = {}): Promise<void> {
  	// [...]     
  	try {
  	// [...]
  		const repositoryPath = await window.withProgress(
  			opts, 
  			(progress, token) => this.git.clone(
  				url!, { 
  					parentPath: parentPath!, progress, recursive: options.recursive 
				}, 
  				token)
            );


extensions/git/src/git.ts

TypeScript
 
async clone(url: string, options: ICloneOptions, cancellationToken?: CancellationToken): Promise<string> {
	let baseFolderName = decodeURI(url).replace(/[\/]+$/, '').replace(/^.*[\/\\]/, '').replace(/\.git$/, '') || 'repository';
  	let folderName = baseFolderName;
  	let folderPath = path.join(options.parentPath, folderName);
  	// [...]
  	try {
		let command = ['clone', url.includes(' ') ? encodeURI(url) : url, folderPath, '--progress'];
  		if (options.recursive) {
  			command.push('--recursive');
		}
		await this.exec(options.parentPath, command, { 
        	cancellationToken, 
          	env: { 'GIT_HTTP_USER_AGENT': this.userAgent },
          	onSpawn,
        });


As promised, there is an argument injection bug in this code: the URL to clone the Git repository is fully controlled and concatenated into the external command line. If this URL starts with dashes, Git will understand it as an option instead of a positional argument. 

Exploiting an Argument Injection on a Git Clone Operation

Argument injection vulnerabilities are very interesting because they are all different and often imply some subtleties; this one is not an exception. This section describes one way to exploit it; other ways exist and are left as an exercise to the reader. 

Git used to implement git-remote-ext to "bridge smart transport to an external command," but this feature is now disabled by default. 

As a reminder, we have two injection points:

  1. url: the URL of the remote Git repository to clone;
  2. folderPath: the destination folder computed from the URL of the Git repository.

This is very important in this case as our injected option takes the place of a positional argument: without the second injection point, Git wouldn't have anything to clone from, and the injection wouldn't be exploitable.

Finally, if there is any space in the payload, it will be URL-encoded before its interpolation in the command line; it will be easier to try to craft one without spaces:

extensions/git/src/git.ts

TypeScript
 
let command = ['clone', url.includes(' ') ? encodeURI(url) : url, folderPath, '--progress'];


My team and I came up with the following payload:

  • vscode://: the custom scheme registered by Visual Studio Code to the operating system;
  • vscode.git/clone?url=: required to trigger the git.clone command in Visual Studio Code;
  • -u$({open,-a,calculator}): we inject the option -u, equivalent to --upload-pack, to override the command that will be used to communicate with the remote end;  
  • :x: this part is required to trick Git into using the transport layer, recognize it as PROTO_LOCAL and invoke the aforementioned upload-pack. 

Patch

Microsoft addressed this issue by improving its validation on the URL of the Git repository to clone as part of the commit c5da533. The URL is parsed using Uri, an internal URI parser,  to validate the scheme against a pre-established allow list. The rationale behind this change is that the argument injection bug can only happen if the prefix of the data is fully controlled, which won't be possible if the scheme part of the URL has to be part of this list. 

diff
 
--- a/extensions/git/src/protocolHandler.ts
+++ b/extensions/git/src/protocolHandler.ts
@@ -7,6 +7,8 @@ import { UriHandler, Uri, window, Disposable, commands } from 'vscode';
 import { dispose } from './util';
 import * as querystring from 'querystring';
 
+const schemes = new Set(['file', 'git', 'http', 'https', 'ssh']);
+
 export class GitProtocolHandler implements UriHandler {
 
 	private disposables: Disposable[] = [];
@@ -26,9 +28,27 @@ export class GitProtocolHandler implements UriHandler {
 
 		if (!data.url) {
 			console.warn('Failed to open URI:', uri);
+			return;
+		}
+
+		if (Array.isArray(data.url) && data.url.length === 0) {
+			console.warn('Failed to open URI:', uri);
+			return;
+		}
+
+		let cloneUri: Uri;
+		try {
+			cloneUri = Uri.parse(Array.isArray(data.url) ? data.url[0] : data.url, true);
+			if (!schemes.has(cloneUri.scheme.toLowerCase())) {
+				throw new Error('Unsupported scheme.');
+			}
+		}
+		catch (ex) {
+			console.warn('Invalid URI:', uri);
+			return;
 		}
-		commands.executeCommand('git.clone', data.url);
+		commands.executeCommand('git.clone', cloneUri.toString(true));
 	}
 	dispose(): void {


This finding was not eligible for a reward from the Microsoft Bug Bounty Program, as only the core is part of the scope; built-in extensions are de facto excluded even if they are enabled by default. This submission still yielded us 40 points for the Microsoft Researcher Recognition Program and got us on the MSRC 2022 Q2 Leaderboard. 

It is also interesting to note that the Visual Studio Code team started publishing information about security issues on GitHub on top of the usual security bulletin and release notes: the label security is now added to issues, and GitHub security advisories are published.

Timeline

Date Action
2022-04-05 This issue is reported to Microsoft on their Researcher Portal.
2022-05-06 Microsoft confirms the issue and starts working on a patch.
2022-05-10 The patch is part of the release 1.67.1.

Summary

In this publication, I demonstrated how a vulnerability in one of the Visual Studio Code URL handlers could lead to the execution of arbitrary commands on the victim's host. The exploitation technique I demonstrated can also be applied to any other argument injection on a git clone invocation. My team and I urge all developers to upgrade their IDE to the latest version and to remain careful when opening foreign links.

Git Integrated development environment Uniform Resource Identifier Visual Studio Code dev Injection Data Types

Published at DZone with permission of Thomas Chauchefoin. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Using Unblocked to Fix a Service That Nobody Owns
  • Writing Great Code: The Five Principles of Clean Code
  • Oracle NoSQL Database: A Comprehensive Guide for Developers
  • Day in the Life of a Developer With Google's Gemini Code Assist: Part 2

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!