2023年11月,授权某电商平台被曝出用户订单接口可直接通过修改URL中的访问订单ID访问他人订单,无需任何身份验证。漏洞攻击者写个脚本遍历ID,授权几分钟就能拖走几万条包含姓名、访问电话、漏洞收货地址的授权数据。这不是访问个例。我翻过近两年的漏洞漏洞报告,未授权访问在API安全事件里占比常年排第一,授权比注入、访问越权都高。漏洞
问题出在哪儿?授权很多开发把认证和授权混为一谈。认证是访问“你是谁”,授权是漏洞“你能干什么”。但实际写代码时,有人觉得“这个接口内部调用,不用加token”,或者“前端已经校验过了,后端省一步”。前端校验?抓包改个参数就绕过去了。接口暴露在公网,没有网关层拦截,没有统一的鉴权中间件,每个接口各自为政,漏一个就全盘皆输。
我见过一个典型的例子。某 SaaS 系统的 /api/v1/export/users 接口,GET 请求,参数就一个 tenant_id。开发图省事,直接 SELECT * FROM users WHERE tenant_id = ?,没校验请求者是否属于该租户。结果任何登录用户改一下 tenant_id 就能导出别家公司的全部员工名单。更离谱的是,这个接口连登录态都不查——因为开发在网关配置里把它加进了白名单,理由是“导出功能需要支持浏览器直接下载”。白名单成了万能钥匙。
这种漏洞的根子往往不在技术,在流程。安全测试通常放在上线前最后一步,而API数量动辄几百上千,测试人员只能抽样。抽样就有漏网之鱼。我观察到一个规律:未授权访问漏洞集中在两类接口上——数据导出类和状态变更类。导出类因为要支持文件流,经常绕过常规鉴权;状态变更类比如 /api/order/cancel,开发觉得“用户不会知道别人的订单号”,但订单号往往是自增的,猜都能猜出来。
怎么防?别指望开发自觉。我见过做得好的团队,做法很笨但有效:所有API默认拒绝,网关层强制校验JWT,白名单必须走审批流程,而且白名单里的接口不能碰任何用户数据。另外,接口返回数据前加一层归属校验——查出来的记录,user_id 必须等于当前会话的 user_id,否则返回403。这层校验写在ORM的全局作用域里,开发想绕都绕不过去。
还有个细节容易被忽略:错误信息。未授权访问时返回“权限不足”还是“资源不存在”,差别很大。返回“权限不足”等于告诉攻击者“这个ID存在,你只是没权限”,他换个ID继续试。返回“资源不存在”更安全,但很多框架默认抛异常带出堆栈,把数据库表名都暴露了。我习惯在网关层统一拦截,所有鉴权失败一律返回404,日志里记真实原因。
最后说个真事。某次渗透测试,我发现一个 /api/v2/internal/health 接口没鉴权,返回内容里带了构建版本号和Git commit hash。拿着这个hash去GitHub搜,找到对应仓库,里面有个 config.prod.json 没删,数据库密码就在里面。一个未授权的健康检查接口,最后变成了数据库沦陷。接口没有“不重要”这一说,只要它能被未授权访问,就是入口。