All checks were successful
CI / Lint & Format (push) Successful in 21s
CI / Security Scan (push) Successful in 21s
CI / Memory Leak Check (push) Successful in 18s
CI / SBOM Generation (push) Successful in 18s
CI / Security Tests (push) Successful in 24s
CI / Unit Tests (push) Successful in 32s
291 lines
8.9 KiB
Markdown
291 lines
8.9 KiB
Markdown
# MIME Detection Security Assessment
|
|
|
|
Penetration testing of FlaskPaste's magic byte-based MIME detection system.
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
**Assessment Date:** 2025-12-25
|
|
**Target:** FlaskPaste MIME detection (`app/api/routes.py`)
|
|
**Methodology:** Polyglot file attacks, magic byte spoofing, header injection
|
|
**Result:** No critical vulnerabilities found. Defense-in-depth approach effective.
|
|
|
|
---
|
|
|
|
## Attack Surface
|
|
|
|
### MIME Detection Implementation
|
|
|
|
```python
|
|
# Location: app/api/routes.py
|
|
MAX_MAGIC_LEN = 16 # Maximum bytes checked for magic signatures
|
|
MAGIC_SIGNATURES = {
|
|
b"\x89PNG\r\n\x1a\n": "image/png",
|
|
b"\xff\xd8\xff": "image/jpeg",
|
|
b"GIF87a": "image/gif",
|
|
b"GIF89a": "image/gif",
|
|
# ... 42 total signatures
|
|
}
|
|
```
|
|
|
|
Detection checks first 16 bytes against known magic byte patterns.
|
|
|
|
---
|
|
|
|
## Testing Methodology
|
|
|
|
### Tools Used
|
|
- `fpaste` CLI client (with PoW solving)
|
|
- `curl` for raw HTTP requests
|
|
- `xxd` for binary file creation
|
|
- Python scripts for complex payloads
|
|
|
|
### Test Categories
|
|
1. Standard MIME detection validation
|
|
2. Polyglot file attacks
|
|
3. Magic byte boundary conditions
|
|
4. Header injection attempts
|
|
5. Security header verification
|
|
|
|
---
|
|
|
|
## Test Results
|
|
|
|
### Phase 1: Standard MIME Detection
|
|
|
|
| Format | Magic Bytes | Detected MIME | Status |
|
|
|--------|-------------|---------------|--------|
|
|
| PNG | `89 50 4E 47` | image/png | PASS |
|
|
| JPEG | `FF D8 FF E0` | image/jpeg | PASS |
|
|
| GIF | `47 49 46 38 39 61` | image/gif | PASS |
|
|
| PDF | `25 50 44 46` | application/pdf | PASS |
|
|
| ZIP | `50 4B 03 04` | application/zip | PASS |
|
|
| GZIP | `1F 8B 08` | application/gzip | PASS |
|
|
| SQLite | `53 51 4C 69 74 65` | application/x-sqlite3 | PASS |
|
|
| ELF | `7F 45 4C 46` | application/x-executable | PASS |
|
|
| PE/EXE | `4D 5A` | application/x-msdownload | PASS |
|
|
|
|
### Phase 2: Polyglot File Attacks
|
|
|
|
#### Test 1: PNG + HTML Payload
|
|
```bash
|
|
# Payload: PNG magic bytes + HTML after detection window
|
|
echo -e '\x89PNG\r\n\x1a\n\x00\x00\x00\x00<script>alert(1)</script>' > poly.bin
|
|
```
|
|
**Result:**
|
|
- Detected MIME: `image/png` (first magic wins)
|
|
- Served Content-Type: `image/png`
|
|
- **SAFE:** HTML payload ignored, served as image
|
|
|
|
#### Test 2: SVG with Embedded JavaScript
|
|
```xml
|
|
<?xml version="1.0"?>
|
|
<svg xmlns="http://www.w3.org/2000/svg">
|
|
<script>alert('XSS')</script>
|
|
</svg>
|
|
```
|
|
**Result:**
|
|
- Detected MIME: `text/plain` (no XML magic in signatures)
|
|
- Served Content-Type: `text/plain; charset=utf-8`
|
|
- **SAFE:** Not served as SVG, scripts won't execute
|
|
|
|
#### Test 3: GIF89a JavaScript Polyglot
|
|
```
|
|
GIF89a/**/=1;alert(1)//
|
|
```
|
|
**Result:**
|
|
- Detected MIME: `image/gif`
|
|
- Served Content-Type: `image/gif`
|
|
- **SAFE:** Even if loaded as `<script>`, CSP blocks execution
|
|
|
|
#### Test 4: JPEG + PHP Polyglot
|
|
```bash
|
|
echo -e '\xff\xd8\xff\xe0\x00\x10JFIF\x00<?php system($_GET["c"]); ?>' > poly.jpg
|
|
```
|
|
**Result:**
|
|
- Detected MIME: `image/jpeg`
|
|
- **SAFE:** No PHP execution on server, served as raw bytes
|
|
|
|
#### Test 5: PDF + ZIP Polyglot
|
|
```
|
|
%PDF-1.4
|
|
...
|
|
%%EOF
|
|
PK<ZIP_CONTENT>
|
|
```
|
|
**Result:**
|
|
- Detected MIME: `application/pdf` (PDF checked before ZIP)
|
|
- **SAFE:** First magic wins, predictable behavior
|
|
|
|
### Phase 3: Boundary Condition Tests
|
|
|
|
| Test | Payload | Result |
|
|
|------|---------|--------|
|
|
| 2-byte PNG prefix | `\x89P` | `application/octet-stream` (incomplete magic) |
|
|
| PNG + ELF embedded | PNG header + ELF binary | `image/png` (first magic wins) |
|
|
| Null byte prefix | `\x00\x00<html>...` | `text/plain` |
|
|
| Header injection | `GIF89a\r\nContent-Type: text/html` | `image/gif` (headers not parsed from content) |
|
|
|
|
### Phase 4: Security Headers
|
|
|
|
```
|
|
Content-Type: <detected-mime>
|
|
X-Content-Type-Options: nosniff
|
|
X-Frame-Options: DENY
|
|
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'
|
|
```
|
|
|
|
**Defense Analysis:**
|
|
|
|
| Header | Purpose | Effectiveness |
|
|
|--------|---------|---------------|
|
|
| `X-Content-Type-Options: nosniff` | Prevents MIME sniffing | Blocks browser interpretation override |
|
|
| `Content-Security-Policy: default-src 'none'` | Blocks all content execution | Scripts/styles/frames blocked |
|
|
| `X-Frame-Options: DENY` | Prevents framing | Clickjacking protection |
|
|
|
|
---
|
|
|
|
## Vulnerability Assessment
|
|
|
|
### Attempted Attacks
|
|
|
|
| Attack Vector | Technique | Result |
|
|
|---------------|-----------|--------|
|
|
| XSS via polyglot | GIF/PNG + JavaScript | BLOCKED by CSP |
|
|
| MIME confusion | SVG with script | Served as text/plain |
|
|
| Server-side execution | PHP in JPEG | Not executed (no PHP handler) |
|
|
| Header injection | CRLF in content | Not reflected in HTTP headers |
|
|
| Path traversal | `../` in content | No file path derived from content |
|
|
|
|
### Security Controls Summary
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ DEFENSE IN DEPTH LAYERS │
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
│ Layer 1: Magic byte detection (first 16 bytes only) │
|
|
│ Layer 2: X-Content-Type-Options: nosniff │
|
|
│ Layer 3: Content-Security-Policy: default-src 'none' │
|
|
│ Layer 4: No server-side content processing/execution │
|
|
│ Layer 5: Content stored as opaque blobs, never interpreted │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Commands Executed
|
|
|
|
### Test File Creation
|
|
```bash
|
|
# PNG with HTML payload
|
|
echo -e '\x89PNG\r\n\x1a\n<script>alert(1)</script>' > poly.bin
|
|
|
|
# SVG with script
|
|
cat > poly.svg << 'EOF'
|
|
<?xml version="1.0"?><svg><script>alert(1)</script></svg>
|
|
EOF
|
|
|
|
# GIF+JS polyglot
|
|
echo 'GIF89a/**/=1;alert(1)//' > poly.gif
|
|
|
|
# JPEG+PHP
|
|
echo -e '\xff\xd8\xff\xe0\x00\x10JFIF\x00<?php system($_GET["c"]); ?>' > poly.jpg
|
|
```
|
|
|
|
### Upload and Verify
|
|
```bash
|
|
# Upload with fpaste
|
|
id=$(./fpaste -s https://mymx.me/paste create -E -q poly.bin)
|
|
|
|
# Check detected MIME
|
|
curl -s "https://mymx.me/paste/$id?info" | jq .mime_type
|
|
|
|
# Verify served headers
|
|
curl -sI "https://mymx.me/paste/$id/raw" | grep -i content-type
|
|
```
|
|
|
|
---
|
|
|
|
## Recommendations
|
|
|
|
### Current Implementation: SECURE
|
|
|
|
The current implementation provides adequate protection through:
|
|
|
|
1. **Deterministic detection:** First matching magic bytes determine MIME type
|
|
2. **Safe defaults:** Unknown content served as `application/octet-stream` or `text/plain`
|
|
3. **Strong CSP:** `default-src 'none'` prevents script execution
|
|
4. **No sniffing:** `X-Content-Type-Options: nosniff` honored by browsers
|
|
5. **No processing:** Content stored and served as-is, never executed
|
|
|
|
### Potential Enhancements (Low Priority)
|
|
|
|
1. **Add SVG detection** with forced `text/plain` serving (prevents accidental SVG execution in older browsers)
|
|
2. **Content-Disposition: attachment** for all downloads (forces download instead of inline display)
|
|
3. **Sandbox iframe serving** for HTML-like content (additional isolation layer)
|
|
|
|
---
|
|
|
|
## Conclusion
|
|
|
|
The FlaskPaste MIME detection system is resistant to polyglot file attacks due to:
|
|
|
|
- Deterministic magic byte matching (first match wins)
|
|
- Strong security headers preventing browser-side exploitation
|
|
- No server-side content interpretation or execution
|
|
- Safe fallback to `text/plain` or `application/octet-stream`
|
|
|
|
**Risk Level: LOW**
|
|
|
|
The attack surface is minimal because detected MIME types only affect the `Content-Type` header served to clients, and browser-side exploitation is blocked by CSP and X-Content-Type-Options headers.
|
|
|
|
---
|
|
|
|
## Automated Fuzzing Results
|
|
|
|
### Fuzzer Execution
|
|
|
|
```bash
|
|
./venv/bin/python tests/fuzz/run_fuzz.py --verbose
|
|
```
|
|
|
|
### Summary
|
|
|
|
| Metric | Value |
|
|
|--------|-------|
|
|
| Total phases | 6 |
|
|
| Total requests | 192 |
|
|
| CRITICAL findings | 3 (expected behavior) |
|
|
| HIGH findings | 10 (false positives) |
|
|
| MEDIUM findings | 1 (intentional) |
|
|
|
|
### Finding Analysis
|
|
|
|
| Finding | Verdict | Reason |
|
|
|---------|---------|--------|
|
|
| X-SSL-Client-SHA1 bypass | EXPECTED | No TRUSTED_PROXY_SECRET configured in test |
|
|
| Header injection (10x) | FALSE POSITIVE | Werkzeug sanitizes; payloads not reflected |
|
|
| /metrics exposed | INTENTIONAL | Required for Prometheus monitoring |
|
|
|
|
### Verification
|
|
|
|
Header injection claims verified as false positives:
|
|
|
|
```bash
|
|
# Host header not reflected
|
|
curl -sI http://127.0.0.1:5099/ -H "Host: evil.com" | grep evil
|
|
# (no output)
|
|
|
|
# SQL payload not reflected
|
|
curl -s http://127.0.0.1:5099/ -H "X-Forwarded-For: ' OR 1=1--" | grep "OR 1=1"
|
|
# (no output)
|
|
|
|
# CRLF injection blocked by Werkzeug
|
|
curl -sI http://127.0.0.1:5099/ -H "Host: evil.com
|
|
X-Injected: true" | grep X-Injected
|
|
# (no output)
|
|
```
|
|
|
|
**Conclusion:** The fuzzer correctly identified expected behaviors but produced false positives for header injection attacks. Flask/Werkzeug properly sanitizes all header inputs.
|