权限划分过粗,系统只判断“你是谁”,却没判断“你能动谁的数据”。换句话说,只要登录了,就能访问不该访问的资源,这就是越权。
为什么权限划分过粗是越权访问的温床
业内专家指出,越权漏洞之所以长期霸榜OWASP Top 10,不是因为它多难防,而是因为它藏在业务逻辑里,常规扫描器扫不出来,权限划分过粗主要体现为三种形态。
只做了“是否登录”的判断
这是最典型的粗放式授权,后端接口只校验session或token是否存在,不校验当前用户有没有权限操作目标资源,比如一个订单删除接口,登录用户把URL里的order_id从100改成101,就可能删掉别人的订单。
这种设计的思路是:能登录就是自己人,但真实业务里,用户之间是有边界的,你删了别人的订单,系统只知道“有人删了订单”,并不知道“这个人没权限删这个订单”。
按角色粗分组,组内人人平等
很多系统的权限模型是:管理员、运营、普通用户三级,看似有区分,但角色内部权限粒度极粗,比如运营角色统一拥有“编辑所有文章”的权限,但某位运营只负责科技频道,却也能改财经频道的内容。
这种按角色划分的方式比“是否登录”进了一步,但依然粗,角色和资源之间缺乏明确的映射关系,导致权限在角色内部横向越权。
前端隐藏按钮,后端不设防
还有一种常见情况:前端根据角色判断是否显示某个按钮,后端却没有响应拦截,比如普通用户在前端看不到“删除所有人”的按钮,但如果直接构造HTTP请求调用该接口,后端照样执行。
这是把安全决策交给了前端,等于把房子的钥匙挂在门口,只拉上了窗帘。
越权访问和水平越权垂直越权的区别
想理解越权访问,必须先分清两个子类。水平越权是同级用户之间互相访问对方数据,垂直越权是低权限用户访问高权限功能,二者的危害和检测思路完全不同。
| 维度 | 水平越权 | 垂直越权 |
|---|---|---|
| 对象 | 同级别用户 | 不同级别角色 |
| 典型场景 | 改订单号看他人订单 | 普通用户调用管理员接口 |
| 危害程度 | 隐私泄露 | 系统被完全控制 |
| 检测难度 | 需要理解业务归属 | 需要比对角色权限表 |
行业共识认为,水平越权更难防御,因为垂直越权往往可以通过权限框架统一管控,而水平越权涉及每一条数据的所有者归属关系,属于业务逻辑层面的细活。
比如一个物流查询接口,普通用户A传自己的waybill_id能查,传用户B的waybill_id也能查,这就是水平越权,它不涉及角色提升,但直接造成数据泄露。
垂直越权则更直接:用户中心的接口本来只允许登录用户调用,但某个“获取全部用户列表”的后台接口没有校验角色,普通用户直接访问就拿到了全部数据。
日常渗透测试里,两类漏洞经常一起出现,修复时单靠加一个中间件拦截远远不够,必须把数据归属校验写进每一个具体业务接口里。
越权访问漏洞测试方法:两分钟验证一个接口
找越权漏洞不需要复杂工具,Burp Suite配合浏览器,手把手就能验证。
第一步:准备两个账号
准备账号A和账号B,两者属于同一角色但数据不同,比如一个是user1,一个是user2。
- 用账号A登录,抓取访问资源X的请求包,比如查看个人订单列表的请求
- 把该请求复制一份,替换成账号B的cookie或token
- 观察返回内容:如果账号A的token获取到了账号B的订单数据,水平越权实锤
需要留意的是,部分请求里的用户标识同时存在于cookie和请求体,替换cookie后,还要检查请求体里的user_id、uid等参数,二者同步替换才能测准。
第二步:从角色维度测垂直越权
- 注册一个普通用户,登录后抓取一个后台管理接口的请求地址
- 将普通用户cookie替换到该请求中
- 如果返回200和真实数据,垂直越权确认
部分系统用GET参数传递操作对象,比如/api/user/delete?id=123,测试时把所有可能越权的接口分类,按“查询、修改、删除、导出”四个操作批量测试效率最高。
第三步:尝试未授权访问
这个不属于严格意义上的越权,但和越权访问高度关联,把请求里的cookie和token全部删掉,直接重放请求,如果后端返回数据,说明连登录校验都缺失了。

测试记录建议用表格汇总:接口路径、请求方式、参数、两个账号的结果差异、是否存在越权,这样后续写修复建议时,开发能直接对着改。
越权访问怎么修复:把权限粒度切细
修复不能靠“加一个判断”解决,而是要从结构上把权限划分改细,重点改造四个层面。
接口层:强制校验资源归属
每一个查询或操作类接口,后端必须拿到“当前用户ID”和“目标资源所属用户ID”,两者比对不一致就直接拒绝。
// 错误示范:只判断是否登录
if (session.getAttribute("user") != null) {
return orderService.getOrderById(orderId);
}
// 正确示范:校验资源归属
Long currentUserId = session.getAttribute("userId");
Order order = orderService.getOrderById(orderId);
if (!order.getUserId().equals(currentUserId)) {
throw new AccessDeniedException("无权访问该订单");
}
这里的核心原则是:用户ID只能从服务端会话获取,不能信任前端传入的user_id参数,前端传的参数一律当作不可信输入处理。
数据层:查询自动追加归属条件
在SQL层面,把“当前用户ID”作为查询的隐形条件,这样即使开发忘了在某一个接口加校验,数据库层面也会兜底。
-- 错误示范:按订单号万能查询
SELECT FROM orders WHERE order_id = #{orderId};
-- 正确示范:自动拼接归属条件
SELECT FROM orders
WHERE order_id = #{orderId}
AND user_id = #{currentUserId};
这个方案的优势在于,即使业务代码改动频繁,数据库层始终有最后一道防线。
权限模型:从角色粗粒度升级到资源细粒度
角色控制权限(RBAC)适合功能级权限,但对于数据级权限,角色根本管不住,把权限模型改为基于属性的访问控制(ABAC),根据用户属性、资源属性、环境条件动态计算权限。
资源类型=订单,资源归属人=当前登录用户,允许操作=查看/取消。
对一个电商系统来说,可以先按“资源类型”拆分权限点,再按“归属关系”拆分数据范围。“查询订单”这个功能权限授予所有登录用户,但数据范围限定为“本人创建的订单”。
统一鉴权中间件
不要在每个接口里手写权限判断,否则漏写是必然的,做一个统一鉴权注解或拦截器,强制标注每个接口的数据归属校验模式。

@RequireLogin:仅校验已登录@RequireOwnership(resource = "order"):校验资源归属@RequireRole("ADMIN"):校验角色
努力做到“默认拒绝”,新接口不标注就不启用,从流程上杜绝漏网之鱼。
权限划分过到底有多细才算正常
很多团队问:权限粒度切到每一行数据,会不会过度设计?答案是看数据敏感度,金融、医疗类数据,必须到字段级,比如医生能看到患者病历,但看不到患者的收入信息;护士能看到医嘱,但看不到诊断结论。
平台,可以按“本人同组全部”三级数据范围划分,例如一个协同办公系统,员工创建的任务默认只有自己和直属上级可见,共享给某个部门后才能被该部门成员访问。
用属性定义数据范围,比硬编码角色更灵活:
data_scope = SELF:仅本人数据data_scope = DEPT:本部门数据data_scope = ALL:全部数据
然后权限配置里明确哪个角色在哪个资源上允许哪种范围的操作,这种多级网格的设计才能真正解决权限划分过粗的问题。
越权访问常见问题解答
问:越权访问和水平越权是一回事吗?
不是,水平越权是越权访问的一种类型,特指同级别用户之间的横向数据越界,越权访问还包括垂直越权,即低权限用户访问高权限功能,垂直越权更严重,一旦被利用可能导致整个后台失守。
问:用网关统一做权限校验能彻底防止越权吗?
不能,网关适合做统一认证和粗粒度鉴权,比如验证JWT签名、检查是否为黑名单用户,但数据级权限需要知道具体业务上下文,网关层拿到不到“这个订单属于谁”这类信息,实际项目中,网关拦截加业务层归属校验必须组合使用。
问:越权访问漏洞能靠扫描器发现吗?
普通漏洞扫描器很难扫到越权漏洞,扫描器只能发现已知路径的配置错误,但越权是业务逻辑漏洞,需要理解业务的白盒测试或基于数据矩阵的自动化测试才能发现,推荐在CI/CD流程中加入越权专项测试,用测试账号矩阵跑数据级权限校验用例。
