How I exploited an arbitrary code execution vulnerability in fast-redact

Intro
This is the story of how I discovered a not-so-trivial JavaScript Code Injection during a code review of the open source library fast-redact on GitHub and how I turned it into a fully working Arbitrary Command Execution. The not-so-trivial comes from the fact that the injected code is being evaluated in a sandbox created via the Node.js vm module.
Discovery
The source code before the fix was looking like this:
An attacker can eventually gain control of the `expr` variable, if the developer using the library passes the user input without sanitization.
This is stated in the README.md file of the repository, at line 181. (sic!)
As mentioned in approach, the
paths
array input is dynamically compiled into a function at initialization time. While thepaths
array is vigourously tested for any developer errors, it's strongly recommended against allowing user input to directly supply any paths to redact. It can't be guaranteed that allowing user input forpaths
couldn't feasibly expose an attack vector.
Exploitation
As we can see `expr` is being used two times for a string concatenation. This allows a trivial JavaScript code injection, but for a successful exploitation we need to:
- make the injection work: this is not trivial due to the fact that the same variable is being used with dot notation against a Proxy and as a part of a computed key. In order to be able to ship arbitrary payloads without caring about the syntax we need to access Function constructor.
- escape the sandbox: to escape the sandbox we need to be able to require some useful Node.js modules to trigger an arbitrary command execution. `require` itself is not available, so we need to work around that in order to spawn processes.
Proof of Concept
After around two hours of research I came up with some interesting payloads. Let’s see first how to solve 1. and 2. cited in the description.
- the problem here is that we need to provide an `expr` string that is statically valid and eventually partially valid at runtime in order to trigger arbitrary JavaScript code injection. This is not trivial due to the fact we need to provide at the same time a valid identifier for dot notation AND a computed key AND can trigger arbitrary code execution. If we think out of the box and we look at what the JavaScript grammar offers, we may notice that the we can use the `&&` operator to inject valid code. Now that we are aware of that, we can provide a valid payload that is not a valid identifier for the dot notation and use an IIFE to define and call a function.
- once we achieve arbitrary code execution, we need to escape the sandbox. As we can see the user provided code is being evaluated in a new context but there is no `“use strict”`directive, so `this` is available to the code running inside the sandbox. This will be our key to the Function constructor and we can trigger arbitrary code execution via `this.constructor.constructor(“console.log(‘OUCH’)”)`. Now that we have arbitrary code execution without limitations on the payload syntax, we can proceed to find a way to escape the sandbox context, that it doesn’t provide `require`. In order to achieve arbitrary command execution we will rely on the global `process` variable using the `binding` function to require internal modules in order to have a working `spawnSync()`. A detailed description of this technique is available here: How we exploited a remote code execution vulnerability in math.js.
After we fix everything, our final payload to spawn a reverse shell will look like this:
Interesting payloads
- Denial of Service:
while (1) 0
- Read
/etc/passwd
Timeline
- 2018/5/28 18:30 — Vulnerability discovered via code review
- 2018/5/28 20:30 — Reverse shell spawned
- 2018/5/28 23:00 — Vendor notified
- 2018/5/29 00:00 — Vendor confirmed vulnerability
- 2018/5/29 02:00 — First fix shipped on GitHub
Follow up
After reviewing the blog post, the vendor asked to include the following statement:
the author does not classify this as a security vulnerability due to clear documentation MUST be coming from a trusted party