SSTI – Server-side template injection with information disclosure via user-supplied objects

This is the write up for the PortSwigger Web Security Academy 5th lab in the series for Server-Side Template Injection (SSTI). This one is definitely a step up in complexity. This goes beyond identifying the location for the injection and then fuzzing with simple payloads. I feel like this is the first lab in the series that gets us closer to a true real world example. This one not only requires the basics, but then the patience to do some Googling and to read some documentation.

Let’s get rolling on the next lab from the PortSwigger Web Academy labs on the topic.

  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.

Lab: Server-side template injection with information disclosure via user-supplied objects

Lab: https://portswigger.net/web-security/server-side-template-injection/exploiting/lab-server-side-template-injection-with-information-disclosure-via-user-supplied-objects

For this one, let’s read the instructions.

  • There are non-standard credentials
  • We need to get the framework’s secret key

Not the standard solution – this means we’re going to be diving deeper into the specific framework rather than utilizing standard payloads to attempt to achieve RCE (remote code execution).

Step 1: Recon

Let’s find where we need to exploit. In this lab, we have instructions with a set of creds.

content-manager:C0nt3ntM4n4g3r

In this case, we’re probably dealing with a situation where we need to authenticate in order to manipulate the template. For this lab, we’re dealing with the shop interface:

screenshot of the web security academy shop interface for the lab Server-side template injection with information disclosure via user-supplied objects

Looking at an item it definitely looks like a template is in play:

screenshot of a product from the shop

Let’s log in and check out the item page:

screenshot of edit template button

And there it is – let’s edit the template and see what we see:

Step 2: Verify Template Injection

screenshot of the template and the edit box

Here, we see that we have the {{ }} syntax in play. For simplicity, let’s get rid of the rest of the template and just keep the part that has the template variables. Get rid of the noise:

screenshot of simplified template with only the parameterized string left

Now, let’s try our trusty {{7*7}} and see what we get:

screenshot of error message showing we are dealing with Django

Nope – we get an error. That’s OK, though, as we see here that Django is in play. Let’s check PayLoadsAllTheThings to see if there is a payload we can try for Django.

PayloadsAllTheThings:Django: https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Server%20Side%20Template%20Injection/Python.md#django—basic-injection

After pasting each of the payloads in, the only payload that yields any results is the {% debug %}. It gives us a TON of information:

screenshot of template with {% debug %}

There’s a lot here. It lists all of the various modules and the such that are available. It’s a LOT to sift through. It confirms we’re dealing with Django, however, with so much on the page it can be difficult to decipher. Let’s figure out a way we can utilize the tooling in either Burp or Caido to help us avoid potentially having to read through all of this debug info.

Step 3: Explore

Let’s generate a word list based on the page and then fuzz. At this point, I saved the template with the {% debug %} payload in the template.

screenshot of kali terminal running cewl

Note – I run kalilinux/kali-rolling in a Docker container so that I don’t always have to have a full Kali VM running. It’s slick for having the tooling available via terminal whenever I need. I do generate the image with a dockerfile so that I can add additional tools in like ‘cewl’.

Tool – cewl: https://www.kali.org/tools/cewl/

I can now run cewl against the product page, and it cranks out a list of 308 different words I can use in a wordlist to see if I can get something to flag. Let’s use automate in Caido to see if one of these will respond and give me something other than a 500. Which request should we send, though?

What’s really nice here is the ‘Preview’ functionality. With a ‘Save’, you get a 302. To look at whether or not we have any interesting results, we would have to follow the redirect and analyze the resulting page. With the ‘Preview’ functionality, we get a 500 response directly.

screenshot of payload with {% badstring %} as a placeholder

Gives us:

screenshot of error message from the bad payload

And that looks like this in the HTTP History within Caido:

screenshot of 500 error in Caido because of bad payload

Nifty! Makes testing a lot easier since we don’t have to follow redirects. All we have to do is right mouse click, send this request to automate and then push through the list generated from cewl.

screenshot of Caido Automate - putting a placeholder on the debug word within the payload and pasting in the cewl wordlist

Let’s run it through and see what we get. Unfortunately, this just cranks out a pile of 500s with the exception of debug (which we already knew) and lorem.

screenshot of automate results with debug (known) and lorem as 200 responses

Django Lorem Template Tag: https://www.w3schools.com/django/ref_tags_lorem.php

I don’t think the lorem tag is our target. This seems fairly benign without a lot of additional functionality. There probably isn’t a way to retrieve a “secret key” with this tag. Let’s keep it in the back of our mind, and circle back if needed.

Remember back to the original template? While the debug statement is using {% %}, the original placeholders in the template were using {{ }}. Let’s give that a double braces a shot:

screenshot of Caido Automate results with product and settings at the top of the list due to their differing response lengths

For this run, everything comes back with a 200. The majority of them have basically the same length varying from 4034 to 4036 depending on the size of the payload string. This makes sense since the payload string is reflected in a <textarea> and then potentially in the “preview-result” <div>. Product and Settings, however, don’t match this pattern.

Settings is where I want to go first, but let’s start with product in order to rule it out. Product looks to return a product object (cool) as we can see in the request above.

screenshot of {{ product }} in the template string and then an encoded string with all of the product fields reflected

It’s possible we could pivot off the product object in a way such that we can get to an ancestor object type, utilize those capabilities to RCE or some other way to talk to the template engine, but let’s see if the settings object gives us what we need. After all, the ‘secret key’ associated with the framework is likely a setting of some type. Here’s what we get from the {{ settings }} payload:

screenshot of UserSettingsHolder reflected from {{ settings }}

That’s really interesting. What is UserSettingsHolder? Doing a little Googling and UserSettingsHolder in Django helps manage app settings by checking for custom values first and falling back to default Django settings. It’s useful for testing different configurations without changing the main settings. OK, but what does that mean to us?

What we do know is that {{ product }} has fields within it – stock, name, and price (from the original template) and we can see those exact fields when we just call {{ product }}. Are there other fields? Maybe.

Let’s look for documentation on Django and settings. A quick Google search for “django settings object” and it returns Django specific documentation for the settings object:

Django docs: https://docs.djangoproject.com/en/5.1/ref/settings/

Reading through here and we see that there is a DEBUG attribute on the settings object. If we can access that (should be either T or F) then we know we’re probably dealing with the actual settings object for the template engine. Let’s give that a go:

screenshot of using settings.DEBUG within the payload and getting a True response

Very promising. Assuming we are talking to the template engine, that means the DEBUG flag is set to ‘True’. Turns out that by having DEBUG=true within the settings is a security risk as it can expose sensitive information – including an attribute on the settings object named SECRET_KEY.

Settings DEBUG: https://docs.djangoproject.com/en/5.1/ref/settings/#debug

Exactly what is the Django secret key?

Secret_Key: https://docs.djangoproject.com/en/5.1/ref/settings/#std-setting-SECRET_KEY

It’s the crypto key used for security related ops within Django. It’s definitely supposed to be secret and unique for each project. Per the instructions for the lab, this is the literal field we are supposed to steal.

Step 4: Solve the Lab

SECRET_KEY is an attribute on the settings object. A key here is that this attribute is only available if DEBUG=’True’. Since we saw DEBUG was set to true earlier (happy coincidence), let’s try and access the SECRET_KEY attribute:

screenshot of using settings.SECRET_KEY in the payload and getting a response

Grab the key and solve the lab!

screenshot of the lab showing solved

Summary

This lab demonstrated how deep recon combined with creative exploitation techniques can reveal sensitive information from a seemingly benign template injection vulnerability. Key takeaways include:

  • Fuzzing with a custom wordlist: Generating and automating tests with tailored wordlists can pinpoint non-obvious injection points.
  • Reading the docs: Consulting official documentation is critical to understand underlying framework objects and their attributes.
  • Leveraging debug output: Utilizing debug payloads can expose internal objects and settings, leading to the discovery of sensitive data like the SECRET_KEY.

Happy Hacking!

Leave a Reply