Server-Side Template Injection (SSTI): Understanding and Mitigating the Risk

Server-Side Template Injection (SSTI): Understanding and Mitigating the Risk

Template engines have become a cornerstone in modern web development, offering a clean separation between the user interface (frontend) and the application logic (backend). This modular approach significantly improves maintainability and design flexibility. However, like any powerful technology, template engines introduce a set of security risks if not handled with care.


What Are Template Engines?

Template engines enable developers to use static template files in their applications that operate independently of the backend layer. These files, often resembling standard HTML with placeholders, define the structure and layout of the web page.

At runtime, the template engine takes the static template, replaces the placeholders with dynamic data provided by the application (usually from the backend layer), and generates the final HTML file to present to the client’s web browser. This mechanism is crucial for designing modern, dynamic HTML sites.

A Practical Example

Consider a scenario using the pug template engine:

1. Template Definition (e.g., in a file named index.pug):

Pug
app.set('view engine', 'pug')
html
  head
    title= title
  body
    h1= message

2. Backend Code (Providing Data):

JavaScript
app.get('/', function (req, res) {
  res.render('index', { title: 'Hey', message: 'Hello there!' })
})

When a user accesses the application, the template engine will execute the res.render() function, parsing the information provided (the title and message). It then translates this file and creates the final HTML code to display to the user, such as:

HTML
html>
  head>
    title>Heytitle>
  head>
  body>
    h1>Hello there!h1>
  body>
html>

The Problem: Server-Side Template Injection (SSTI)

The efficiency of template engines relies on their ability to interpret and execute content. The security risk arises when the engine reads and processes data that is validated incorrectly or is entirely user-controlled.

Server-Side Template Injection (SSTI) is a vulnerability where an attacker can inject malicious native template syntax into a variable that is then processed by the template engine on the server.

Potential Impact

The potential impact of an SSTI vulnerability is severe and can escalate significantly:


Understanding SSTI Through Examples

While all template engines serve the same purpose, the specific syntax and exploitation vectors vary. Let’s examine different examples using various popular template engines to understand how SSTI works in practice.

Twig and FreeMarker (Python/Java)

Twig (often used with PHP or Python frameworks like Symfony or Flask) and FreeMarker (a Java template engine) operate by processing dynamic data.

Secure Scenario:

In the following line, the template is securely waiting for a variable named first_name, which is then displayed. There is no problem here because the input is strictly controlled.

PHP
$output = $twig->render("Dear {first_name},", array("first_name" => $user.first_name) );

Vulnerable Scenario:

In this subsequent line, the template engine is waiting for input in order to personalize the content, but the input is sourced directly from an unvalidated user parameter ($_GET[‘custom_email’]).

PHP
$output = $twig->render($_GET['custom_email'], array("first_name" => $user.first_name) );

As shown in the preceding code snippet, the template is vulnerable because it’s open to receive any kind of data entered by the user. A user can inject a formatted email, or, more dangerously, a chunk of code that modifies the display or executes commands.

Exploiting the Template

This vulnerability is considered high-impact because it goes beyond simple XSS. While a malicious user might commonly exploit it with XSS and try to attack another user using social engineering (psychological manipulation of people into performing actions or divulging confidential information), an experienced tester or attacker will aim to gain more information about the application itself in runtime (while the program is running).

Template Syntax Execution:

By injecting native template syntax, an attacker can test if the engine will interpret the content as code:

Payload: custom_email={{7*7}}

If the application is vulnerable, the output will be 49. As observed, if an arithmetic operation is entered using the template syntax, it is solved by the engine.

Object Introspection:

The next step is often to make a reference to a server-side object to gain information about the application’s configuration, environment, or classes.

Payload: custom_email={{self}}

Output: Object of class __TwigTemplate_7ae62e582f8a35e5ea6cc639800ecf15b96c0d6f78db3538221c1145580ca4a5 could not be converted to string

The response, while an error, confirms that the self object was processed by the template engine, validating the vulnerability and providing a key piece of information—the internal class name—that can be used for further exploitation attempts toward RCE.

Smarty (PHP)

Smarty is a well-known template engine developed in PHP. It includes tags that allow for the direct execution of PHP code within the template.

Example of Vulnerable Code:

PHP
{php}echo 'id';{/php}

This seemingly inoffensive line could result in an RCE attack. The reason is that the content between the {php} tags is passed directly to the PHP interpreter. If an attacker passes a PHP web shell (a script used for remote administration that can execute operating system commands) as a parameter, it will be executed by the template engine, granting the attacker control over the server.

Marko (JavaScript)

Marko is another template engine with a syntax very similar to HTML and JavaScript. It provides tags for script execution, which can be highly dangerous if the input is not validated.

Example of Vulnerable Code:

JavaScript

import os
x=os.popen('id').read()
%>
${x}

This code also puts the application at RCE risk. The template is designed to receive any parameter without validation, execute the arbitrary code (in this case, importing the os module and running the id command), and displays the result directly to the user, confirming the execution of the command on the server.


Detecting and Identifying Server-Side Template Injection (SSTI)

Detecting a Server-Side Template Injection (SSTI) vulnerability is the critical first step toward remediation or exploitation. The vulnerability can manifest in different contexts within a web application, and once found, the next objective is to identify the specific template engine in use to tailor the attack.


Contexts of SSTI Appearance

SSTIs can appear in two primary contexts within a web application: Plaintext Context and Code Context.

1. Plaintext Context

In a Plaintext context, the user’s input is directly rendered within the HTML without being interpreted as template code. This is common in features like text editors, profile descriptions, or simple welcome messages where the application expects basic text but fails to properly escape the template syntax.

The injection occurs where the application allows users to input data that is later wrapped in template syntax on the server. The attacker’s goal is to introduce template syntax that the engine will interpret.

Template EngineExample Input (What the user sees)Example Output
SmartyHello {user.name}Hello user1
FreeMarkerHello ${username}Hello newuser
GenericHelloHello

2. Code Context

In a Code context, the user’s input is expected to be part of the actual template code or is passed as an argument that the template engine attempts to process or evaluate.

Example InputExample Output (Varies)
personal_greeting=usernameHello user01 or Hello
personal_greeting=username}}Hello user01

In this context, the input is already being processed by the application’s logic. If the input is evaluated (processed) as a template expression, the user can inject template commands. This type of evaluation often results in XSS (Cross-Site Scripting) attacks, as the evaluated input can include functions like alert() which will be shown on the client’s browser.


SSTI Detection and Engine Identification

Once a potential SSTI vulnerability is detected by providing an invalid input (e.g., template syntax that throws an error or executes an operation) and getting a resulting change or error, it is important to determine which template engine is used.

Why is this essential? Because even though all template engines work in similar ways, they have important differences in syntax, available objects, and functions. An exploit designed for Twig will not work directly on Smarty or Jinja2. We need to keep these differences in mind while exploiting the vulnerability.

Determining the Template Engine

By observing the application’s response to specific, engine-specific input, it is possible to determine the template engine used. Attackers often use a template engine identification diagram or a decision tree based on the template’s syntax for arithmetic operations, string concatenation, or object access.

Based on a sequence of inputs and the resulting output, one can determine which engine is used by the application in some cases. For example:

This systematic process of testing specific syntax is known as fuzzing and helps identify the underlying technology before attempting to craft a successful Remote Code Execution (RCE) payload.


Exploitation Concept: Achieving Remote Code Execution (RCE) via SSTI

The potential for exploitation in a template engine stems from its design to evaluate and execute code. If an application fails to properly separate user-supplied data from the application’s code, an attacker can inject template syntax that forces the engine to execute unintended commands.

1. Proof of Concept (PoC) Detection

The first step in an attack is always to confirm the vulnerability exists. This is often done using a simple arithmetic operation, which tests the engine’s ability to evaluate expressions:

Confirmation: When the output is the evaluated result (e.g., 2) instead of the literal input (${{1+1}}), the vulnerability is confirmed. The application is treating user input as template code.

2. Escalation to Remote Code Execution (RCE)

Once the vulnerability is confirmed, an attacker attempts to escalate the simple expression evaluation to Remote Code Execution (RCE). This involves finding template functions or objects that allow access to the underlying operating system commands or application environment.

An example of a conceptual payload targeting a specific, vulnerable template configuration might look for a way to call a system function:

Conceptual RCE Payload:

{{_self.env.registerUndefinedFilterCallback(“exec”)}}{{_self.env.getFilter(“id”)}}

If successful, this highly specialized payload would manipulate the template engine’s environment to execute the operating system command id, which identifies the user account running the application:

Conceptual Successful Output: uid=1000(k) gid=1000(k) groups=1000(k),10(wheel)

Interpretation: This output confirms that the attacker can execute arbitrary commands on the server, resulting in a full RCE vulnerability.


Defensive Strategies and Mitigation

The best defense against SSTI is never allowing user-supplied input to be processed as part of the template code itself. Developers should focus on the following core principles:

1. Input Sanitization and Validation

2. Principle of Least Privilege (Sandboxing)

3. Static Code Analysis

Security Testing: Utilize Static Application Security Testing (SAST) tools to scan the application’s source code for dangerous patterns where user input is passed directly to template rendering functions without proper sanitization or escaping.

Template Injection Vulnerabilities: Mitigation and Real-World Examples

While Server-Side Template Injection (SSTI) can be complex to report due to its frequent classification as another, similar vulnerability (like Remote Code Execution (RCE)), understanding its specific mitigation is crucial. These flaws are extensive, affecting a large number of applications and exposing not just the application, but also the web server and the internal network.


Mitigation Strategies for SSTI

Effective mitigation for SSTI focuses on robust input handling and limiting the template engine’s power. When writing recommendations for a security report, the following points are important to keep in mind:

  1. Validate Strings Loaded: Treat any user-supplied string loaded by the template engine with the same caution as if you were using an eval() function (a function that executes a string of code). Implement strict input validation, only allowing expected characters and formats.
  2. Implement Protections for Local File Inclusions (LFIs): Attacks exploiting SSTI often mimic a require function (a function that loads and executes a file or module) to pull sensitive files from the server’s filesystem. Mitigation involves strict access control and validation on any file path or resource name passed to the template engine or view renderer.
  3. Do Not Pass Dynamic Data Directly to a Template: The fundamental rule is to avoid passing unvalidated, dynamic data directly to a template engine function that is intended to parse or render template logic. Instead, use the engine’s built-in functionality for data presentation, which often includes auto-escaping features.
  4. Enable Context-Aware Auto-Escaping: This is the most important defense. Configure the template engine to automatically escape all user-supplied data. This ensures that template syntax (e.g., {{, $ symbols) is treated as a literal string for display, rather than code to be executed.

Reported Vulnerabilities

Reviewing real-world cases of SSTI vulnerabilities helps illustrate their impact and how they are detected across different template engines.

Uber Jinja2 SSTI

In a significant report on April 6, 2016, security researcher Orange Tsai published an SSTI vulnerability affecting the Uber application, which used the Flask Jinja2 template engine.

Detection Method:

Orange Tsai tested the vulnerability by entering a simple arithmetic expression into the Name field on the profile section of rider.uber.com:

Payload: {{ '7'*7 }}

When the change was accepted, the application sent an email to the user. In the email’s body, the evaluated result appeared: 7777777.

The user’s displayed name in the Uber application also changed to the result, confirming that the input was being evaluated as template code.

Escalation:

With the vulnerability confirmed, Orange Tsai used advanced Python code designed for Jinja2 exploitation to gain information about the running instance:

Payload Snippets (Conceptual):

Python
{{ '7'*7 }}
{{ [].class.base.subclasses() }} # Attempt to access all available Python classes
{{''.class.mro()[1].subclasses()}}
{%for c in [1,2,3] %}{{c,c,c}}{% endfor %}

Result: He could successfully extract all information about the currently running instance, demonstrating the potential for RCE.

Detection Tip: To detect this kind of vulnerability, enter values that are easily evaluated and show the result in a simple way, such as using integers in a multiplication or simple addition.

Uber Angular Template Injection

Shortly before the Jinja2 report, on April 4, 2016, security researcher James Kettle reported an SSTI vulnerability in the Angular template used by Uber on developer.uber.com. This case involved exploiting the vulnerability via an XSS (Cross-Site Scripting) attack.

Detection Method:

James Kettle found the vulnerability in the developer.uber.com/docs/deep-linking URL. He sent a GET request with a payload in the q query variable:

URL with Payload: https://developer.uber.com/docs/deep-linking?q=wrtz{{7*7}}

Result: The page displayed the string wrtz49. The 49 confirmed the template engine was executing the arithmetic operation.

Exploitation (XSS):

To exploit the confirmed vulnerability, James Kettle injected a payload that executed a JavaScript alert() function:

URL with XSS Payload: https://developer.uber.com/docs/deep-linking?q=wrtz{{(_="".sub).call.call({}[$="constructor"].getOwnPropertyDescriptor(_.__proto__,$).value,0,"alert(1)")()}}zzzz

Result: The application showed a pop-up with the JavaScript function alert(1) being executed, confirming an XSS vulnerability derived from template injection.

Yahoo SSTI Vulnerability

On July 8, 2018, a bug bounty hunter named Jedna Linijka published an SSTI vulnerability affecting Yahoo. The vulnerability was found in a forgotten URL, http://datax.yahoo.com/swagger-ui.html, which initially returned a 403 error code.

Detection Method:

After reading the API documentation for the “DataX” service, the researcher discovered an entry point that reflected the user-entered value in an error page. He tested it with a standard template expression:

Testing String: ${7*7}

Result: The resulting page showed the calculated value, 49, confirming the template engine (likely FreeMarker or a similar expression language) was vulnerable.

Exploitation:

After confirmation, the researcher entered a payload designed to access the underlying system environment:

Payload: ${T(java.lang.System).getenv()}

Result: The application displayed information directly off of the system, likely including environment variables, which could contain sensitive data like database credentials or API keys.

Exploitation Tip: When an SSTI vulnerability is detected, read the documentation for the identified technology (e.g., API documentation, framework documentation) to understand how to pass different values and consume entry points, which guides payload crafting.

Rails Dynamic Render (CVE-2016-0752)

On February 12, 2016, security researcher John Poulin published a vulnerability affecting all versions of Ruby on Rails (RoR) up to that date.

The Vulnerability:

RoR uses the Action View component to render views. John Poulin discovered that the component did not validate the input used to select the view template.

A vulnerable controller function looked like this:

Ruby
def index
  render params[:id]
end

This function was intended to load a view file like app/views/user/dashboard.{ext} by taking a simple template name from params[:id].

However, an attacker could use directory traversal syntax to access files outside the intended views directory:

Vulnerable Input: ../admin/dashboard

The Impact (LFI):

The application would attempt to render the missing file, searching through different paths, including the RAILS_ROOT path and eventually the filesystem root. By exploiting this Local File Inclusion (LFI) issue, it was possible to extract sensitive files from the server, such as /etc/passwd.

Proposed Mitigation (White-listing):

The mitigation focused on validating the paths from where Action View loads files by using a strict allow-list (whitelist).

Mitigation Example 1 (Explicit Allow-list):

Ruby
def show
  template = params[:id]
  valid_templates = {
    "dashboard" => "dashboard",
    "profile" => "profile",
    "deals" => "deals"
  }
  if valid_templates.include?(template)
    render " #{valid_templates[template]}"
  else
    # throw exception or 404
  end
end

This code strictly validates that template can only be one of the pre-defined keys.

Mitigation Example 2 (Root Folder Limit):

Ruby
def show
  template = params[:id]
  d = Dir["myfolder/*.erb"]
  if d.include?("myfolder/#{template}.erb")
    render "myfolder/#{template}"
  else
    # throw exception or 404
  end
end

This code limits the file search to a specific myfolder directory and throws a 404 error code if the file is not found there.

This example is interesting because it affected all applications developed with the vulnerable RoR versions, highlighting the risk of unchecked user input in rendering functions.


Summary of SSTI Flaws

SSTI is a critical bug with extensive consequences.

To look for SSTI vulnerabilities, enter values to be evaluated (e.g., arithmetic expressions) and, if a result is returned, try harder to escalate the vulnerability.

The impact could be an RCE attack, not only on the affected server but potentially on others on the same network.

An SSTI found in an application exposes the application, the web server, and the entire network.

Exit mobile version