Oct 10, 2022

Enter "Sandbreak" - Vulnerability In vm2 Sandbox Module Enables Remote Code Execution (CVE-2022-36067)

Yuval Ostrovsky
Architect
Gal Goldstein
Security Researcher

The Oxeye research team has found "Sandbreak", a critical remote code execution vulnerability in the popular sandbox library vm2. The vulnerability was disclosed to the project owners and was rapidly patched in version 3.9.11. GitHub has issued CVE-2022-36067 for this critical vulnerability and the maximum CVSS score of 10.0.

What Is The Potential Impact of this Vulnerability?

vm2 is a widely used JavaScript sandbox - according to the NPM package manager, it has more than 16 million monthly downloads and offers an isolated environment where applications can run untrusted code.

A threat actor who exploits this vulnerability will be able to bypass the vm2 sandbox environment and run shell commands on the machine hosting the sandbox. Sandboxes serve different purposes in modern applications, such as examining attached files in email servers, providing an additional security layer in web browsers, or isolating running applications in certain operating systems. Given the nature of the use cases for sandboxes, it’s clear that the vm2 vulnerability can have dire consequences for applications that use it. 

 The fact that this vulnerability has the maximum CVSS score of 10.0 and is extremely popular means its potential impact is widespread and critical.

Sandbox 101

An application may sometimes require the execution of untrusted code provided by the user as part of its business logic. This is considered dangerous since the user can abuse this mechanism to take over the application. Utilizing a sandbox mechanism such as vm2 helps to eliminate this risk. 

The term “sandbox” refers to an isolated environment within which the untrusted code can run in an attempt to mitigate the risk of malicious code affecting the host machine running it. While sandboxes are extremely useful as isolation mechanisms, they should be used with caution since it is possible to bypass the restrictions, as demonstrated below.

Technical deep dive

Background

The guiding principles behind our choice of research topics are:

  1. Pervasiveness - how wide-reaching is the vulnerability?
  2. Impact - how severe can the consequences be if the vulnerability is exploited?

As we looked for potential vulnerabilities to dig deeper into, the idea of sandboxes came up. By their very definition, sandboxes are considered safe places and trusted as mechanisms that isolate potentially dangerous code from our applications. But what would happen if this trust was compromised? This thesis drove our explorations and eventually led us to discover the vm2 sandbox vulnerability.

Laying the groundwork:

Our usual approach when evaluating a given software's security is first to analyze the previous security lapses discovered in the same software. This helps us better grasp the available attack surface and may also lead to low-hanging bugs stemming from incomplete fixes. It also helps us come up with techniques to bypass the implemented fixes. While reviewing the previous bugs disclosed to the vm2 maintainers, we noticed an interesting technique: the bug reporter abused the error mechanism in Node.js to escape the sandbox.

Node.js allows the application developer to customize the call stack of an error that occurred in the application. Customizing the call stack can achieve this by implementing the “prepareStacktrace” method under the global “Error” object. This means that when an error occurs and the “stack” property of the thrown error object is accessed, Node.js will call this method while providing it with a string representation of the error alongside an array of “CallSite” objects as arguments. The following screenshot shows Node.js attempting to call the “prepareStackTrace” function:

Each “CallSite” object in the array represents a different stack frame. Together, they comprise the call stack state when the error occurred. One of the methods exposed by the “CallSite” objects is “getThis” which is responsible for returning the “this” object that was available in the related stack frame. This behavior may lead to sandbox escapes as some of the “CallSite” objects may return objects created outside the sandbox when invoking the “getThis” method. After gaining hold of a “CallSite” object created outside the sandbox, it might be possible to access Node’s global objects and execute arbitrary system commands from there.

The vm2 maintainers were aware that overriding “prepareStackTrace” could lead to a sandbox escape and tried to mitigate this escape path by wrapping the Error object and the “prepareStackTrace” method with their own implementation, which prevents the users from overriding this method.

The reporter’s POC bypassed the logic above since vm2 missed wrapping specific methods related to the “WeakMap” JavaScript built-in type. This allowed the attacker to provide their own implementation of “prepareStackTrace”, then trigger an error, and escape the sandbox.

Escaping the sandbox

By this step, we understand that the prepareStackTrace function of the Error object is the function we want to override. Providing our own implementation of it while triggering an error would result in a sandbox escape.

That got us thinking about what would happen if we tried to use a similar escape technique, but instead of finding a way to override “prepareStackTrace” itself, we would simply try to override the global Error object with our own object, which implements the prepareStackTrace function.

The following code would result in our own implementation being called:

The only thing left to do here is to access the CallSite object of a frame that resides outside the sandbox; from there, we can access Node’s global members and access the currently executing process, which allows us to execute commands:

Takeaway

Although sandboxes are meant to run untrusted code within your application, you shouldn’t automatically assume that they are safe. If the use of a sandbox is unavoidable, it is recommended to separate the logical sensitive part of your application from the microservice that runs the sandbox code so if a threat actor successfully breaks out from the sandbox, the attack surface is limited to the isolated microservice.

Moreover, avoid using a sandbox that relies on a dynamic programming language such as JavaScript when possible. The dynamic nature of the language widens the attack surface for a potential attacker, making defending against such attacks much harder.

Vulnerability researchers are more likely to look at the high-profile dependencies of your application, resulting in more frequent vulnerabilities within the dependency. Make sure to monitor your application dependencies frequently and upgrade their versions accordingly.

Discovery and Disclosure Timeline

Leveraging Oxeye to Assess the Potential Severity of the VM2 Vulnerability - CVE-2022-36067 

Our research team uses our commercial tool when they perform vulnerability research (yes, we eat our own dog food here at Oxeye!). This has the dual effect of helping us map the potential threats, detect vulnerabilities for our customers and in open-source projects, and helping us develop internal policies in our engine that enable us to find and rank code vulnerabilities better. 

One of the key tenets of cloud native application architecture is the distributed nature of the architecture. In monolithic applications, all vulnerabilities happen within the same codebase. In modern architectures, vulnerabilities can span multiple code components and infrastructure layers. Correspondingly, the very nature of vulnerabilities has changed, from residing in code blocks to traversing application flows. This results in new attack vectors, and demands a new way of testing for vulnerabilities.

Oxeye’s application security solution combines static analysis with flow tracing and infrastructure analysis. This multilayered approach provides data that enables us not only to find these vulnerabilities, but to also identify the vulnerable flows associated with each instance of the vulnerability. We make the vulnerability insights even smarter by differentiating which dependencies in your app are merely installed and which are actually loaded and used by the application. This allows us to rank the severity of vulnerable packages such as the vm2 package in your application to help you focus remediation efforts on the most critical vulnerabilities first. 

The image below illustrates this more clearly. While the same vulnerability is discovered three times in the staging environment of the application, each instance of the vulnerability possesses a different level of severity. 

Multilayered analysis, vulnerable flow tracing, and the ability to differentiate between packages that are loaded and used and those that are just installed are critical requirements for modern application security. Contact us to learn more.

About Oxeye

Oxeye provides a cloud-native application security solution designed specifically for modern architectures. We enable you to quickly identify and resolve all application-layer risks as an integral part of the software development lifecycle (SDLC). Our seamless, comprehensive, and effective solution ensures touchless assessment, focuses on exploitable risks, and provides actionable remediation guidance. Built for DevOps and AppSec teams, Oxeye helps shift security left while accelerating development cycles, reducing friction, and eliminating risks. Visit www.oxeye.io to learn more.