forked from username/flaskpaste
fix lint errors (unused vars, line length, formatting)
This commit is contained in:
@@ -104,9 +104,7 @@ class Config:
|
||||
# Maximum unique IPs tracked in rate limit storage (RATE-001: memory DoS protection)
|
||||
RATE_LIMIT_MAX_ENTRIES = int(os.environ.get("FLASKPASTE_RATE_MAX_ENTRIES", "10000"))
|
||||
# RATE-002: Cleanup threshold (0.0-1.0) - trigger cleanup when entries exceed this ratio
|
||||
RATE_LIMIT_CLEANUP_THRESHOLD = float(
|
||||
os.environ.get("FLASKPASTE_RATE_CLEANUP_THRESHOLD", "0.8")
|
||||
)
|
||||
RATE_LIMIT_CLEANUP_THRESHOLD = float(os.environ.get("FLASKPASTE_RATE_CLEANUP_THRESHOLD", "0.8"))
|
||||
|
||||
# ENUM-001: Rate limiting for paste lookups (prevents enumeration attacks)
|
||||
# Separate from creation limits - allows more reads but prevents brute-force
|
||||
|
||||
@@ -276,9 +276,7 @@ class PKI:
|
||||
Raises:
|
||||
PKIError: If unable to generate unique serial after max retries
|
||||
"""
|
||||
existing_serials = {
|
||||
cert["serial"] for cert in self._certificates.values()
|
||||
}
|
||||
existing_serials = {cert["serial"] for cert in self._certificates.values()}
|
||||
|
||||
for _ in range(_SERIAL_MAX_RETRIES):
|
||||
serial = x509.random_serial_number()
|
||||
|
||||
@@ -13,15 +13,13 @@ Examples:
|
||||
./run_fuzz.py --phases 1,2,3 # Run specific phases
|
||||
./run_fuzz.py --quick # Quick smoke test
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import signal
|
||||
import socket
|
||||
import string
|
||||
@@ -114,9 +112,7 @@ class FlaskPasteFuzzer:
|
||||
def log(self, msg: str, level: str = "INFO") -> None:
|
||||
"""Log message."""
|
||||
if self.verbose or level in ("ERROR", "CRITICAL", "FINDING"):
|
||||
symbol = {"INFO": "●", "ERROR": "✗", "FINDING": "⚠", "OK": "✓"}.get(
|
||||
level, "●"
|
||||
)
|
||||
symbol = {"INFO": "●", "ERROR": "✗", "FINDING": "⚠", "OK": "✓"}.get(level, "●")
|
||||
print(f"{symbol} {msg}")
|
||||
|
||||
def start_server(self) -> bool:
|
||||
@@ -139,9 +135,11 @@ class FlaskPasteFuzzer:
|
||||
)
|
||||
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
log_file = open(self.fuzz_dir / "logs" / "server.log", "w")
|
||||
self.log_file = open( # noqa: SIM115
|
||||
self.fuzz_dir / "logs" / "server.log", "w"
|
||||
)
|
||||
|
||||
self.server_proc = subprocess.Popen(
|
||||
self.server_proc = subprocess.Popen( # noqa: S603
|
||||
[
|
||||
str(project_root / "venv" / "bin" / "python"),
|
||||
"run.py",
|
||||
@@ -152,7 +150,7 @@ class FlaskPasteFuzzer:
|
||||
],
|
||||
cwd=project_root,
|
||||
env=env,
|
||||
stdout=log_file,
|
||||
stdout=self.log_file,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
|
||||
@@ -165,7 +163,7 @@ class FlaskPasteFuzzer:
|
||||
sock.close()
|
||||
self.log("Server started successfully", "OK")
|
||||
return True
|
||||
except (ConnectionRefusedError, socket.timeout):
|
||||
except (TimeoutError, ConnectionRefusedError):
|
||||
time.sleep(0.5)
|
||||
|
||||
self.log("Server failed to start", "ERROR")
|
||||
@@ -283,7 +281,7 @@ class FlaskPasteFuzzer:
|
||||
# HTTP method enumeration
|
||||
methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "TRACE"]
|
||||
for method in methods:
|
||||
status, body, _ = self.http_request(method, "/")
|
||||
status, _body, _ = self.http_request(method, "/")
|
||||
requests += 1
|
||||
if method == "TRACE" and status == 200:
|
||||
findings.append(
|
||||
@@ -333,7 +331,7 @@ class FlaskPasteFuzzer:
|
||||
]
|
||||
|
||||
for payload in payloads:
|
||||
status, body, duration = self.http_request(
|
||||
status, body, _duration = self.http_request(
|
||||
"POST",
|
||||
"/",
|
||||
data=payload,
|
||||
@@ -427,7 +425,7 @@ class FlaskPasteFuzzer:
|
||||
encoded = quote(payload, safe="")
|
||||
|
||||
# Test in paste ID
|
||||
status, body, duration = self.http_request("GET", f"/{encoded}")
|
||||
_status, body, duration = self.http_request("GET", f"/{encoded}")
|
||||
requests += 1
|
||||
|
||||
# Timing-based detection
|
||||
@@ -469,7 +467,7 @@ class FlaskPasteFuzzer:
|
||||
]
|
||||
|
||||
for payload in traversal_payloads:
|
||||
status, body, _ = self.http_request("GET", f"/{payload}")
|
||||
_status, body, _ = self.http_request("GET", f"/{payload}")
|
||||
requests += 1
|
||||
|
||||
if b"root:" in body:
|
||||
@@ -494,7 +492,7 @@ class FlaskPasteFuzzer:
|
||||
]
|
||||
|
||||
for payload, indicator in ssti_payloads:
|
||||
status, body, _ = self.http_request(
|
||||
_status, body, _ = self.http_request(
|
||||
"POST",
|
||||
"/",
|
||||
data=payload.encode(),
|
||||
@@ -578,9 +576,7 @@ class FlaskPasteFuzzer:
|
||||
]
|
||||
|
||||
for fp in fake_fingerprints:
|
||||
status, body, _ = self.http_request(
|
||||
"GET", "/pastes", headers={"X-SSL-Client-SHA1": fp}
|
||||
)
|
||||
status, body, _ = self.http_request("GET", "/pastes", headers={"X-SSL-Client-SHA1": fp})
|
||||
requests += 1
|
||||
|
||||
if status == 200:
|
||||
@@ -609,9 +605,7 @@ class FlaskPasteFuzzer:
|
||||
]
|
||||
|
||||
for headers, desc in pow_bypasses:
|
||||
status, body, _ = self.http_request(
|
||||
"POST", "/", data=b"test", headers=headers
|
||||
)
|
||||
status, body, _ = self.http_request("POST", "/", data=b"test", headers=headers)
|
||||
requests += 1
|
||||
|
||||
if status == 201:
|
||||
@@ -705,7 +699,9 @@ class FlaskPasteFuzzer:
|
||||
endpoint="POST /",
|
||||
vector="X-Expiry",
|
||||
description=f"Expiry manipulation ({desc}) accepted",
|
||||
reproduction=f'curl -X POST {self.target}/ -d test -H "X-Expiry: {value}"',
|
||||
reproduction=(
|
||||
f'curl -X POST {self.target}/ -d test -H "X-Expiry: {value}"'
|
||||
),
|
||||
impact="Paste lifetime manipulation",
|
||||
)
|
||||
)
|
||||
@@ -734,7 +730,10 @@ class FlaskPasteFuzzer:
|
||||
endpoint="POST /",
|
||||
vector="Body size",
|
||||
description=f"Large payload ({size // 1024 // 1024}MB) accepted",
|
||||
reproduction=f"dd if=/dev/urandom bs=1M count={size // 1024 // 1024} | curl -X POST {self.target}/ --data-binary @-",
|
||||
reproduction=(
|
||||
f"dd if=/dev/urandom bs=1M count={size // 1024 // 1024} | "
|
||||
f"curl -X POST {self.target}/ --data-binary @-"
|
||||
),
|
||||
impact="Resource exhaustion",
|
||||
evidence=f"Upload time: {duration:.2f}s",
|
||||
)
|
||||
@@ -747,7 +746,10 @@ class FlaskPasteFuzzer:
|
||||
endpoint="POST /",
|
||||
vector="Body size",
|
||||
description=f"Server crash on large payload ({size // 1024 // 1024}MB)",
|
||||
reproduction=f"dd if=/dev/urandom bs=1M count={size // 1024 // 1024} | curl -X POST {self.target}/ --data-binary @-",
|
||||
reproduction=(
|
||||
f"dd if=/dev/urandom bs=1M count={size // 1024 // 1024} | "
|
||||
f"curl -X POST {self.target}/ --data-binary @-"
|
||||
),
|
||||
impact="Denial of service",
|
||||
)
|
||||
)
|
||||
@@ -821,9 +823,7 @@ class FlaskPasteFuzzer:
|
||||
def generate_report(self) -> str:
|
||||
"""Generate final report."""
|
||||
severity_order = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3, "INFO": 4}
|
||||
sorted_findings = sorted(
|
||||
self.findings, key=lambda f: severity_order.get(f.severity, 5)
|
||||
)
|
||||
sorted_findings = sorted(self.findings, key=lambda f: severity_order.get(f.severity, 5))
|
||||
|
||||
report = []
|
||||
report.append("=" * 79)
|
||||
@@ -931,9 +931,7 @@ class FlaskPasteFuzzer:
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="FlaskPaste Offensive Security Tester")
|
||||
parser.add_argument(
|
||||
"--phases", type=str, help="Comma-separated phase numbers (e.g., 1,2,3)"
|
||||
)
|
||||
parser.add_argument("--phases", type=str, help="Comma-separated phase numbers (e.g., 1,2,3)")
|
||||
parser.add_argument("--quick", action="store_true", help="Quick smoke test")
|
||||
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
|
||||
parser.add_argument("--fuzz-dir", type=str, help="Fuzzing workspace directory")
|
||||
|
||||
Reference in New Issue
Block a user