docs: add MIME detection security assessment

This commit is contained in:
Username
2025-12-25 22:21:35 +01:00
parent 4a44d846c2
commit ff05f1b289

View File

@@ -0,0 +1,241 @@
# 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.