Manual Testing for Server-Side Template Injection (SSTI)
Server-Side Template Injection (SSTI) is a critical vulnerability that allows an attacker to inject malicious template code into a web application, potentially leading to remote code execution, data exfiltration, or unauthorized access. This post is a technical deep dive into manually testing for SSTI during web application penetration tests. We’ll explore the underlying concepts, examine common payloads, and work through practical examples using different templating engines.
1. Understanding SSTI
What is a Template Engine?
Modern web applications often use template engines to render dynamic content. Engines such as Jinja2 (Python), Twig (PHP), and others allow developers to embed logic in HTML. SSTI arises when user input is unsafely passed to these template engines, enabling the execution of unintended template code.
Why is SSTI Dangerous?
An SSTI vulnerability can expose sensitive internal functions and objects. For instance, in Python’s Jinja2, an attacker might traverse the class hierarchy to access functions that interact with the operating system, potentially leading to full system compromise.
2. Manual Testing Methodology
Step 1: Identify Potential Injection Points
Start by analyzing the application for inputs that might be processed by a templating engine. Common candidates include:
- URL parameters
- POST data
- HTTP headers (e.g.,
User-Agent
,Referer
) - Cookies
Step 2: Insert Basic Payloads
Insert simple payloads that trigger expression evaluation. A classic example is:
{{7*7}}
If the application is vulnerable, the rendered page might display “49” where you inserted the payload.
Step 3: Analyze the Output
If the payload is reflected in the response and computed (e.g., “49” appears), you likely have an SSTI vulnerability. Note if the payload appears unaltered or escapes HTML characters, as these behaviors can indicate different levels of risk or filtering.
Step 4: Escalate with More Advanced Payloads
Once a basic injection is confirmed, test with payloads that attempt to access server internals. For example, with Jinja2 you might try:
{{''.__class__.__mro__[2].__subclasses__()}}
This payload accesses the Python object hierarchy to list classes, which can help you identify exploitable objects such as file handlers or OS command executors.
3. Practical Examples
Example 1: Testing with cURL
Assume you have a vulnerable endpoint http://example.com/search
that directly renders user input within a template. Use cURL to test:
curl -G "http://example.com/search" --data-urlencode "query={{7*7}}"
Expected Output:
If vulnerable, the rendered page should display “49” in the location where the query
parameter is reflected.
Example 2: Manual Testing in Burp Suite
Intercept a Request:
Capture a request where the input might be rendered in the HTML response.Modify the Request:
Replace the input value with a payload such as{{7*7}}
and forward the request.Analyze the Response:
Look for computed output. If “49” is visible, you have a confirmed SSTI injection point.
Example 3: Advanced Payload Exploration in Jinja2
After confirming SSTI, you may attempt to enumerate sensitive classes to facilitate further exploitation. Use the following payload:
{{ ''.__class__.__mro__[2].__subclasses__() }}
Interpretation:
''.__class__
: Retrieves the class of an empty string (i.e.,<class 'str'>
)..__mro__
: Returns the Method Resolution Order of the class, which is a tuple of classes.[2]
: Selects a base class that might give access to system-level classes..__subclasses__()
: Lists all subclasses of that base class, exposing classes like file handlers, exception classes, etc.
Note: The output can be extensive. In practical testing, use filters to search for classes that enable file I/O or command execution. For example, in Python, you might search for the <class 'os._wrap_close'>
or similar entries that can lead to further exploitation.
Example 4: Evaluating Payload Impact with Python
Sometimes, it’s helpful to simulate payloads in a controlled environment. Below is a Python snippet to mimic a simplified template engine scenario:
from jinja2 import Template
# Simulated vulnerable template rendering
def render_user_input(user_input):
template = Template("User input: " + user_input)
return template.render()
# Test with a basic payload
payload = "{{7*7}}"
result = render_user_input(payload)
print("Rendered Output:", result)
Expected Outcome:
If the application is vulnerable, the output should be:
Rendered Output: User input: 49
This controlled test helps you understand how the templating engine interprets the payload.
4. Deep Dive: Techniques for Further Exploitation
Enumerating the Class Hierarchy
After confirming SSTI, the next step is often to navigate through the template engine’s internal objects. This can involve:
- Listing Subclasses: As shown in the advanced payload, obtaining the list of subclasses can expose critical classes.
- Filtering Results: You might not need the full list, so using filters (either in your testing tool or via string manipulation) can help focus on potentially dangerous objects.
Bypassing Input Sanitization
Some applications may sanitize or encode output. Techniques include:
- Double Encoding: Inserting URL or HTML encoded payloads to bypass filters.
- Template Syntax Variations: Some engines allow alternative syntax (e.g.,
[% %]
instead of{{ }}
) or use different delimiters that might not be sanitized in the same way.
Combining Payloads
Once you have a basic understanding of the available objects, combine payloads to execute system commands. For example, in Jinja2, if you can access the os
module indirectly, you might execute:
{{ config.__class__.__init__.__globals__['os'].popen('id').read() }}
This payload:
- Navigates to the
config
object’s class, - Accesses its global variables,
- Retrieves the
os
module, - Executes the command
id
(a simple command to display the current user identity).
Disclaimer: Always ensure that testing like this is conducted on systems you have explicit permission to test.
5. Mitigation and Best Practices
While the focus here is on identifying and exploiting SSTI vulnerabilities, it’s equally important to understand how to prevent them:
- Input Validation: Never pass user-controlled input directly to a template engine without proper sanitization.
- Use Safe Rendering Methods: Many templating engines offer safe rendering functions that escape input.
- Keep Templating Engines Updated: Regularly update the libraries to incorporate security patches.
- Implement a Security Review: Regular code reviews and automated scanning tools can help detect potential SSTI vulnerabilities early.
6. Conclusion
Server-Side Template Injection remains a potent vulnerability in web applications that rely on dynamic content rendering. Through careful manual testing, penetration testers can uncover these vulnerabilities using basic payloads, advanced payload exploration, and controlled environment testing. By understanding the mechanics behind SSTI and employing rigorous testing methodologies, security professionals can better protect applications from exploitation.
This guide has walked through the manual testing process, provided practical examples with cURL, Burp Suite, and Python, and highlighted both the risks and remediation strategies. As with all security testing, ensure you have proper authorization and adhere to legal and ethical guidelines during your assessments.
Happy testing, and stay secure!