对象级授权缺失BOLA详解

5 人参与

说起对象级授权缺失(BOLA)在OWASP API Security Top 10里能排到第一,很多人第一反应是“这玩意儿到底有多罕见?”——但实际上,它几乎天天出现在渗透测试报告里。原因很简单:大多数系统把精力全堆在登录校验和防注入上,反而忘了最基本的一步——这个用户到底有没有权利动这个对象。

为什么BOLA如此普遍?

说到底,API设计天然容易踩这个坑。RESTful风格接口里,对象ID通常直接放在URL路径里,比如 /api/users/123/api/orders/456。开发时默认“已经登录了是合法用户”,却忽略了“合法用户不一定有权限访问这个id对应的资源”。测试环境里大家用同一个账号,自然不会暴露问题;一旦上线,攻击者随便换个ID就能越权。

更隐蔽的是,有些团队把“前端隐藏按钮”当成授权手段:用户A没看见“删除”按钮,但后端那个DELETE /api/items/789接口仍然公开可用。只要有人知道路径,照样可以调用。这就像把钥匙藏在门前地毯下——防君子不防小人。

你真的了解“已登录”和“已授权”的鸿沟吗?

很多开发者常见的误区在于,觉得“用了JWT或Session就安全了”。JWT只是身份凭证,不是权限地图。举个例子:一个电商后台管理系统,运营人员A和运营人员B都是同一角色,但A只能管理华北区订单,B只能管理华南区订单。如果接口只是校验“是不是运营角色”,而不校验“这个订单是否属于该运营负责的区域”,那就是典型的BOLA。即使ID换成了UUID,只要授权判断缺失,攻击者一样可以枚举或猜测。

修复BOLA的真正关键点

很多人第一个想法是“把自增ID换成UUID”,这确实能提高枚举成本,但解决不了根本问题。真正的核心在于:每次访问资源时,都必须基于当前用户的身份、角色、所属租户、资源归属关系做明确校验。这需要一套可靠的更科法是把授权逻辑从业务代码里抽离,放进统一的策略引擎或中间件里。比如,在查询订单前,先调用一个“是否允许访问”的检查:当前用户ID是否为订单的owner?是否属于订单所在组织的管理员?是否拥有“跨租户查看”权限?

另外,必须小心“客户端传参”陷阱。不少接口允许客户端传递 user_idcompany_id 来指定操作对象,服务端直接信任这些参数。正确的做法是从会话上下文(Token载荷、Session数据)中推导出当前用户,然后强制用这个值去校验资源归属,而不是依赖客户端传过来的“用户自报家门”。

工程化落地:如何让BOLA不再漏网

真正有效的做法是把“对象级授权”变成开发默认动作,而不是事后补丁。几点实操建议:

  • 建立统一的资源归属模型:每个资源表都记录 owner_idtenant_idorg_id,查询时自动追加过滤条件。
  • 所有涉及对象ID的接口,都强制经过一个授权检查函数级权限检查。可以把检查逻辑封装成注解或装饰器,比如 @RequireAccess(resourceType="Order", action="read")
  • 测试阶段必须走真实的跨账号测试:A用户能访问B用户订单吗?C用户能修改D用户的资料吗?这些用例要写进自动化回归测试。
  • 日志审计也得跟上。BOLA攻击往往伴随着ID枚举,如果后台能记录下“同一IP在短时段内访问了连续100个订单ID”的行为,就能提前告警。

最后提醒一句

别以为BOLA只存在于传统Web应用里。微服务架构下,服务间调用同样容易出这个问题:两个服务共享一个用户ID,但内部调用可能跳过租户校验。GraphQL接口允许客户端选择字段,也可能变成BOLA的变种——攻击者通过查询不同对象ID的__typename来探测资源是否存在。安全不是一劳永逸的配置,而是持续的工程习惯。

参与讨论

5 条评论
  • 山海行者

    又是权限的锅,开发天天背锅

    回复
  • 末世诗人

    说得对,UUID真不是万能药

    回复
  • 山的冥想

    JWT里加个role字段不就完了?为啥还要单独搞个策略引擎?

    回复
  • 山月照

    还有那种前端传user_id的,简直作死

    回复
  • 瀚海驼影

    但实际项目里全量校验性能扛不住吧?

    回复