网站越权访问漏洞,说白了就是权限校验逻辑没写好,导致用户A能看用户B的数据,或者普通用户能干管理员的事。这几乎是Web应用里最常见也最致命的安全问题之一。排查和修复不能光靠扫描器扫一遍就完事,必须深入业务逻辑,从代码层面和架构层面双管齐下。
搞清楚漏洞到底长什么样很多人一上来就找工具扫,但如果你连漏洞的表现形式都不清楚,扫出来的结果你也看不懂,更别提验证误报。越权主要分三种:水平越权、垂直越权、以及上下文越权。水平越权就是同级别用户之间互相操作,比如张三通过修改URL里的ID参数,直接看到了李四的订单详情。垂直越权是低权限用户执行了高权限操作,比如一个普通注册用户,直接访问后台管理接口去创建管理员账号。上下文越权比较隐蔽,指的是用户在当前业务流程中跳过了某些必须步骤,直接执行了后续操作,比如未支付就能下载付费资源。
排查的第一步:梳理全量API接口和敏感操作点别急着上扫描器,先做资产和接口梳理。没有这份清单,后续的测试就是盲人摸象。你需要把系统里所有对外暴露的API端点、表单提交地址、功能入口全部列出来。重点关注那些包含唯一标识符的请求,比如/user/info?id=123、/order/detail?orderId=456、/api/deleteFile?fileId=789。把这些接口按业务模块分类,标记出哪些是读操作,哪些是写操作,哪些涉及敏感数据(手机号、身份证、资金)。同时,把系统的角色权限矩阵画出来,普通用户、VIP用户、管理员、超级管理员各自能访问哪些接口,一目了然。
实战测试手法:用低权限账号去触碰高权限资源梳理完接口,真正的排查才刚开始。你需要准备至少两个不同权限级别的测试账号。对于水平越权测试,用A账号登录,正常访问自己的资源,抓包拿到请求格式,然后把其中的资源ID替换成B账号的对应ID,重放请求。如果返回了B账号的数据,那就是典型的水平越权。注意,这里不能只看响应体里有没有数据,还要看响应状态码。有些开发只在前端做了跳转,后端接口照样返回数据,只是前端不展示而已,这种危害同样巨大。
垂直越权测试更直接,用低权限账号的会话凭证(Cookie或Token),直接去请求高权限的接口。比如普通用户拿着自己的Token去调/admin/createUser接口。如果后端只验证了Token是否有效,而没有验证该Token对应的用户角色是否有权限执行此操作,那这个接口就存在垂直越权漏洞。
容易被忽略的上下文越权与业务逻辑漏洞这类漏洞扫描器基本抓瞎,全靠人工理解业务。举个例子,一个电商网站的下单流程是:选商品→填地址→确认订单→支付。如果用户直接通过URL跳过了“确认订单”步骤,直接请求支付接口,并且系统没有校验前置步骤是否完成,就可能造成价格篡改或订单信息不一致。测试这类漏洞,你需要像普通用户一样完整走一遍流程,记录每一步的请求参数和响应内容,然后尝试跳过中间步骤,直接调用后续接口。重点观察后端是否对业务状态进行了严格校验。
代码层面的根因分析与修复原则排查出漏洞只是第一步,修复不当等于白干,甚至引入新问题。越权漏洞的根因几乎都是“信任客户端输入”和“缺少服务端权限校验”。很多开发习惯从客户端传入当前用户ID,然后直接用这个ID去数据库查数据。攻击者改掉这个ID,就能看别人的信息。正确的做法是:当前登录用户的身份标识必须从服务端会话(Session或已验证的JWT Token)中获取,严禁从客户端请求参数(GET、POST、Cookie)中直接取用户ID用于数据查询。
修复垂直越权,核心在于建立统一的权限拦截层。不要在几百个Controller的方法里各自写权限判断,那样迟早会漏。应该用中间件或拦截器(Interceptor/AOP)对所有请求进行前置拦截,根据请求的URL模式和请求方法,结合当前用户的角色,在业务逻辑执行前就完成权限校验。权限配置最好集中管理,比如存在数据库或配置中心,方便动态调整。
具体代码修复示例下面是一个典型的错误写法,直接从请求参数中获取用户ID去查询数据:
// 错误示例:信任了客户端传入的userId
@RequestMapping("/api/userInfo")
public User getUserInfo(String userId) {
// 直接使用客户端传入的userId查询,存在水平越权风险
return userService.getUserById(userId);
}
修复后的代码应该从安全上下文中获取当前登录用户的ID:
// 正确示例:从认证信息中获取当前用户ID
@RequestMapping("/api/userInfo")
public User getUserInfo() {
// 从Spring Security或自定义的上下文中获取当前登录用户ID
String currentUserId = SecurityContextHolder.getContext().getAuthentication().getName();
return userService.getUserById(currentUserId);
}
对于需要传入资源ID的接口,比如查看某条订单详情,必须校验该资源是否属于当前用户:
// 正确示例:校验资源归属
@RequestMapping("/api/order/detail")
public Order getOrderDetail(String orderId) {
String currentUserId = SecurityContextHolder.getContext().getAuthentication().getName();
Order order = orderService.getOrderById(orderId);
if (order == null || !order.getUserId().equals(currentUserId)) {
throw new ForbiddenException("无权访问该订单");
}
return order;
}
对于垂直越权,典型的修复方式是在拦截器或过滤器中进行角色判断:
// 权限拦截器伪代码
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String requestUri = request.getRequestURI();
String userRole = getCurrentUserRole(); // 从会话或Token中获取角色
if (requestUri.startsWith("/admin/") && !"ADMIN".equals(userRole)) {
response.sendError(403, "无权限访问");
return false;
}
return true;
}
}
深入排查:接口参数篡改与批量数据泄露
除了改ID这种简单操作,还要关注接口里的其他参数。比如一些导出功能,请求参数里可能包含一个部门ID列表,攻击者可以把它改成所有部门的ID,一次性导出全公司数据。还有的接口接收一个JSON对象,里面有个isAdmin字段,后端如果没有过滤,直接把这个字段写进数据库,攻击者就能把自己提权成管理员。排查时,要把每个接口的入参都当成不可信数据处理,尝试增、删、改参数名和参数值,观察响应变化。
另外,注意批量操作接口。比如批量删除、批量查询,请求体里传一个ID数组。攻击者可以遍历或猜测ID,批量获取或删除他人数据。这类接口除了校验每个ID的归属外,还应该做频率限制和总量控制。
工具辅助与自动化排查策略人工排查虽然细致,但效率有限。可以结合半自动化手段提高覆盖率。用Burp Suite的Intruder模块,把资源ID参数标记为变量,加载一份测试用的ID字典,快速重放请求,通过比较响应长度和内容来判断是否存在越权。更进阶的做法是编写自动化脚本,利用两个账号的会话,分别请求相同的资源列表,然后对比两个账号返回数据的差异。如果A账号的请求能拿到B账号的数据,脚本自动标记为疑似漏洞。
在持续集成流程里,可以加入权限测试用例。每次发布前,用自动化脚本跑一遍核心业务接口的越权测试,确保新代码没有破坏原有的权限控制逻辑。这部分测试用例需要持续维护,业务变更时同步更新。
防御纵深:从架构层面降低越权风险代码修复是最后一道防线,架构设计上可以做更多。推荐使用UUID作为资源主键,而不是自增数字ID。自增ID极易被遍历,UUID则极大增加了猜测难度。但这只是增加了攻击成本,不能替代权限校验。另外,敏感操作(修改密码、绑定手机、转账)应该加入二次认证,比如输入短信验证码或交易密码。即使存在越权漏洞,攻击者无法完成二次认证,也能降低实际危害。
数据库层面也可以做一层防护。对于特别敏感的数据表,可以设计成行级权限控制,但这对业务侵入性较大,需要权衡。更实际的做法是做好日志记录和监控告警。记录所有涉及敏感数据的访问日志,包含操作人、时间、IP、请求参数。设置告警规则,比如某个用户在短时间内尝试访问大量不同ID的资源,立刻触发告警,这很可能是在进行越权遍历攻击。
长期运营:建立安全开发规范与意识排查和修复一轮漏洞只是治标,要从根本上减少越权漏洞的产生,必须把安全要求嵌入到开发流程里。制定明确的编码规范:所有数据查询必须基于服务端会话中的用户ID进行过滤;所有管理接口必须声明角色要求;所有涉及资源归属的接口必须显式校验所有权。代码评审时,把权限校验作为必查项。定期对开发团队进行安全培训,用真实的越权案例演示危害,让每个人都清楚,一行缺少权限判断的代码可能导致整个数据库泄露。
越权漏洞的排查和防护没有一劳永逸的办法,它是一个持续对抗的过程。业务逻辑越复杂,权限模型越细化,出错的概率就越大。只有把安全意识落实到每一次需求评审、每一行代码编写、每一次测试发布中,才能真正把风险控制在可接受范围内。
