mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-10 21:24:56 +03:00
c21af3347e
- Add scripts/agent.py and references/api-reference.md to all remaining skills - Update all 648 LICENSE files: copyright now reads 'Mahipal' - Add implementing-security-monitoring-with-datadog (new skill with full anatomy) - All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
197 lines
6.6 KiB
Python
197 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
"""API enumeration attack detection agent."""
|
|
|
|
import json
|
|
import sys
|
|
import argparse
|
|
import re
|
|
from datetime import datetime
|
|
from collections import defaultdict
|
|
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
print("Install: pip install requests")
|
|
sys.exit(1)
|
|
|
|
|
|
ENUMERATION_PATTERNS = [
|
|
re.compile(r"/api/v\d+/users/\d+", re.IGNORECASE),
|
|
re.compile(r"/api/v\d+/accounts/[a-f0-9-]+", re.IGNORECASE),
|
|
re.compile(r"/api/v\d+/orders/\d+", re.IGNORECASE),
|
|
re.compile(r"/graphql.*introspection", re.IGNORECASE),
|
|
re.compile(r"/(admin|internal|debug|swagger|api-docs)", re.IGNORECASE),
|
|
]
|
|
|
|
SEQUENTIAL_THRESHOLD = 10
|
|
RATE_THRESHOLD = 50
|
|
|
|
|
|
def parse_access_log(log_path):
|
|
"""Parse NGINX/Apache combined log format for API requests."""
|
|
log_pattern = re.compile(
|
|
r'(\S+) \S+ \S+ \[([^\]]+)\] "(\S+) (\S+) \S+" (\d+) \d+'
|
|
)
|
|
entries = []
|
|
with open(log_path, "r") as f:
|
|
for line in f:
|
|
m = log_pattern.match(line)
|
|
if m:
|
|
entries.append({
|
|
"ip": m.group(1),
|
|
"timestamp": m.group(2),
|
|
"method": m.group(3),
|
|
"path": m.group(4),
|
|
"status": int(m.group(5)),
|
|
})
|
|
return entries
|
|
|
|
|
|
def detect_sequential_ids(entries):
|
|
"""Detect sequential ID enumeration in API paths."""
|
|
id_pattern = re.compile(r"/(\d+)(?:/|$|\?)")
|
|
ip_sequences = defaultdict(list)
|
|
for entry in entries:
|
|
m = id_pattern.search(entry["path"])
|
|
if m:
|
|
ip_sequences[entry["ip"]].append(int(m.group(1)))
|
|
|
|
findings = []
|
|
for ip, ids in ip_sequences.items():
|
|
if len(ids) < SEQUENTIAL_THRESHOLD:
|
|
continue
|
|
sorted_ids = sorted(ids)
|
|
sequential_count = sum(1 for i in range(1, len(sorted_ids))
|
|
if sorted_ids[i] - sorted_ids[i-1] == 1)
|
|
if sequential_count >= SEQUENTIAL_THRESHOLD:
|
|
findings.append({
|
|
"ip": ip,
|
|
"issue": f"Sequential ID enumeration detected ({sequential_count} sequential IDs)",
|
|
"severity": "HIGH",
|
|
"sample_ids": sorted_ids[:20],
|
|
"total_requests": len(ids),
|
|
})
|
|
return findings
|
|
|
|
|
|
def detect_rate_anomalies(entries, window_seconds=60):
|
|
"""Detect abnormal request rates per IP to API endpoints."""
|
|
ip_counts = defaultdict(int)
|
|
ip_404s = defaultdict(int)
|
|
ip_401s = defaultdict(int)
|
|
for entry in entries:
|
|
if "/api/" in entry["path"]:
|
|
ip_counts[entry["ip"]] += 1
|
|
if entry["status"] == 404:
|
|
ip_404s[entry["ip"]] += 1
|
|
elif entry["status"] == 401:
|
|
ip_401s[entry["ip"]] += 1
|
|
|
|
findings = []
|
|
for ip, count in ip_counts.items():
|
|
if count > RATE_THRESHOLD:
|
|
findings.append({
|
|
"ip": ip,
|
|
"issue": f"High API request rate ({count} requests)",
|
|
"severity": "MEDIUM",
|
|
"total_requests": count,
|
|
"404_count": ip_404s.get(ip, 0),
|
|
"401_count": ip_401s.get(ip, 0),
|
|
})
|
|
if ip_404s.get(ip, 0) > 20:
|
|
findings.append({
|
|
"ip": ip,
|
|
"issue": f"Excessive 404s on API ({ip_404s[ip]} not-found responses)",
|
|
"severity": "HIGH",
|
|
"detail": "Possible endpoint discovery/fuzzing",
|
|
})
|
|
return findings
|
|
|
|
|
|
def detect_path_enumeration(entries):
|
|
"""Detect API path/endpoint enumeration patterns."""
|
|
ip_paths = defaultdict(set)
|
|
for entry in entries:
|
|
ip_paths[entry["ip"]].add(entry["path"].split("?")[0])
|
|
|
|
findings = []
|
|
for ip, paths in ip_paths.items():
|
|
for pattern in ENUMERATION_PATTERNS:
|
|
matched = [p for p in paths if pattern.search(p)]
|
|
if len(matched) > 5:
|
|
findings.append({
|
|
"ip": ip,
|
|
"issue": f"Path enumeration pattern: {pattern.pattern}",
|
|
"severity": "HIGH",
|
|
"matched_paths": len(matched),
|
|
"samples": list(matched)[:5],
|
|
})
|
|
return findings
|
|
|
|
|
|
def query_waf_logs(waf_url, api_key, hours=24):
|
|
"""Query WAF API for blocked enumeration attempts."""
|
|
headers = {"Authorization": f"Bearer {api_key}"}
|
|
try:
|
|
resp = requests.get(f"{waf_url}/api/v1/events",
|
|
params={"hours": hours, "rule_category": "api-abuse"},
|
|
headers=headers, timeout=15)
|
|
resp.raise_for_status()
|
|
return resp.json().get("events", [])
|
|
except Exception as e:
|
|
return [{"error": str(e)}]
|
|
|
|
|
|
def run_audit(args):
|
|
"""Execute API enumeration detection audit."""
|
|
print(f"\n{'='*60}")
|
|
print(f" API ENUMERATION ATTACK DETECTION")
|
|
print(f" Generated: {datetime.utcnow().isoformat()} UTC")
|
|
print(f"{'='*60}\n")
|
|
|
|
report = {}
|
|
|
|
if args.log_file:
|
|
entries = parse_access_log(args.log_file)
|
|
report["total_log_entries"] = len(entries)
|
|
print(f"Parsed {len(entries)} log entries from {args.log_file}\n")
|
|
|
|
seq_findings = detect_sequential_ids(entries)
|
|
report["sequential_id_findings"] = seq_findings
|
|
print(f"--- SEQUENTIAL ID ENUMERATION ({len(seq_findings)} findings) ---")
|
|
for f in seq_findings[:10]:
|
|
print(f" [{f['severity']}] {f['ip']}: {f['issue']}")
|
|
|
|
rate_findings = detect_rate_anomalies(entries)
|
|
report["rate_findings"] = rate_findings
|
|
print(f"\n--- RATE ANOMALIES ({len(rate_findings)} findings) ---")
|
|
for f in rate_findings[:10]:
|
|
print(f" [{f['severity']}] {f['ip']}: {f['issue']}")
|
|
|
|
path_findings = detect_path_enumeration(entries)
|
|
report["path_findings"] = path_findings
|
|
print(f"\n--- PATH ENUMERATION ({len(path_findings)} findings) ---")
|
|
for f in path_findings[:10]:
|
|
print(f" [{f['severity']}] {f['ip']}: {f['issue']}")
|
|
|
|
return report
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="API Enumeration Detection Agent")
|
|
parser.add_argument("--log-file", help="Access log file to analyze")
|
|
parser.add_argument("--waf-url", help="WAF API URL for event queries")
|
|
parser.add_argument("--waf-key", help="WAF API key")
|
|
parser.add_argument("--output", help="Save report to JSON file")
|
|
args = parser.parse_args()
|
|
|
|
report = run_audit(args)
|
|
if args.output:
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2, default=str)
|
|
print(f"\n[+] Report saved to {args.output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|