越权漏洞修复
越权漏洞修复
一个订单查询接口,越权URL 里带着订单 ID。漏洞用户 A 登录后能查自己的修复订单,把 ID 改成 B 的越权,居然也能查到。漏洞这就是修复最典型的越权——水平越权。代码里只校验了“有没有登录”,越权没校验“这个订单是漏洞不是你的”。
越权分两类。修复水平越权是越权同级用户之间互相能看到对方数据,像上面那个例子。漏洞垂直越权是修复低权限用户干了高权限的事,比如普通用户直接访问了 /admin/deleteUser?越权id=5,后台没做角色判断,漏洞人就删了。修复很多人以为前端藏了菜单按钮就安全了,其实抓包改请求照样打进去。前端只负责好看,后端才负责安全,这个边界搞混了,漏洞就来了。
修复的核心思路只有一句话:每一次数据访问,都要在服务端确认“当前用户”和“目标资源”的归属关系。 听起来简单,实际落地时到处是坑。
拿水平越权来说,常见做法是在 SQL 查询里直接带上当前用户的 ID。比如查订单,不要写 SELECT * FROM orders WHERE id = ?,而是写 SELECT * FROM orders WHERE id = ? AND user_id = ?。第二个参数从会话里取,不从请求里取。这样即使攻击者改了 URL 里的 ID,数据库层面也查不出别人的数据。有人喜欢先查出来再在代码里 if (order.userId != currentUserId) return 403;,也能用,但多一次查询,而且容易漏。我倾向于把权限条件直接压进查询语句,少一层判断就少一个遗忘的机会。
垂直越权更麻烦一点。它往往不是单个接口的问题,而是一组接口的权限模型没设计好。有些系统用 role 字段做判断,if (user.role == "admin") 散落在几十个地方。改天加了个新角色,或者 role 值写错了,某个接口就漏了。更好的做法是把权限抽成独立的校验层,比如用注解或者中间件统一拦截。Spring Security 里用 @PreAuthorize("hasRole('ADMIN')"),或者自己写个拦截器,在进入业务逻辑之前就把权限判掉。这样新增接口时只要加注解就行,不用在每个方法里重复写 if。
还有一种容易被忽略的情况:越权不一定发生在“读”上,“写”和“删”同样危险。曾经见过一个系统,查询接口做了用户隔离,但删除接口只传了记录 ID,没校验归属。结果攻击者遍历 ID 就能删掉别人的数据。修复时要把所有涉及资源操作的方法都过一遍,不能只盯着查询。
参数篡改也是越权的一个变种。比如请求体里有个 userId 字段,后端直接拿它去查数据,而不是从 token 里解析。攻击者把 userId 改成别人的,数据就泄露了。原则是:凡是能从会话里推导出来的身份信息,绝对不要从请求参数里取。 请求参数只用来传业务数据,不传身份。
修复完之后怎么验证?别只点几下页面就完事。用两个不同权限的账号,抓包对比请求,手动改 ID、改角色参数、改路径,看后端返回什么。有条件的话写自动化测试,把越权场景固化成用例,每次上线前跑一遍。人工测容易漏,尤其是接口多的时候。
最后说个现实问题:越权漏洞修复往往不是技术难,而是梳理难。一个跑了三年的系统,接口几百个,谁写的都有,权限逻辑东一块西一块。这时候只能一个个接口过,画一张资源归属表,标清楚每个接口应该校验什么。慢,但绕不过去。安全这事没有捷径,漏掉一个接口,整个系统就等于没设防。
