Expand 39 api-reference stubs with real tool docs, expand 15 agent.py boilerplate stubs

This commit is contained in:
mukul975
2026-03-19 13:29:50 +01:00
parent d63b578a2f
commit d005ae764b
191 changed files with 4300 additions and 587 deletions
@@ -1,28 +1,211 @@
# API Reference: CSP bypass testing agent
# API Reference: Content Security Policy (CSP) Bypass Testing
## API Details
Content-Security-Policy header parsing, directive analysis, bypass vectors, nonce detection
## Libraries Used
| Library | Purpose |
|---------|---------|
| `requests` | Fetch target page headers and HTML content |
| `re` | Parse CSP directives and detect bypass patterns |
| `json` | Structure findings and report output |
| `urllib.parse` | Parse and analyze allowed CSP source domains |
## Installation
```bash
pip install requests re
pip install requests
```
## Libraries
## CSP Directive Reference
| Library | Use |
|---------|-----|
| `requests` | requests |
| `re` | re |
| Directive | Controls |
|-----------|----------|
| `default-src` | Fallback for all resource types |
| `script-src` | JavaScript execution sources |
| `style-src` | CSS stylesheet sources |
| `img-src` | Image sources |
| `connect-src` | XMLHttpRequest, fetch, WebSocket |
| `font-src` | Font file sources |
| `object-src` | Plugin sources (Flash, Java) |
| `frame-src` | iframe embedding sources |
| `base-uri` | Controls `<base>` tag URLs |
| `form-action` | Controls form submission targets |
| `frame-ancestors` | Controls who can embed this page |
| `report-uri` | CSP violation report endpoint |
## Authentication
## Core Operations
| Method | Header |
|--------|--------|
| Bearer Token | `Authorization: Bearer <token>` |
| API Key | `X-API-Key: <key>` |
### Fetch and Parse CSP Header
```python
import requests
import re
def get_csp(url):
resp = requests.get(url, timeout=10)
csp = resp.headers.get("Content-Security-Policy", "")
csp_ro = resp.headers.get("Content-Security-Policy-Report-Only", "")
return {
"url": url,
"csp": csp,
"csp_report_only": csp_ro,
"has_csp": bool(csp),
"directives": parse_csp(csp) if csp else {},
}
def parse_csp(csp_string):
directives = {}
for directive in csp_string.split(";"):
parts = directive.strip().split()
if parts:
name = parts[0].lower()
values = parts[1:] if len(parts) > 1 else []
directives[name] = values
return directives
```
### Analyze CSP for Weaknesses
```python
BYPASS_PATTERNS = {
"'unsafe-inline'": "Allows inline scripts — XSS bypass",
"'unsafe-eval'": "Allows eval() — code injection bypass",
"data:": "Allows data: URIs — can inject inline content",
"blob:": "Allows blob: URIs — can create executable blobs",
"*": "Wildcard source — no effective restriction",
"http:": "Allows HTTP — mixed content / MITM bypass",
}
JSONP_ENDPOINTS = [
"accounts.google.com", "ajax.googleapis.com",
"cdn.jsdelivr.net", "cdnjs.cloudflare.com",
"*.githubusercontent.com", "raw.githubusercontent.com",
]
def analyze_csp(directives):
findings = []
# Check for missing critical directives
if "default-src" not in directives and "script-src" not in directives:
findings.append({
"directive": "script-src",
"issue": "No script-src or default-src — scripts unrestricted",
"severity": "critical",
})
if "object-src" not in directives:
findings.append({
"directive": "object-src",
"issue": "Missing object-src — plugin-based XSS possible",
"severity": "high",
})
if "base-uri" not in directives:
findings.append({
"directive": "base-uri",
"issue": "Missing base-uri — base tag injection possible",
"severity": "medium",
})
# Check each directive for bypass patterns
for directive, values in directives.items():
for value in values:
if value in BYPASS_PATTERNS:
findings.append({
"directive": directive,
"value": value,
"issue": BYPASS_PATTERNS[value],
"severity": "high" if value in ("'unsafe-inline'", "'unsafe-eval'", "*") else "medium",
})
# Check for JSONP-hosting CDNs
for jsonp_host in JSONP_ENDPOINTS:
if jsonp_host in value or value.endswith(jsonp_host):
findings.append({
"directive": directive,
"value": value,
"issue": f"Allows {jsonp_host} — JSONP/script gadget bypass possible",
"severity": "high",
})
return findings
```
### Check for Nonce/Hash Based CSP
```python
def check_nonce_hash(directives, html_content):
script_src = directives.get("script-src", [])
nonces = [v for v in script_src if v.startswith("'nonce-")]
hashes = [v for v in script_src if v.startswith("'sha256-") or v.startswith("'sha384-")]
findings = []
if nonces:
# Check if nonce is reused (static)
nonce_value = nonces[0].strip("'").replace("nonce-", "")
if len(nonce_value) < 16:
findings.append({
"issue": "Nonce is too short — may be predictable",
"severity": "medium",
})
if not nonces and not hashes and "'strict-dynamic'" not in script_src:
if "'unsafe-inline'" not in script_src:
findings.append({
"issue": "No nonce, hash, or strict-dynamic — consider adding",
"severity": "info",
})
return {"nonces": len(nonces), "hashes": len(hashes), "findings": findings}
```
### Generate Bypass Payloads
```python
def suggest_bypasses(directives):
"""Suggest CSP bypass techniques based on the policy."""
bypasses = []
script_src = directives.get("script-src", directives.get("default-src", []))
if "'unsafe-inline'" in script_src:
bypasses.append({
"technique": "Inline script injection",
"payload": "<script>alert(document.domain)</script>",
})
if "'unsafe-eval'" in script_src:
bypasses.append({
"technique": "eval() injection",
"payload": "<img src=x onerror=\"eval(atob('YWxlcnQoMSk='))\">",
})
if any("googleapis.com" in v for v in script_src):
bypasses.append({
"technique": "Google JSONP callback",
"payload": "<script src='https://accounts.google.com/o/oauth2/revoke?callback=alert(1)'></script>",
})
if "data:" in script_src:
bypasses.append({
"technique": "Data URI script",
"payload": "<script src='data:text/javascript,alert(1)'></script>",
})
return bypasses
```
## Output Format
```json
{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"}
{
"url": "https://example.com",
"has_csp": true,
"directives_count": 8,
"findings": [
{
"directive": "script-src",
"value": "'unsafe-inline'",
"issue": "Allows inline scripts — XSS bypass",
"severity": "high"
}
],
"bypass_techniques": 2,
"overall_rating": "weak"
}
```
@@ -1,60 +1,312 @@
#!/usr/bin/env python3
"""CSP bypass testing agent."""
import argparse, json
"""Content Security Policy (CSP) analysis and bypass testing agent.
Fetches and analyzes CSP headers from web applications to identify
misconfigurations, overly permissive directives, and potential bypass
vectors. Tests for unsafe-inline, unsafe-eval, wildcard sources,
missing directives, and known CSP bypass patterns.
AUTHORIZED TESTING ONLY: Only use against targets you have explicit
written permission to test.
"""
import argparse
import json
import re
import sys
from datetime import datetime, timezone
try:
import requests
except ImportError:
requests = None
print("[!] 'requests' library required: pip install requests", file=sys.stderr)
sys.exit(1)
def run_scan(target, token=None):
CSP_DIRECTIVES = [
"default-src", "script-src", "style-src", "img-src", "font-src",
"connect-src", "media-src", "object-src", "frame-src", "child-src",
"worker-src", "frame-ancestors", "form-action", "base-uri",
"manifest-src", "prefetch-src", "navigate-to",
]
def fetch_csp(url, headers=None, cookies=None):
"""Fetch CSP header(s) from a URL."""
print(f"[*] Fetching CSP from {url}")
h = headers or {}
c = cookies or {}
resp = requests.get(url, headers=h, cookies=c, timeout=15, allow_redirects=True)
csp_header = resp.headers.get("Content-Security-Policy", "")
csp_ro = resp.headers.get("Content-Security-Policy-Report-Only", "")
print(f"[+] Status: {resp.status_code}")
if csp_header:
print(f"[+] CSP header found ({len(csp_header)} chars)")
else:
print("[!] No CSP header found")
if csp_ro:
print(f"[+] CSP-Report-Only header found ({len(csp_ro)} chars)")
return csp_header, csp_ro, resp.status_code
def parse_csp(csp_string):
"""Parse a CSP string into a structured dict."""
directives = {}
if not csp_string:
return directives
for part in csp_string.split(";"):
part = part.strip()
if not part:
continue
tokens = part.split()
if tokens:
directive_name = tokens[0].lower()
values = tokens[1:] if len(tokens) > 1 else []
directives[directive_name] = values
return directives
def analyze_csp(directives, csp_string):
"""Analyze CSP directives for security weaknesses."""
findings = []
if not requests: return [{"error": "requests required"}]
headers = {"Authorization": f"Bearer {token}"} if token else {}
try:
resp = requests.get(f"{target}", headers=headers, timeout=15)
if resp.status_code == 200:
findings.append({"check": "Target Accessible", "status": "OK", "severity": "INFO"})
else:
findings.append({"check": "Target Access", "status": f"HTTP {resp.status_code}", "severity": "MEDIUM"})
except requests.RequestException as e:
findings.append({"error": str(e)})
# Missing CSP entirely
if not directives:
findings.append({
"check": "CSP Header Present",
"severity": "HIGH",
"status": "MISSING",
"description": "No Content-Security-Policy header",
"recommendation": "Implement a CSP header",
})
return findings
# Check for missing critical directives
if "default-src" not in directives:
findings.append({
"check": "default-src directive",
"severity": "HIGH",
"status": "MISSING",
"description": "No default-src fallback directive",
"recommendation": "Add default-src 'none' or default-src 'self'",
})
if "script-src" not in directives and "default-src" not in directives:
findings.append({
"check": "script-src directive",
"severity": "CRITICAL",
"status": "MISSING",
"description": "No script-src or default-src; scripts unrestricted",
})
if "object-src" not in directives:
findings.append({
"check": "object-src directive",
"severity": "MEDIUM",
"status": "MISSING",
"description": "Missing object-src; plugin-based XSS possible",
"recommendation": "Add object-src 'none'",
})
if "base-uri" not in directives:
findings.append({
"check": "base-uri directive",
"severity": "MEDIUM",
"status": "MISSING",
"description": "Missing base-uri; base tag injection possible",
"recommendation": "Add base-uri 'self' or base-uri 'none'",
})
if "frame-ancestors" not in directives:
findings.append({
"check": "frame-ancestors directive",
"severity": "MEDIUM",
"status": "MISSING",
"description": "Missing frame-ancestors; clickjacking possible",
"recommendation": "Add frame-ancestors 'self'",
})
# Analyze each directive
for directive, values in directives.items():
values_str = " ".join(values)
# unsafe-inline
if "'unsafe-inline'" in values:
sev = "CRITICAL" if directive in ("script-src", "default-src") else "MEDIUM"
findings.append({
"check": f"unsafe-inline in {directive}",
"severity": sev,
"status": "FAIL",
"description": f"'unsafe-inline' allows inline scripts/styles in {directive}",
"bypass": "Inject inline <script> or event handlers",
})
# unsafe-eval
if "'unsafe-eval'" in values:
findings.append({
"check": f"unsafe-eval in {directive}",
"severity": "CRITICAL" if directive in ("script-src", "default-src") else "HIGH",
"status": "FAIL",
"description": f"'unsafe-eval' allows eval() and similar in {directive}",
"bypass": "Use eval(), Function(), setTimeout('string') for XSS",
})
# Wildcard sources
if "*" in values:
findings.append({
"check": f"Wildcard (*) in {directive}",
"severity": "HIGH",
"status": "FAIL",
"description": f"Wildcard source in {directive} allows loading from any origin",
"bypass": "Host payload on any domain",
})
# data: URI
if "data:" in values and directive in ("script-src", "default-src", "object-src"):
findings.append({
"check": f"data: URI in {directive}",
"severity": "HIGH",
"status": "FAIL",
"description": f"data: URI in {directive} allows inline data execution",
"bypass": "Use data:text/html payload or data:application/javascript",
})
# blob: URI
if "blob:" in values and directive in ("script-src", "default-src", "worker-src"):
findings.append({
"check": f"blob: URI in {directive}",
"severity": "MEDIUM",
"status": "FAIL",
"description": f"blob: URI in {directive} may allow bypass via Blob URLs",
})
# Known bypass CDNs
bypass_cdns = ["cdn.jsdelivr.net", "cdnjs.cloudflare.com", "unpkg.com",
"ajax.googleapis.com", "raw.githubusercontent.com"]
for cdn in bypass_cdns:
if cdn in values_str:
findings.append({
"check": f"Bypassable CDN in {directive}",
"severity": "HIGH",
"status": "FAIL",
"description": f"{cdn} in {directive} can host arbitrary scripts",
"bypass": f"Upload/find malicious script on {cdn}",
})
# http: in HTTPS context
if any(v.startswith("http:") for v in values):
findings.append({
"check": f"HTTP source in {directive}",
"severity": "MEDIUM",
"status": "FAIL",
"description": f"HTTP source allows MitM injection in {directive}",
})
return findings
def analyze_results(target, token=None):
findings = []
if not requests: return []
headers = {"Authorization": f"Bearer {token}"} if token else {}
try:
resp = requests.get(f"{target}/api/v1/results", headers=headers, timeout=15)
if resp.status_code == 200:
data = resp.json()
for item in data.get("findings", data.get("results", [])):
severity = item.get("severity", item.get("risk", "MEDIUM"))
findings.append({"check": item.get("name", item.get("title", "unknown")),
"severity": severity.upper() if isinstance(severity, str) else "MEDIUM"})
except requests.RequestException:
pass
return findings
def format_summary(url, directives, findings, csp_string):
"""Print analysis summary."""
print(f"\n{'='*60}")
print(f" CSP Analysis Report")
print(f"{'='*60}")
print(f" URL : {url}")
print(f" Directives : {len(directives)}")
print(f" Findings : {len(findings)}")
severity_counts = {}
for f in findings:
sev = f.get("severity", "INFO")
severity_counts[sev] = severity_counts.get(sev, 0) + 1
print(f"\n By Severity:")
for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]:
count = severity_counts.get(sev, 0)
if count > 0:
print(f" {sev:10s}: {count}")
if directives:
print(f"\n Parsed Directives:")
for d, v in directives.items():
print(f" {d:20s}: {' '.join(v)[:60]}")
if findings:
print(f"\n Security Issues:")
for f in findings:
if f["severity"] in ("CRITICAL", "HIGH"):
bypass = f" | Bypass: {f['bypass']}" if f.get("bypass") else ""
print(f" [{f['severity']:8s}] {f['check']}{bypass}")
return severity_counts
def main():
p = argparse.ArgumentParser(description="CSP bypass testing agent")
p.add_argument("--target", required=True, help="Target URL or IP")
p.add_argument("--token", help="API token")
p.add_argument("--output", "-o", help="Output JSON report")
p.add_argument("--verbose", "-v", action="store_true")
a = p.parse_args()
print("[*] CSP bypass testing agent")
report = {"timestamp": datetime.now(timezone.utc).isoformat(), "target": a.target, "findings": []}
report["findings"].extend(run_scan(a.target, a.token))
report["findings"].extend(analyze_results(a.target, a.token))
high = sum(1 for f in report["findings"] if f.get("severity") in ("HIGH", "CRITICAL"))
report["risk_level"] = "CRITICAL" if high > 2 else "HIGH" if high else "MEDIUM" if report["findings"] else "LOW"
print(f"[*] {len(report['findings'])} findings, risk: {report['risk_level']}")
if a.output:
with open(a.output, "w") as f: json.dump(report, f, indent=2)
parser = argparse.ArgumentParser(
description="CSP analysis and bypass testing agent (authorized testing only)"
)
parser.add_argument("--url", required=True, help="Target URL to fetch CSP from")
parser.add_argument("--csp-string", help="Analyze a CSP string directly instead of fetching")
parser.add_argument("--header", nargs="+", help="Custom headers (key:value)")
parser.add_argument("--cookie", help="Cookie string")
parser.add_argument("--output", "-o", help="Output JSON report path")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
if args.csp_string:
csp_string = args.csp_string
csp_ro = ""
status_code = 0
else:
headers = {}
if args.header:
for h in args.header:
k, v = h.split(":", 1)
headers[k.strip()] = v.strip()
cookies = {}
if args.cookie:
for pair in args.cookie.split(";"):
if "=" in pair:
k, v = pair.strip().split("=", 1)
cookies[k] = v
csp_string, csp_ro, status_code = fetch_csp(args.url, headers or None, cookies or None)
directives = parse_csp(csp_string)
findings = analyze_csp(directives, csp_string)
if csp_ro:
ro_directives = parse_csp(csp_ro)
findings.append({
"check": "CSP-Report-Only mode",
"severity": "MEDIUM",
"status": "WARN",
"description": "CSP in report-only mode does not enforce restrictions",
})
severity_counts = format_summary(args.url, directives, findings, csp_string)
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"tool": "CSP Analyzer",
"url": args.url,
"csp_header": csp_string,
"csp_report_only": csp_ro,
"directives": {k: v for k, v in directives.items()},
"findings": findings,
"severity_counts": severity_counts,
"risk_level": (
"CRITICAL" if severity_counts.get("CRITICAL", 0) > 0
else "HIGH" if severity_counts.get("HIGH", 0) > 0
else "MEDIUM" if severity_counts.get("MEDIUM", 0) > 0
else "LOW"
),
}
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"\n[+] Report saved to {args.output}")
elif args.verbose:
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()