关于 performing-api-inventory-and-discovery
面向安全团队的 API 盘点与发现技能,指导通过流量、主动探测、JavaScript 和云网关整理 API 攻击面,并识别未记录或已废弃的接口。仅应在获得书面授权后使用。
主要功能
- 01从流量记录提取 API 端点
- 02枚举子域名并主动探测
- 03从 JavaScript 中发现端点
- 04盘点云 API 网关
- 05比对识别影子与僵尸 API
适用场景
- 01安全评估前梳理组织 API 攻击面
- 02查找未经安全审查部署的 API
- 03发现仍可访问的废弃 API 版本
- 04为合规审计建立 API 清单
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill performing-api-inventory-and-discovery安装后在 Claude Code 中输入/performing-api-inventory-and-discovery确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
LICENSEreferences/api-reference.mdscripts/agent.py详细介绍
执行 API 清单梳理与发现
适用场景
- 在安全评估前,梳理组织的完整 API 攻击面
- 识别开发团队未经安全审查即部署的影子 API
- 发现仍可访问但已无人维护的弃用或僵尸 API 版本
- 查找通过移动应用、SPA 或微服务暴露的未记录 API 端点
- 为合规要求(PCI-DSS、SOC2、GDPR)构建 API 清单
请勿在未获得书面授权的情况下使用。API 发现涉及扫描网络基础设施并分析流量。
前提条件
- 明确指定目标域名和网段的书面授权
- 被动流量捕获能力(网络分路器、代理或云流量镜像)
- 主动扫描工具:Amass、subfinder、httpx 和 nuclei
- JavaScript 分析工具:LinkFinder、JS-Miner 或自定义解析器
- 可访问云控制台(AWS、Azure、GCP)以梳理 API 网关
- 用于被动发现 API 端点的 Burp Suite Professional
工作流
步骤 1:通过流量分析被动发现 API
import re
import json
from collections import defaultdict
# Parse HAR file from browser developer tools or proxy
def analyze_har_for_apis(har_file_path):
"""Extract API endpoints from HTTP Archive (HAR) file."""
with open(har_file_path) as f:
har = json.load(f)
api_endpoints = defaultdict(lambda: {
"methods": set(), "content_types": set(),
"auth_types": set(), "count": 0
})
for entry in har["log"]["entries"]:
url = entry["request"]["url"]
method = entry["request"]["method"]
# Identify API patterns
api_patterns = [
r'/api/', r'/v\d+/', r'/graphql', r'/rest/',
r'/ws/', r'/rpc/', r'/grpc', r'/json',
]
if any(re.search(p, url) for p in api_patterns):
# Normalize the URL (remove query params and IDs)
normalized = re.sub(r'\?.*$', '', url)
normalized = re.sub(r'/\d+(/|$)', '/{id}\\1', normalized)
normalized = re.sub(
r'/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}',
'/{uuid}', normalized)
ep = api_endpoints[normalized]
ep["methods"].add(method)
ep["count"] += 1
# Detect authentication type
for header in entry["request"]["headers"]:
name = header["name"].lower()
if name == "authorization":
if "bearer" in header["value"].lower():
ep["auth_types"].add("Bearer/JWT")
elif "basic" in header["value"].lower():
ep["auth_types"].add("Basic")
elif name == "x-api-key":
ep["auth_types"].add("API Key")
# Detect content type
content_type = next(
(h["value"] for h in entry["request"]["headers"]
if h["name"].lower() == "content-type"), None)
if content_type:
ep["content_types"].add(content_type.split(";")[0])
print(f"Discovered {len(api_endpoints)} unique API endpoints:\n")
for url, info in sorted(api_endpoints.items()):
methods = ", ".join(sorted(info["methods"]))
auth = ", ".join(info["auth_types"]) or "None"
print(f" [{methods}] {url}")
print(f" Auth: {auth} | Requests: {info['count']}")
return api_endpoints
步骤 2:主动发现 API 端点
# DNS enumeration for API subdomains
amass enum -d example.com -o amass_results.txt
subfinder -d example.com -o subfinder_results.txt
# Filter for API-related subdomains
grep -iE '(api|rest|graphql|ws|gateway|backend|internal|staging|dev|v1|v2)' \
amass_results.txt subfinder_results.txt | sort -u > api_subdomains.txt
# Check which subdomains are alive
cat api_subdomains.txt | httpx -status-code -content-length -title \
-tech-detect -o live_apis.txt
# Probe common API paths on each live subdomain
cat api_subdomains.txt | while read domain; do
for path in /api /api/v1 /api/v2 /graphql /swagger.json /openapi.json \
/api-docs /docs /health /status /metrics /actuator; do
curl -s -o /dev/null -w "%{http_code} %{url_effective}\n" \
"https://${domain}${path}" 2>/dev/null | grep -v "^404"
done
done
import requests
import concurrent.futures
def discover_api_endpoints(base_domains):
"""Actively probe for API endpoints across discovered domains."""
# Common API paths to test
API_PATHS = [
"/api", "/api/v1", "/api/v2", "/api/v3",
"/graphql", "/gql", "/query",
"/rest", "/json", "/rpc",
"/swagger.json", "/swagger/v1/swagger.json",
"/openapi.json", "/openapi.yaml", "/api-docs",
"/docs", "/redoc", "/explorer",
"/.well-known/openid-configuration",
"/health", "/healthz", "/ready",
"/status", "/info", "/version",
"/metrics", "/prometheus",
"/actuator", "/actuator/health", "/actuator/info",
"/admin", "/admin/api", "/internal",
"/debug", "/debug/vars", "/debug/pprof",
"/ws", "/websocket", "/socket.io",
"/grpc", "/twirp",
]
discovered = []
def check_endpoint(domain, path):
for scheme in ["https", "http"]:
url = f"{scheme}://{domain}{path}"
try:
resp = requests.get(url, timeout=5, allow_redirects=False,
verify=False) # TLS verification disabled for discovery; enable in production
if resp.status_code not in (404, 502, 503):
return {
"url": url,
"status": resp.status_code,
"content_type": resp.headers.get("Content-Type", ""),
"server": resp.headers.get("Server", ""),
"size": len(resp.content),
}
except requests.exceptions.RequestException:
pass
return None
with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
futures = {}
for domain in base_domains:
for path in API_PATHS:
future = executor.submit(check_endpoint, domain, path)
futures[future] = (domain, path)
for future in concurrent.futures.as_completed(futures):
result = future.result()
if result:
discovered.append(result)
print(f" [FOUND] {result['url']} -> {result['status']} ({result['content_type']})")
return discovered
步骤 3:通过 JavaScript 源码分析发现 API 端点
import re
import requests
def extract_apis_from_javascript(js_urls):
"""Extract API endpoints from JavaScript source files."""
api_pattern = re.compile(
r'''(?:['"`])((?:/api/|/v[0-9]+/|/graphql|/rest/)[^'"`\s<>{}]+)(?:['"`])''',
re.IGNORECASE
)
url_pattern = re.compile(
r'''(?:['"`])(https?://[a-zA-Z0-9._-]+(?:\.[a-zA-Z]{2,})+(?:/[^'"`\s<>{}]*)?)(?:['"`])'''
)
fetch_pattern = re.compile(
r'''(?:fetch|axios|ajax|XMLHttpRequest|\.get|\.post|\.put|\.delete|\.patch)\s*\(\s*(?:['"`])([^'"`]+)'''
)
all_endpoints = set()
for js_url in js_urls:
try:
resp = requests.get(js_url, timeout=10)
content = resp.text
# Extract relative API paths
for match in api_pattern.findall(content):
all_endpoints.add(("relative", match))
# Extract absolute URLs
for match in url_pattern.findall(content):
if any(kw in match.lower() for kw in ["/api", "/v1", "/v2", "graphql"]):
all_endpoints.add(("absolute", match))
# Extract from fetch/axios calls
for match in fetch_pattern.findall(content):
all_endpoints.add(("fetch", match))
except requests.exceptions.RequestException:
pass
print(f"\nAPI endpoints discovered from JavaScript ({len(all_endpoints)}):")
for source, endpoint in sorted(all_endpoints):
print(f" [{source}] {endpoint}")
return all_endpoints
# Find JavaScript files from the target domain
def find_js_files(domain):
"""Discover JavaScript files from a web application."""
resp = requests.get(f"https://{domain}", timeout=10)
js_files = re.findall(r'src=["\']([^"\']+\.js[^"\']*)', resp.text)
full_urls = []
for js in js_files:
if js.startswith("http"):
full_urls.append(js)
elif js.startswith("//"):
full_urls.append(f"https:{js}")
elif js.startswith("/"):
full_urls.append(f"https://{domain}{js}")
return full_urls
步骤 4:梳理云 API 网关
import boto3
def inventory_aws_apis():
"""Inventory all APIs in AWS API Gateway."""
apigw = boto3.client('apigateway')
apigwv2 = boto3.client('apigatewayv2')
apis = []
# REST APIs (API Gateway v1)
rest_apis = apigw.get_rest_apis()
for api in rest_apis['items']:
resources = apigw.get_resources(restApiId=api['id'])
stages = apigw.get_stages(restApiId=api['id'])
for stage in stages['item']:
for resource in resources['items']:
for method in resource.get('resourceMethods', {}).keys():
apis.append({
"type": "REST",
"name": api['name'],
"stage": stage['stageName'],
"path": resource['path'],
"method": method,
"url": f"https://{api['id']}.execute-api.{boto3.session.Session().region_name}.amazonaws.com/{stage['stageName']}{resource['path']}",
"created": str(api.get('createdDate', '')),
})
# HTTP APIs (API Gateway v2)
http_apis = apigwv2.get_apis()
for api in http_apis['Items']:
routes = apigwv2.get_routes(ApiId=api['ApiId'])
stages = apigwv2.get_stages(ApiId=api['ApiId'])
for route in routes['Items']:
apis.append({
"type": "HTTP",
"name": api['Name'],
"route": route['RouteKey'],
"api_id": api['ApiId'],
"protocol": api['ProtocolType'],
})
print(f"\nAWS API Inventory ({len(apis)} endpoints):")
for api in apis:
print(f" [{api['type']}] {api.get('name')} - {api.get('method', '')} {api.get('path', api.get('route', ''))}")
return apis
步骤 5:检测 API 版本与影子 API
def detect_shadow_and_zombie_apis(discovered_endpoints, documented_endpoints):
"""Compare discovered APIs against documented inventory."""
# Normalize endpoints for comparison
def normalize(ep):
ep = re.sub(r'/v\d+/', '/vX/', ep)
ep = re.sub(r'/\d+', '/{id}', ep)
return ep.lower().rstrip('/')
documented_normalized = {normalize(ep) for ep in documented_endpoints}
shadow_apis = [] # Discovered but not documented
zombie_apis = [] # Old versions still accessible
for ep in discovered_endpoints:
normalized = normalize(ep["url"])
if normalized not in documented_normalized:
# Check if it is an old version of a documented API
if re.search(r'/v[0-9]+/', ep["url"]):
zombie_apis.append(ep)
else:
shadow_apis.append(ep)
print(f"\nShadow APIs (undocumented): {len(shadow_apis)}")
for api in shadow_apis:
print(f" [SHADOW] {api['url']} -> {api['status']}")
print(f"\nZombie APIs (deprecated versions): {len(zombie_apis)}")
for api in zombie_apis:
print(f" [ZOMBIE] {api['url']} -> {api['status']}")
# Check if zombie APIs lack security controls
for api in zombie_apis:
resp = requests.get(api["url"], timeout=5)
if resp.status_code not in (401, 403):
print(f" [CRITICAL] Zombie API accessible without auth: {api['url']}")
return shadow_apis, zombie_apis
关键概念
| 术语 | 定义 |
|---|---|
| 影子 API | 由开发团队部署,但未经过官方 API 管理或安全审查流程的 API |
| 僵尸 API | 已弃用或旧版本的 API,仍可访问并运行,但不再维护或监控 |
| API 清单 | 组织内所有 API 的完整目录,包括端点 URL、负责人、版本、认证方式和数据分类 |
| 清单管理不当 | OWASP API9:2023 —— 未能维护准确的 API 清单,导致 API 端点失去监控和保护 |
| 攻击面 | 攻击者可能与之交互的全部 API 端点、方法和参数集合 |
| API 泛滥 | 组织内 API 的无序增长,通常源于采用微服务但缺乏集中治理 |
工具与系统
- Amass:OWASP 工具,通过 DNS 枚举、网页抓取和 API 发现来梳理攻击面
- httpx:快速 HTTP 探测工具,用于验证已发现的域名并识别存活的 API 端点
- nuclei:基于模板的扫描器,用于检测暴露的 API 文档、调试端点和配置错误的服务
- Swagger UI Detector:用于在整个组织内查找暴露的 Swagger/OpenAPI 文档端点的工具
- Akto:API 安全平台,通过流量分析发现 API 并维护自动化清单
常见场景
场景:企业 API 攻击面评估
背景:一家大型企业有 200+ 个开发团队使用微服务。安全团队怀疑许多未记录的 API 暴露在互联网上。需要一份全面的 API 清单用于安全审计。
方法:
- DNS 枚举发现 340 个子域,其中 45 个包含 API 相关关键词(api、rest、gateway、backend)
- 使用 API 路径字典对所有子域进行主动探测,发现 127 个活跃 API 端点
- 对主 Web 应用进行 JavaScript 分析,发现 34 个 API 端点,其中 8 个指向未记录的内部服务
- AWS API Gateway 清单显示 12 个账户中共有 67 个 REST API 和 23 个 HTTP API
- 与官方 API 目录交叉比对:31 个影子 API(未记录),14 个僵尸 API(已弃用版本)
- 3 个僵尸 API 没有身份验证,通过本应停用的端点暴露客户数据
- 2 个影子 API 未经授权将内部管理功能暴露到互联网
陷阱:
- 只检查已记录的 API 端点,遗漏部署在 API 网关之外的影子 API
- 不扫描前端应用硬编码 API 端点 URL 的 JavaScript 包
- 遗漏位于非标准端口或子路径后面的 API
- 不检查多个 API 版本,旧版本可能缺少安全控制
- 假设所有 API 都经过 API 网关,而有些可能直接暴露
输出格式
## API Inventory and Discovery Report
**Organization**: Example Corp
**Assessment Date**: 2024-12-15
**Domains Scanned**: 340
### Summary
| Category | Count |
|----------|-------|
| Total APIs Discovered | 127 |
| Documented APIs | 82 |
| Shadow APIs (undocumented) | 31 |
| Zombie APIs (deprecated) | 14 |
| APIs Without Authentication | 8 |
| APIs Exposing Sensitive Data | 5 |
### Critical Findings
1. **Zombie API**: api-v1.example.com/api/v1/users - Deprecated in 2022,
still accessible, no authentication required, returns full user data
2. **Shadow API**: internal-tools.example.com/api/admin - Admin functions
exposed to internet without authorization
3. **Exposed Documentation**: 12 Swagger UI instances accessible publicly,
revealing full API schema and endpoint details
评论
支持 Markdown