4. API资产发现与梳理

API资产发现与梳理是API安全治理的起点。如果连系统对外暴露了哪些API、每个API由谁维护、当前版本是什么、是否仍在被调用都不清楚,后续的鉴权测试、越权检测、限流防护和风险处置都无从谈起。本节围绕四条主线展开:从代码与文档中主动发现端点、解析Swagger/OpenAPI描述文件、从真实流量中反向还原API清单,以及在此基础上识别影子API与僵尸API。

一、API端点发现的三种来源

端点发现的目标是形成一份“候选API清单”,它通常来自三个互补的来源。第一是代码与框架路由:Spring Boot的@RequestMapping、@GetMapping,Flask的@app.route,Express的router.get,以及gRPC的proto服务定义,都能静态提取出路径、方法和参数结构。第二是接口文档:Swagger/OpenAPI、RAML、Postman Collection等描述文件本身就是结构化的端点清单。第三是运行流量:网关、Nginx、Service Mesh或APM采集到的真实请求,能反映实际被访问的端点,包括未写入文档的部分。

三者之间存在明显差异:代码里有但文档没写的,往往是内部接口或调试接口;文档里有但流量中从未出现的,可能是废弃接口;流量中出现但代码和文档都没有的,则高度疑似影子API。因此端点发现不应只依赖单一来源,而应做交叉比对。

二、Swagger/OpenAPI文档解析

OpenAPI规范(Swagger 2.0与OpenAPI 3.x)以JSON或YAML描述每个端点的路径、方法、参数、请求体、响应码和安全方案。解析时重点关注以下字段:paths下的每个路径与方法组合构成一个端点;parameters中的in字段区分query、path、header、cookie参数;requestBody与schema描述数据结构;security声明该端点是否需要认证。这些信息直接决定后续测试用例的构造方式。

实际解析中常见几类问题:一是文档与实现不一致,例如文档声明GET /users/{id}需要Bearer Token,但代码实际未校验;二是servers字段配置了多个环境地址,需要区分生产与测试;三是$ref引用导致结构嵌套,需要递归展开。下面是一段使用Python解析OpenAPI并提取端点清单的示例:

import json, yaml

def load_spec(path):
    with open(path) as f:
        return yaml.safe_load(f) if path.endswith((".yaml", ".yml")) else json.load(f)

def extract_endpoints(spec):
    endpoints = []
    for path, methods in spec.get("paths", {}).items():
        for method, detail in methods.items():
            if method.lower() not in ("get", "post", "put", "delete", "patch"):
                continue
            params = [p.get("name") for p in detail.get("parameters", [])]
            auth = bool(detail.get("security", spec.get("security", [])))
            endpoints.append({
                "method": method.upper(),
                "path": path,
                "params": params,
                "auth_required": auth,
                "tags": detail.get("tags", [])
            })
    return endpoints

spec = load_spec("openapi.yaml")
for ep in extract_endpoints(spec):
    print(ep)

输出结果可直接作为资产台账的初始数据。示例中auth_required字段尤其重要,它标记了文档声称需要认证的端点,后续可与流量中实际携带凭证的比例做对比,发现“文档要求认证但实际匿名可访问”的高风险接口。

三、流量分析还原真实API

流量分析解决的是“实际发生了什么”。在网关或镜像端口采集HTTP请求后,按Host + Method + 路径模板聚合。路径模板化是关键步骤:把/api/v1/users/1024和/api/v1/users/2048归并为/api/v1/users/{id},否则会产生海量伪端点。常用做法是用正则替换数字、UUID、长哈希等动态段。

聚合后为每个端点统计:调用次数、独立来源IP数、平均响应码分布、是否携带Authorization头、请求体大小范围、最近一次调用时间。这些指标是后续识别影子与僵尸API的核心依据。例如,一个端点近90天调用次数为0,基本可判定为僵尸API;一个端点调用量很高但从未出现在任何文档中,则属于影子API。

API资产发现与交叉比对模型 代码与框架路由 @RequestMapping / route proto 服务定义 Swagger / OpenAPI paths / parameters security / servers 运行流量 网关 / Nginx / APM 真实请求与响应 路径模板化 + 端点归一化 /users/1024 → /users/{id} 交叉比对:文档 ∩ 代码 ∩ 流量 一致端点 / 仅文档 / 仅代码 / 仅流量 正常受管API 文档有·流量无 代码有·文档无 流量有·文档无

四、影子API与僵尸API识别

影子API指未纳入文档、未经过安全评审、但实际可被访问的接口。常见成因包括:开发人员为联调临时开放的调试接口、旧版本未下线但仍在路由中的接口、第三方SDK自动注册的回调地址、以及微服务间未走网关的内部接口被误暴露。识别影子API的核心判据是“流量中存在、但文档与代码清单中均不存在”,同时结合响应码为2xx、返回体包含业务数据等特征提高置信度。

僵尸API指已停止维护、文档可能仍保留、但实际已无正常业务调用的接口。它们往往运行在旧版本框架上,缺少最新安全补丁,一旦被攻击者发现即可利用。识别判据是“文档或代码中存在、但近N天流量为0”。N的取值需结合业务周期,一般取30至90天;对账务、报表类低频接口可适当放宽。

实际梳理中建议形成一张资产台账,字段包括:端点路径、方法、来源(代码/文档/流量)、认证要求、最近调用时间、调用量、归属服务、负责人、风险标签。下表给出一个简化的示例片段:

方法路径来源认证近30天调用标签
GET/api/v1/users/{id}代码+文档+流量Bearer128,430正常
POST/api/v1/orders代码+文档+流量Bearer56,210正常
GET/api/v1/debug/env仅流量无37影子API
GET/api/v0/report/export文档+代码Bearer0僵尸API
POST/internal/cache/flush仅代码无0待确认
影子API与僵尸API的判定不能只看单一指标。调用量为0的端点也可能是按季度触发的对账接口,应结合负责人确认后再标记为僵尸;仅流量中出现的端点也可能是新上线尚未更新文档,应先与研发核对再定性为影子API。

五、梳理结果的持续维护

API资产不是一次性清单,而是持续变化的对象。建议将端点发现流程接入CI/CD:每次代码合并时重新提取路由,与上一版文档和流量基线做差异比对,新增或变更的端点自动进入评审队列。同时,网关侧定期导出流量聚合结果,与资产台账做双向校验,确保“台账中的端点都在流量中有对应,流量中的端点都在台账中有归属”。只有形成这种闭环,影子API和僵尸API才不会在两次梳理之间悄悄积累。

落地时可以先从流量侧入手,用真实调用数据快速建立基线,再逐步补齐代码与文档来源。对于规模较大的系统,优先梳理对外暴露且无需认证的端点,这类接口的风险敞口最大,投入产出比最高。