越权访问的本质不是黑客用了多复杂的技术,而是权限校验逻辑在关键节点上缺位。很多系统在用户登录后只验证了“你是谁”,却没有在每一个数据操作入口持续验证“你有没有资格做这件事”。攻击者一旦拿到一个低权限账号,就能通过篡改资源ID、遍历接口参数、替换角色标识等方式,读取或修改本不属于自己的数据。要规避这类风险,不能只靠防火墙或WAF,必须在应用架构层面建立一套完整的权限管控模型。

先分清越权的两种基本形态

水平越权发生在同级别用户之间。用户A通过修改请求中的订单号、用户ID或文件路径,访问到了用户B的数据。典型场景是查询接口直接接收前端传来的主键,后端不做归属校验。垂直越权则是低权限用户调用了高权限功能,比如普通会员访问了管理员删除接口,或者通过修改角色字段把自己提升为管理员。两种越权经常混合出现,一个接口既可能存在水平越权,也可能因为角色判断缺失而同时存在垂直越权。

权限校验必须放在服务端且靠近数据层

前端隐藏按钮、禁用菜单、路由守卫都只是体验优化,不是安全控制。攻击者可以直接构造HTTP请求绕过所有前端限制。真正有效的校验必须落在服务端,而且不能只放在网关层做粗粒度判断。网关可以拦截未登录请求,但很难判断“当前用户是否能操作这笔订单”。权限校验逻辑应该紧贴数据访问层,在查询或变更数据之前,用当前会话的用户身份与目标资源进行归属比对或角色能力比对。

资源归属校验要成为数据访问的强制步骤

所有包含资源标识的接口,例如/user/info?id=123、/order/detail?orderNo=20240101001,后端必须校验该资源是否属于当前登录用户。不能信任前端传来的任何ID,也不能因为前端页面只展示当前用户自己的ID就认为安全。正确的做法是从会话中取出用户身份,再用这个身份去查询资源归属。以订单查询为例,SQL条件应当同时包含订单号和用户ID,而不是只按订单号查询后再判断。

-- 不安全的查询:仅按订单号查询
SELECT * FROM orders WHERE order_no = ?;

-- 安全的查询:同时校验订单归属
SELECT * FROM orders WHERE order_no = ? AND user_id = ?;

这种写法让数据库在查询阶段就完成了水平越权拦截。如果攻击者篡改了订单号,查询结果为空,不会泄露任何数据。对于列表类接口,同样要在WHERE条件中强制带上当前用户ID,而不是先查出全部数据再在内存中过滤。

垂直权限控制要用角色与权限点分离的模型

简单的角色判断如“if user.role == 'admin'”在小型系统中勉强可用,但一旦角色增多、权限交叉,硬编码判断就会失控。更可靠的做法是建立“用户-角色-权限点”三层模型。每个功能操作对应一个权限点,例如“订单删除”“用户导出”“角色修改”。角色是权限点的集合,用户再关联一个或多个角色。后端在每个需要控制的功能入口,校验当前用户是否拥有对应权限点,而不是判断他属于哪个角色。

这种模型的好处是权限变更灵活,新增角色或调整权限时不需要修改业务代码。同时要避免把权限点定义得太粗,比如只设置“管理员”和“普通用户”两个角色,这样仍然无法精细控制管理员内部的不同操作范围。权限点应该细到具体操作级别,甚至数据范围级别。

接口设计要避免暴露可预测的资源标识

自增ID虽然方便,但很容易被遍历。攻击者只需要把id=100改成id=101,就能尝试访问下一条记录。对于敏感资源,建议使用不可预测的标识符,例如UUID或带随机成分的业务编号。如果必须使用数字ID,则必须配合严格的归属校验,并且对访问频率做限制。仅仅依靠ID不可预测是不够的,因为ID仍然可能通过日志、分享链接、浏览器历史等渠道泄露,归属校验才是根本防线。

批量操作接口是越权重灾区

批量删除、批量导出、批量修改这类接口往往接收一个ID数组,后端容易只校验数组非空就直接执行。攻击者可以在数组中混入不属于自己的资源ID,从而批量操作他人数据。处理批量请求时,必须逐个校验每个ID的归属,或者先查出当前用户有权限操作的所有ID集合,再与请求中的ID集合取交集,只对交集部分执行操作。对于交集为空的情况,应当返回明确错误,而不是静默成功。

文件下载与静态资源访问同样需要鉴权

很多系统把用户上传的文件存放在公开目录,通过URL直接访问。攻击者只要猜到或枚举文件路径,就能下载他人的合同、身份证照片、报表等敏感文件。文件访问必须经过鉴权接口,由后端校验当前用户对该文件的访问权限后,再以流的形式返回文件内容。不要把文件路径直接暴露给前端,也不要把文件存放在Web容器可直接访问的静态目录下。如果使用对象存储,应使用私有桶加临时签名URL的方式,签名URL有效期要短,并且签名生成前必须完成权限校验。

权限变更要即时生效并清理会话缓存

用户被降权、角色被移除、账号被禁用后,如果服务端仍然信任之前签发的会话或令牌,越权风险就会持续存在。权限校验不能只在登录时做一次,而应该在每次请求时都从可信数据源获取当前用户的权限状态。对于使用JWT等无状态令牌的系统,可以在令牌中设置较短的过期时间,并在关键操作时强制查询数据库确认用户状态。对于有状态会话,权限变更时要主动使相关会话失效。缓存权限信息时,缓存时间要短,并且权限变更时要主动清除缓存。

敏感操作要增加二次验证与审计日志

对于删除数据、修改支付信息、导出全量用户、变更角色等高风险操作,即使权限校验通过,也建议增加二次身份验证,例如要求重新输入密码或验证码。这可以降低因账号被盗或会话劫持导致的越权操作风险。同时,所有敏感操作都要记录详细审计日志,包括操作人、操作时间、目标资源、操作前后值、来源IP、用户代理等。日志本身也要做访问控制,防止攻击者通过日志获取更多资源标识或敏感信息。

开发流程中要把权限测试纳入标准环节

越权漏洞往往不是设计缺陷,而是开发过程中遗漏了某个接口的校验。要避免这种情况,需要在开发规范中明确:凡是涉及资源ID的接口,必须写明归属校验逻辑;凡是涉及角色或权限的功能,必须接入权限点校验。代码评审时,权限校验是必查项。测试阶段要专门编写越权测试用例,覆盖水平越权和垂直越权场景,包括跨用户访问、跨角色调用、批量操作混入他人ID、篡改角色字段、直接调用未授权接口等。自动化安全测试工具可以辅助发现部分问题,但不能替代人工对业务逻辑权限的审查。

权限模型要随着业务演进持续治理

系统上线后,权限管控不是一劳永逸的。新功能上线可能引入新的权限点,旧功能的权限点可能因为业务调整而失效或合并。建议定期进行权限梳理,检查是否存在权限点定义过粗、角色分配过多、离职员工账号未及时回收、临时权限未过期等问题。对于长期未使用的权限点,应当及时下线。对于高权限账号,要遵循最小权限原则,只授予完成工作所必需的最小权限集合。定期审计高权限账号的实际使用情况,发现异常访问模式及时处置。

越权访问的防护核心在于:不信任前端传来的任何身份与资源标识,所有权限判断都在服务端靠近数据层完成,资源归属校验与操作权限校验双管齐下,并且把权限测试嵌入开发流程。做到这些,才能把越权风险控制在可接受的范围内。