9.2 KiB
MIME Detection Security Assessment
Note (v1.5.1): Magic byte detection has been simplified to UTF-8 validation only. Content is now classified as
text/plain(valid UTF-8) orapplication/octet-stream(binary). Security headers (nosniff, CSP) provide the primary defense against MIME confusion attacks. This document is retained for historical reference.
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
# 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
fpasteCLI client (with PoW solving)curlfor raw HTTP requestsxxdfor binary file creation- Python scripts for complex payloads
Test Categories
- Standard MIME detection validation
- Polyglot file attacks
- Magic byte boundary conditions
- Header injection attempts
- 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 |
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
# 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 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
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
# 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
# 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:
- Deterministic detection: First matching magic bytes determine MIME type
- Safe defaults: Unknown content served as
application/octet-streamortext/plain - Strong CSP:
default-src 'none'prevents script execution - No sniffing:
X-Content-Type-Options: nosniffhonored by browsers - No processing: Content stored and served as-is, never executed
Potential Enhancements (Low Priority)
- Add SVG detection with forced
text/plainserving (prevents accidental SVG execution in older browsers) - Content-Disposition: attachment for all downloads (forces download instead of inline display)
- 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/plainorapplication/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
./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:
# 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.