PlatformPortSwigger Web Security Academy
TopicServer-side template injection
LabSSTI with information disclosure via user-supplied objects
DifficultyPractitioner
EngineDjango (Python)
GoalLeak the framework’s SECRET_KEY
ToolsBrowser, the engine’s own documentation

The idea#

Not every SSTI ends in remote code execution. Django’s template language is deliberately sandboxed, there’s no {{7*7}} arithmetic, no imports, no os.system. But a sandbox that blocks code doesn’t necessarily block data: if a sensitive object is reachable from the template context, you can still read it. This lab leaks Django’s SECRET_KEY by doing exactly that.

Reaching the template#

Getting to the product template editor is the same starting point as the documentation lab : log in as content-manager, open a product, and click Edit template. Rather than repeat those steps, I’ll pick up from the editor.

Product page with the Edit template button
Open a product and click Edit template.

Fingerprinting the engine#

The template interpolates objects with {{ ... }}, e.g. {{product.stock}} and {{product.name}}. Several engines use that syntax, so provoke an error with a burst of template metacharacters that are each special in a different engine (so one is sure to be invalid for whatever parser is running), ${{<%[%'"}}%\, and preview:

A metacharacter burst injected into the template
A deliberately invalid payload to break the parser.

The stack trace names the engine outright:

Internal Server Error with a Django traceback
The traceback points at django/template/base.py: a TemplateSyntaxError from Django (Python).

Reading the documentation#

Since Django’s templates are sandboxed against code execution, we pivot from RCE to information disclosure, and the engine’s own docs point the way. Search for the Django template documentation:

Searching for the Django template documentation
Let the docs do the work.

Django ships a built-in {% debug %} tag that outputs a load of debugging information, including the current template context and the imported modules:

Django docs describing debug mode
The docs describe Django’s debug output.

One catch worth knowing: Django’s error-page debug deliberately excludes sensitive settings, anything whose name contains KEY, SECRET, PASS, SIGNATURE, or TOKEN, so SECRET_KEY will never show up in a raw stack trace:

Django settings docs noting SECRET_KEY is excluded from debug output
SECRET_KEY is filtered out of the fancy error page, so we need another route to it.

Dumping the context#

That filtering only applies to the error page. The {% debug %} tag rendered inside the template is not filtered the same way, so inject it:

{% debug %}
Injecting the debug tag into the template
Inject {% debug %} and save.

The rendered output dumps the whole template context, and alongside product there’s a settings object in scope:

Rendered debug output showing a settings object in the context
The context exposes a settings object next to product.
Full debug context dump with imported modules
The full dump: context plus every imported module.

Leaking the secret key#

settings is reachable, so just read the attribute directly, no code execution needed:

{{settings.SECRET_KEY}}

Render it and Django prints the signing key straight into the page. Submit that value to solve the lab.

The rendered template leaking Django's SECRET_KEY
settings.SECRET_KEY rendered in the output: the secret is leaked.

Real-world impact#

This lab leaks a value rather than running code, but a disclosed Django SECRET_KEY is serious. The key signs and verifies everything Django trusts. With it, in a real, authorized engagement, an attacker can:

  • Forge session cookies — mint a valid signed session for any user, including staff/admin, for a full account takeover with no password.
  • Forge other signed data — password-reset tokens, signed URLs, cookies, and any signing / dumps() payloads the app relies on.
  • Pivot to deeper compromise — admin or privileged access often exposes further functionality (file uploads, template editing, integrations) that can be chained toward code execution on the server.

So an “information disclosure” finding here is really the key to impersonation and, frequently, full application compromise, which is why a leaked SECRET_KEY must be rotated immediately. Only ever against systems you are authorized to test.

Remediation#

  • Don’t expose sensitive objects to templates. Pass only the specific, non-sensitive values a template needs, never whole config/settings objects.
  • Keep user input out of template source. Render fixed templates and bind user data as context values.
  • Rotate a leaked SECRET_KEY immediately, it signs sessions, password-reset tokens, and more; disclosure can lead to session forgery.

A concrete, generic example, putting only the needed values in the context rather than whole objects:

# Unsafe: the whole settings object is reachable from the template
render(request, "product.html", {"settings": settings, "product": p})

# Safe: expose only the specific, non-sensitive values the template needs
render(request, "product.html", {"product_name": p.name, "stock": p.stock})

Key takeaways#

  • SSTI is not only RCE, in a sandboxed engine it can still be a powerful information-disclosure primitive.
  • {% debug %} is the fastest way to see what’s actually reachable in a Django template’s context.
  • A reachable settings object turns one line, {{settings.SECRET_KEY}}, into a full secret-key leak.