ssti-methodology

Detect, identify, and exploit server-side template injection across Jinja2, Twig, FreeMarker, and other engines.

1.7k|238|Updated Dec 7, 2019
One-click install
npx skills add https://github.com/wgpsec/AboutSecurity --skill ssti-methodology
Or copy as Structured Prompt for Agent
Please help me install this Agent Skill.
Skill: ssti-methodology
Source: https://github.com/wgpsec/AboutSecurity/tree/main/skills/exploit/web-method/ssti-methodology
Command: npx skills add https://github.com/wgpsec/AboutSecurity --skill ssti-methodology

SYSTEM DOCUMENTATION & REQUIREMENTS

💡 This Skill includes references (resource) components.

What problem does it solve?

Server-side template injection (SSTI) testing requires knowing which template engine is running before any payload will work, and each engine (Jinja2, Twig, FreeMarker, Mako, Django, Pug) has completely different exploitation chains. This Skill provides a phased decision-tree workflow that takes a tester from injection-point discovery through engine fingerprinting to engine-specific exploitation, avoiding wasted attempts with incompatible payloads.

Core Features & Use Cases

  • Detection decision trees: Step-by-step probe sequences ({{7*7}}, ${7*7}, <%= 7*7 %>, #{7*7}) that distinguish Jinja2 from Twig, FreeMarker, ERB, Pug, Razor, and Smarty based on response differences.
  • Engine-specific exploitation references: Dedicated reference files covering Jinja2 context-variable reads, file reads, and RCE chains (lipsum/cycler globals), FreeMarker ?new() Execute chains, Twig filter-based RCE, and Django context-variable enumeration.
  • Filter bypass techniques: Documented workarounds for blacklists on underscores, dots, quotes, keywords, and numbers, including attr() filter chains, hex encoding, and request.args external parameter passing.
  • Use Case: During a web pentest, a /render?template= parameter reflects user input. Following the Skill, you send {{7*7}}, confirm Jinja2 via {{7*'7'}} returning 7777777, read the Jinja2 reference, and escalate from {{config}} to the lipsum RCE chain to read the flag file.

Quick Start

Ask the AI to test a reflecting parameter for SSTI by sending {{7*7}} and following the engine identification decision tree to select the right exploitation payload.

Frequently Asked Questions about ssti-methodology

High-intent search queries and answers about installing and using this skill.

FAQPage Schema
How do I detect SSTI in a web application?

Send probe payloads like {{7*7}}, ${7*7}, <%= 7*7 %>, and #{7*7} to parameters that reflect in the response. If {{7*7}} returns 49, the input is being rendered by a template engine; follow-up probes like {{7*'7'}} distinguish Jinja2 (returns 7777777) from Twig (returns 49).

How to identify which template engine is behind an SSTI vulnerability?

Use a decision tree based on probe responses: {{7*'7'}} returning 7777777 confirms Jinja2, ${.version} output confirms FreeMarker, #{7*7} returning 49 indicates Pug/Jade, and Server headers like Werkzeug or Tomcat provide supporting evidence.

What is the fastest Jinja2 SSTI payload for RCE?

The lipsum chain {{lipsum.__globals__['os'].popen('id').read()}} is the recommended first choice because lipsum is a built-in Jinja2 global available in most environments. Before RCE, always try simpler reads like {{config}} since flags often sit in template context.

Can Django templates be exploited for RCE through SSTI?

No, Django's template language does not support method calls or arbitrary code execution. Exploitation focuses on reading context variables like {{flag}}, {{settings.SECRET_KEY}}, and {{request.META}}, then using leaked credentials or debug pages to find other attack surfaces.

How to bypass keyword filters blocking __class__ or import in SSTI?

Pass blocked keywords through external parameters using request.args, for example ?a=__class__&b=__mro__ with {{config|attr(request.args.a)}}. Filters typically inspect only template content, not URL parameters, and hex encoding like \x5f\x5f also evades underscore blacklists.

Why do hardcoded subclass indexes fail in Jinja2 sandbox escapes?

Subclass indexes for os._wrap_close or subprocess.Popen vary across Python versions and environments, so hardcoded numbers break. Iterate __subclasses__() with a Jinja2 for loop and filter with a condition like 'popen' in str(cls) to locate the target dynamically.