mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-08-10 20:33:20 +03:00
Add folder anatomy (scripts/agent.py + references/api-reference.md) for 648 cybersecurity skills
Complete skill folder anatomy across all cybersecurity skills: - scripts/agent.py: 80-150 line Python agents using real libraries (impacket, boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.) - references/api-reference.md: real API documentation with method signatures - LICENSE: MIT license for all skill folders
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Anthropic Agent Skills Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,94 @@
|
||||
# API Reference: Shadow API Endpoint Detection
|
||||
|
||||
## OpenAPI 3.0 Specification Structure
|
||||
|
||||
### Loading Paths
|
||||
```json
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/api/users": {
|
||||
"get": { "summary": "List users" },
|
||||
"post": { "summary": "Create user" }
|
||||
},
|
||||
"/api/users/{id}": {
|
||||
"get": { "summary": "Get user by ID" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Key Fields
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `paths` | Map of URL paths to operations |
|
||||
| `servers[].url` | Base URL for the API |
|
||||
| `components.securitySchemes` | Authentication methods |
|
||||
|
||||
## Web Access Log Formats
|
||||
|
||||
### Apache/Nginx Combined Log
|
||||
```
|
||||
127.0.0.1 - frank [10/Oct/2024:13:55:36 -0700] "GET /api/users HTTP/1.1" 200 2326
|
||||
```
|
||||
|
||||
### Regex Pattern
|
||||
```python
|
||||
r'(\S+)\s+\S+\s+\S+\s+\[([^\]]+)\]\s+"(\S+)\s+(\S+)\s+\S+"\s+(\d+)\s+(\d+)'
|
||||
```
|
||||
|
||||
| Group | Content |
|
||||
|-------|---------|
|
||||
| 1 | Client IP |
|
||||
| 2 | Timestamp |
|
||||
| 3 | HTTP Method |
|
||||
| 4 | Request Path |
|
||||
| 5 | Status Code |
|
||||
| 6 | Response Size |
|
||||
|
||||
## Path Normalization Patterns
|
||||
|
||||
### ID replacement
|
||||
```python
|
||||
re.sub(r'/\d+', '/{id}', path) # /users/123 -> /users/{id}
|
||||
re.sub(r'/[0-9a-f]{24,}', '/{id}', path) # MongoDB ObjectId
|
||||
re.sub(r'/[0-9a-f-]{36}', '/{uuid}', path) # UUID v4
|
||||
```
|
||||
|
||||
## OWASP API Security Top 10 (2023)
|
||||
|
||||
| # | Risk | Relevance to Shadow APIs |
|
||||
|---|------|--------------------------|
|
||||
| API1 | Broken Object Level Auth | Shadow endpoints may lack auth |
|
||||
| API2 | Broken Authentication | Undocumented auth bypass |
|
||||
| API5 | Broken Function Level Auth | Admin endpoints exposed |
|
||||
| API9 | Improper Inventory Management | Core shadow API risk |
|
||||
|
||||
## Akamai API Discovery
|
||||
|
||||
### List discovered APIs
|
||||
```http
|
||||
GET https://cloud.akamai.com/api-gateway/v1/apis/discovered
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
## AWS API Gateway — Export API
|
||||
```bash
|
||||
aws apigateway get-export \
|
||||
--rest-api-id abc123 \
|
||||
--stage-name prod \
|
||||
--export-type oas30 \
|
||||
exported-api.json
|
||||
```
|
||||
|
||||
## Burp Suite Enterprise — API Scan
|
||||
```http
|
||||
POST https://burp-enterprise/api/v1/scans
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"scan_type": "api_discovery",
|
||||
"target_url": "https://api.example.com",
|
||||
"openapi_spec": "https://api.example.com/openapi.json"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Agent for discovering undocumented (shadow) API endpoints via traffic analysis."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timezone
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
def parse_access_log(log_path, api_prefix="/api"):
|
||||
"""Parse web server access logs to extract API endpoint calls."""
|
||||
endpoints = defaultdict(lambda: {"count": 0, "methods": set(), "status_codes": set()})
|
||||
# Combined log format: IP - - [date] "METHOD path HTTP/ver" status size
|
||||
pattern = re.compile(
|
||||
r'(\S+)\s+\S+\s+\S+\s+\[([^\]]+)\]\s+"(\S+)\s+(\S+)\s+\S+"\s+(\d+)\s+(\d+)'
|
||||
)
|
||||
try:
|
||||
with open(log_path, "r") as f:
|
||||
for line in f:
|
||||
m = pattern.match(line)
|
||||
if not m:
|
||||
continue
|
||||
method, path, status = m.group(3), m.group(4), m.group(5)
|
||||
parsed = urlparse(path)
|
||||
clean_path = re.sub(r'/\d+', '/{id}', parsed.path)
|
||||
clean_path = re.sub(r'/[0-9a-f]{24,}', '/{id}', clean_path)
|
||||
clean_path = re.sub(
|
||||
r'/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}',
|
||||
'/{uuid}', clean_path
|
||||
)
|
||||
if api_prefix and not clean_path.startswith(api_prefix):
|
||||
continue
|
||||
key = f"{method} {clean_path}"
|
||||
endpoints[key]["count"] += 1
|
||||
endpoints[key]["methods"].add(method)
|
||||
endpoints[key]["status_codes"].add(status)
|
||||
except FileNotFoundError:
|
||||
print(f"[!] Log file not found: {log_path}")
|
||||
return endpoints
|
||||
|
||||
|
||||
def load_openapi_spec(spec_path):
|
||||
"""Load documented endpoints from OpenAPI/Swagger spec."""
|
||||
documented = set()
|
||||
try:
|
||||
with open(spec_path, "r") as f:
|
||||
spec = json.load(f)
|
||||
paths = spec.get("paths", {})
|
||||
for path, methods in paths.items():
|
||||
normalized = re.sub(r'\{[^}]+\}', '{id}', path)
|
||||
for method in methods:
|
||||
if method.upper() in ("GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"):
|
||||
documented.add(f"{method.upper()} {normalized}")
|
||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||
print(f"[!] Error loading spec: {e}")
|
||||
return documented
|
||||
|
||||
|
||||
def find_shadow_endpoints(observed, documented):
|
||||
"""Identify endpoints in traffic that are not in the API spec."""
|
||||
shadow = []
|
||||
for endpoint, data in observed.items():
|
||||
if endpoint not in documented:
|
||||
shadow.append({
|
||||
"endpoint": endpoint,
|
||||
"call_count": data["count"],
|
||||
"status_codes": sorted(data["status_codes"]),
|
||||
"risk": "HIGH" if any(s.startswith("2") for s in data["status_codes"]) else "MEDIUM",
|
||||
})
|
||||
return sorted(shadow, key=lambda x: x["call_count"], reverse=True)
|
||||
|
||||
|
||||
def classify_risk(shadow_endpoints):
|
||||
"""Classify shadow endpoints by risk category."""
|
||||
categories = {
|
||||
"debug": [], "admin": [], "internal": [],
|
||||
"deprecated": [], "unknown": [],
|
||||
}
|
||||
for ep in shadow_endpoints:
|
||||
path = ep["endpoint"].lower()
|
||||
if any(k in path for k in ["debug", "test", "dev", "health"]):
|
||||
categories["debug"].append(ep)
|
||||
elif any(k in path for k in ["admin", "manage", "console", "dashboard"]):
|
||||
categories["admin"].append(ep)
|
||||
elif any(k in path for k in ["internal", "private", "system"]):
|
||||
categories["internal"].append(ep)
|
||||
elif any(k in path for k in ["v1", "v0", "old", "legacy"]):
|
||||
categories["deprecated"].append(ep)
|
||||
else:
|
||||
categories["unknown"].append(ep)
|
||||
return categories
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Discover undocumented shadow API endpoints"
|
||||
)
|
||||
parser.add_argument("--access-log", required=True, help="Path to web access log")
|
||||
parser.add_argument("--openapi-spec", help="Path to OpenAPI/Swagger JSON spec")
|
||||
parser.add_argument("--api-prefix", default="/api", help="API path prefix filter")
|
||||
parser.add_argument("--output", "-o", help="Output JSON report path")
|
||||
parser.add_argument("--min-calls", type=int, default=1, help="Minimum call count threshold")
|
||||
args = parser.parse_args()
|
||||
|
||||
print("[*] Shadow API Endpoint Detection Agent")
|
||||
observed = parse_access_log(args.access_log, args.api_prefix)
|
||||
print(f"[*] Observed {len(observed)} unique API endpoints in traffic")
|
||||
|
||||
documented = set()
|
||||
if args.openapi_spec:
|
||||
documented = load_openapi_spec(args.openapi_spec)
|
||||
print(f"[*] Loaded {len(documented)} documented endpoints from spec")
|
||||
|
||||
shadow = find_shadow_endpoints(observed, documented)
|
||||
shadow = [s for s in shadow if s["call_count"] >= args.min_calls]
|
||||
categories = classify_risk(shadow)
|
||||
|
||||
report = {
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"total_observed": len(observed),
|
||||
"documented": len(documented),
|
||||
"shadow_count": len(shadow),
|
||||
"categories": {k: len(v) for k, v in categories.items()},
|
||||
"shadow_endpoints": shadow[:50],
|
||||
}
|
||||
|
||||
high_risk = sum(1 for s in shadow if s["risk"] == "HIGH")
|
||||
report["risk_level"] = "CRITICAL" if high_risk >= 5 else "HIGH" if high_risk >= 2 else "MEDIUM" if shadow else "LOW"
|
||||
|
||||
print(f"[*] Shadow endpoints: {len(shadow)} (HIGH risk: {high_risk})")
|
||||
|
||||
if args.output:
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
print(f"[*] Report saved to {args.output}")
|
||||
else:
|
||||
print(json.dumps(report, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user