Files
flaskpaste/documentation/mime-security-assessment.md

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) or application/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

  • 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

# 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:

  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

./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.