2. API基础与攻击面分析
API安全测试的第一步不是直接上扫描器,而是先弄清楚“被测对象到底是什么”。同样是HTTP接口,REST、GraphQL、gRPC、WebSocket在协议、数据格式、调用方式和信任模型上差异很大,攻击面也完全不同。如果测试人员把GraphQL当成REST来测,很可能只覆盖了少量查询入口,却漏掉了内省、批量查询、字段级越权等关键风险。
四类主流API的协议特征与攻击面差异
REST以资源为中心,使用HTTP方法(GET/POST/PUT/PATCH/DELETE)表达操作语义,通常以JSON传输数据,通过URL路径和查询参数定位资源。它的攻击面集中在:越权访问(IDOR)、批量赋值、HTTP方法覆盖、参数污染、版本回退(如/v1/仍可访问)、以及错误信息泄露。测试时应重点关注资源ID的可枚举性和方法级权限校验是否一致。
GraphQL只有一个入口端点(通常是/graphql),由客户端提交查询文档,服务端按字段解析。它的攻击面与REST截然不同:内省查询(__schema)可能暴露完整数据模型;嵌套查询和别名可以构造高复杂度请求造成资源耗尽;字段级授权若只在查询根节点校验,深层字段可能被越权读取;批量操作(如多个mutation)可能绕过单次请求限制。测试重点是查询深度、复杂度限制、字段级权限和内省是否在生产环境关闭。
gRPC基于HTTP/2和Protocol Buffers,使用.proto定义服务契约,支持四种调用模式:一元、服务端流、客户端流、双向流。它的攻击面包括:反射服务(Server Reflection)暴露接口定义、消息字段的默认值与可选性导致校验遗漏、流式调用中的资源控制、以及HTTP/2特有的多路复用和头部压缩带来的请求走私风险。测试时需要关注proto文件是否可获取、流式接口是否有速率和大小限制。
WebSocket通过一次HTTP升级握手建立全双工长连接,之后的消息不再遵循请求-响应模式。它的攻击面在于:握手阶段的认证是否可靠、连接建立后是否有消息级授权、跨站WebSocket劫持(CSWSH)、消息频率和大小限制、以及连接生命周期管理。测试重点是握手时的Origin校验、消息级权限、以及断线重连是否会导致会话状态异常。
API生命周期与攻击面迁移
API不是静态存在的,它经历设计、开发、测试、发布、运行、迭代、下线七个阶段。攻击面会随生命周期迁移:设计阶段若未定义权限模型,开发阶段就可能把鉴权逻辑散落在各接口中;测试阶段若只测功能不测安全,发布后就会暴露未授权访问;运行阶段若缺少版本管理和废弃策略,旧版本接口可能成为绕过新安全控制的“后门”。
实际工作中,建议建立一张API资产台账,至少记录:端点路径、协议类型、认证方式、数据敏感级别、负责人、当前版本、是否对外网开放、最近一次安全测试时间。这张台账是攻击面识别的起点,也是后续风险治理的依据。
常见API架构与信任边界
API架构决定了信任边界在哪里。常见的三种模式:
- 直连模式:客户端直接调用后端服务。信任边界在客户端与API网关之间,网关负责认证、限流、日志。测试重点是网关规则是否可绕过(如直接访问后端IP)。
- BFF模式:为不同前端(Web/App/小程序)提供专属后端。信任边界在BFF层,BFF可能聚合多个下游服务。测试重点是BFF是否对下游服务做了权限透传,还是简单转发导致越权。
- 微服务网关模式:API网关统一入口,内部服务通过服务网格通信。信任边界在网关和网格边车(Sidecar)之间。测试重点是服务间认证(mTLS)、网格策略是否覆盖所有服务、以及内部服务是否意外暴露。
攻击面识别实操:从文档到验证
以某电商平台的REST API为例,演示攻击面识别的关键动作:
# 1. 获取API文档(示例)
curl -s https://api.example.com/swagger.json | jq '.paths | keys[]'
# 输出示例:
# /api/v1/users/{id}
# /api/v1/orders
# /api/v1/orders/{id}/cancel
# /api/v1/admin/users
# 2. 检查未鉴权端点(示例)
curl -s -o /dev/null -w "%{http_code}" https://api.example.com/api/v1/users/1001
# 若返回 200 且包含用户数据,说明存在未授权访问
# 3. 检查方法覆盖(示例)
curl -s -X PUT https://api.example.com/api/v1/users/1001 -d '{"role":"admin"}'
# 若返回 200,说明存在批量赋值或方法级权限缺失
# 4. GraphQL内省检查(示例)
curl -s -X POST https://api.example.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{__schema{types{name}}}"}'
# 若返回完整类型列表,说明内省未关闭
上述命令中的域名、路径和返回码均为教学示例,实际测试需替换为目标环境并确保获得授权。判断依据是:未鉴权端点返回敏感数据、方法覆盖成功修改了不应修改的字段、内省返回了完整数据模型,这三类结果都意味着攻击面已经暴露。
攻击面清单模板
完成识别后,建议输出一份可执行的攻击面清单,字段如下:
| 字段 | 说明 | 示例 |
|---|---|---|
| 端点 | 完整路径与方法 | GET /api/v1/users/{id} |
| 协议 | REST/GraphQL/gRPC/WebSocket | REST |
| 认证方式 | JWT/Session/API Key/无 | JWT |
| 权限要求 | 角色或权限标识 | user:read |
| 数据敏感级 | 公开/内部/机密 | 机密 |
| 已识别风险 | 风险类型与验证结果 | IDOR(已验证) |
| 风险等级 | 高/中/低 | 高 |
| 修复建议 | 具体可执行措施 | 增加资源归属校验 |
这张清单不是一次性文档,而应随API迭代持续更新。每次新增端点、修改权限、升级版本时,都应在清单中同步记录,并在发布前完成对应的安全验证。攻击面识别不是“测一次就结束”的任务,而是贯穿API生命周期的持续活动。