一个越权漏洞怎么被测出来:给权限矩阵织一张可回归的『水平/垂直越权』测试网

简介: 本文详解如何将零散的越权测试升级为可自动回归的系统性防护网:通过构建角色×资源×操作的权限矩阵,用pytest参数化自动生成水平/垂直越权用例,强制断言403或空响应,确保每次回归都精准拦截“不该看的数据”与“不该做的操作”。

一个越权漏洞怎么被测出来:给权限矩阵织一张可回归的『水平/垂直越权』测试网

审计里翻出这样一个反常现象:登录正常、鉴权中间件在跑、每个接口的功能单测全绿——可测试同学拿用户 A 的 token 去请求用户 B 的订单详情接口,接口居然把 B 的数据原样返回了。没有报 401,没有报 403,200 加一份别人的隐私。

根因很典型:每个接口的单测只验了一件事——「登录态有效」。没人验第二件事——「这条数据,是不是当前登录用户有权看的」。鉴权中间件保证了「你是个登录用户」,但没保证「你只能看你自己的」。这就是 OWASP 长期列为高危的 Broken Access Control(失效的访问控制)里最经典的水平越权。这篇讲怎么把越权测试从「渗透同学上线前手工试两下」做成一张每次回归都自动跑、能持续兜住的测试网。

一、审计结论摘要

审计对象是这套 Web/API 的权限测试覆盖,三条发现按风险从高到低。

第一,越权靠人工渗透、不进回归。渗透同学上线前手工试一次,试到了算运气,试不到就漏到线上;下次改接口没人再试,漏洞随迭代复发。这是本次事故的直接原因,属阻断级。

第二,所有接口单测只断言「登录态有效」,没有一条断言「资源归属校验」。功能测试证明的是「有权限的人能拿到数据」,证明不了「没权限的人拿不到」,这两件事方向相反。

第三,权限规则散落在各接口的 if 判断里,没有一张统一的权限矩阵。没有矩阵,就没法系统性地说「哪些角色对哪些资源有哪些操作」,越权用例也就无从穷举。

建议动作三条:把权限规则抽成一张角色×资源×操作的矩阵、用 pytest 参数化把矩阵每个单元格展开成越权用例、把这张网挂进回归每次自动跑。第六节给可运行实现,第七节给审计清单。

二、水平越权与垂直越权:两种方向相反的失控

越权不是一个漏洞,是两个方向。分清方向,断言点才不会写错。

水平越权是「同角色、换他人资源」:用户 A 和用户 B 都是普通用户,A 把请求里的资源 ID 换成 B 的,就拿到了 B 的数据。垂直越权是「跨角色、升权」:普通用户去打只有管理员能访问的接口,比如直接请求 /admin/users。前者破坏的是数据隔离,后者破坏的是功能隔离。

维度 水平越权 垂直越权
定义 同角色用户访问了不属于自己的他人资源 低权限角色访问了高权限角色的功能/资源
触发方式 保持角色不变,替换请求里的资源 ID(订单号/用户 ID) 保持资源不变,用低角色 token 打高角色接口
断言点 必须 403 或返回空数据,绝不能返回他人数据 必须 403,绝不能执行成功或返回管理数据
典型漏测 单测只验登录态,没人换他人 ID 试 单测只用管理员账号测管理接口,没拿普通账号试

一句话:水平越权测的是「换了资源 ID 还拦不拦」,垂直越权测的是「换了低角色 token 还拦不拦」。两者都要专门造出「不该有权限的请求」,再断言它被拒。

三、先画一张权限矩阵:角色×资源×操作

要系统性测越权,先得有一张能穷举的地图。这张地图就是权限矩阵:行是角色(普通用户/商家/管理员),列是资源(订单/商品/用户),每个单元格标注这个角色对这个资源能做哪些操作(读/写/删/无权限)。

矩阵一旦画出来,越权用例就能被机械地生成——遍历每个「无权限」或「仅限他人」的单元格,造一个对应的越权请求,断言它被拒。这就是把「渗透同学凭经验手工试」升级成「按矩阵穷举自动试」的关键一步:经验会漏,矩阵不会。

角色\资源 订单(order) 商品(product) 用户(user)
普通用户(user) 读/写:仅自己的 读:全部 读:仅自己的
商家(merchant) 读:仅本店 读/写:仅本店 无权限
管理员(admin) 读/写/删:全部 读/写/删:全部 读/写/删:全部

从这张矩阵能直接读出该测什么:普通用户对「他人订单」是无权限的 → 生成一条水平越权用例;普通用户对「用户资源的管理操作」是无权限的 → 生成一条垂直越权用例;商家对「用户资源」完全无权限 → 生成一条跨资源越权用例。每一个「仅自己的」「无权限」都是一个待测单元格。

image.png

四、把矩阵展开成可回归的越权用例

有了矩阵,测试网就成型了。核心思路是:对每个「不该有权限」的单元格,自动造一个越权请求,用两个真实测试账号的 token 交叉打接口,断言结果必须是 403 或空数据。

水平越权用例长这样:准备 A、B 两个普通用户,各自有一条订单;拿 A 的 token 去请求 B 的订单 ID,断言拿不到 B 的数据。垂直越权用例长这样:拿普通用户 A 的 token 去请求 /admin/users,断言 403。关键在于这些用例由矩阵参数化生成,不是手写——矩阵加一行角色、加一列资源,越权用例自动跟着长出来,不会因为「忘了补」而漏。

这张网和只验登录态的单接口功能测试,覆盖面完全不在一个量级:

维度 单接口功能测试(只验登录态) 权限矩阵越权回归
覆盖面 只测「有权限的人能成功」这一个正向 穷举「无权限的人被拒」,含水平/垂直/跨资源
可复现 越权靠人工偶然试到,无法稳定复现 由矩阵机械生成,每次回归都跑同一批用例
漏测风险 高——改接口没人再手工试,漏洞复发 低——矩阵变了用例自动跟着变
可维护 规则散在各接口 if 里,改一处漏一片 权限集中一张矩阵,改矩阵即改用例

五、断言点:403 还是空数据,别只断「不报错」

越权用例最容易写错的地方是断言。很多团队写「断言请求不抛异常」——可越权恰恰是不抛异常的,它静默返回 200 加一份别人的数据。正确的断言必须钉死在权限语义上:要么状态码是 403,要么返回体里没有目标资源的数据。

还要注意一个陷阱:有些接口对越权请求返回 200 但 body 是空列表,这也算「拦住了」;但有些返回 200 且 body 里带着他人数据的一个字段(比如脱敏没脱干净的手机号),这就是漏。所以断言不能只看状态码,得同时校验「响应里不含目标资源的归属数据」。第六节的代码把这两层断言都写进去了。

六、可运行实现:权限矩阵 + pytest 参数化越权用例

实现分两段。第一段是权限矩阵配置与越权用例的展开逻辑,第二段是用两个测试账号 token 交叉打接口并断言拒绝。为了可直接跑,用一个内存里的假接口层模拟后端权限校验。

"""
authz_matrix.py —— 权限矩阵配置 + 越权用例展开(纯标准库,可直接跑)
矩阵定义:角色 × 资源 × 操作 → 允许范围(self / all / none)
把矩阵里每个「不该有权限」的单元格,机械展开成一条越权测试用例。
运行自测:python authz_matrix.py
"""

# 权限矩阵:{角色: {资源: {操作: 允许范围}}}
# 允许范围: "self"=仅自己的, "all"=全部, "none"=无权限
MATRIX = {
   
    "user":     {
   "order": {
   "read": "self"}, "user": {
   "read": "self"},
                 "admin_panel": {
   "read": "none"}},
    "merchant": {
   "order": {
   "read": "self"}, "product": {
   "write": "self"},
                 "user": {
   "read": "none"}},
    "admin":    {
   "order": {
   "read": "all"}, "user": {
   "read": "all"},
                 "admin_panel": {
   "read": "all"}},
}


def build_violation_cases():
    """遍历矩阵,生成两类越权用例:
    - horizontal: 允许范围是 self 的单元格 → 换他人资源应被拒
    - vertical:   允许范围是 none 的单元格 → 低角色打高权限应被拒
    """
    cases = []
    for role, resources in MATRIX.items():
        for res, ops in resources.items():
            for op, scope in ops.items():
                if scope == "self":
                    cases.append({
   "type": "horizontal", "role": role,
                                  "resource": res, "op": op,
                                  "desc": f"{role} 换他人 {res} 应被拒"})
                elif scope == "none":
                    cases.append({
   "type": "vertical", "role": role,
                                  "resource": res, "op": op,
                                  "desc": f"{role} 打 {res} 应被拒(无权限)"})
    return cases


if __name__ == "__main__":
    cases = build_violation_cases()
    print(f"从权限矩阵展开出 {len(cases)} 条越权用例:")
    for c in cases:
        print(f"  [{c['type']:10s}] {c['desc']}")
"""
test_authz.py —— 用两个测试账号 token 交叉打接口,断言越权被拒(pytest)
运行:pytest -q test_authz.py
依赖 authz_matrix.py。用一个假后端 fake_api 模拟权限校验,
演示真实项目里把它换成 requests 调用即可。
"""
import pytest
from authz_matrix import MATRIX, build_violation_cases

# 两个测试账号,各自拥有一条订单
OWNERS = {
   "order": {
   "A": "order_A", "B": "order_B"},
          "user": {
   "A": "user_A", "B": "user_B"},
          "product": {
   "A": "prod_A", "B": "prod_B"},
          "admin_panel": {
   "A": "panel", "B": "panel"}}
ROLE_OF = {
   "A": "user", "B": "user"}   # A、B 都是普通用户,方便测水平越权


def fake_api(caller, resource, resource_owner):
    """假后端:按权限矩阵校验。返回 (status_code, 是否含目标数据)。
    真实项目里换成 requests.get(url, headers={'Authorization': token})。"""
    role = ROLE_OF[caller]
    scope = MATRIX.get(role, {
   }).get(resource, {
   }).get("read", "none")
    if scope == "all":
        return 200, True
    if scope == "self" and resource_owner == caller:
        return 200, True
    # 其余情况都应拒绝:403 或 200 空数据
    return 403, False


@pytest.mark.parametrize("case", build_violation_cases())
def test_violation_denied(case):
    """矩阵展开的每条越权用例:用 A 的身份去打不属于 A 的资源,必须被拒。"""
    res = case["resource"]
    # 水平越权:A 打 B 的资源;垂直越权:A 打自己 none 的资源
    victim_owner = "B"
    status, has_data = fake_api(caller="A", resource=res,
                                resource_owner=OWNERS[res][victim_owner])
    # 断言双层:状态码拒绝 或 响应不含目标数据,二者必须至少成立
    assert status == 403 or has_data is False, \
        f"越权未被拦截: {case['desc']} -> status={status}, has_data={has_data}"

为什么这么写:把权限规则集中到一张 MATRIX 字典,而不是散在各接口的 if 里,是因为越权测试网的价值全在「穷举」——只有规则集中,才能用 build_violation_cases() 一次遍历生成全部用例;规则一散,就永远有接口漏在网外。用例用 @pytest.mark.parametrize 展开,是因为这样矩阵加一行角色、加一列资源,用例数量自动跟着涨,不用手工补,这正是「可维护」那一栏的落地。断言写成 status == 403 or has_data is False 双层,是因为前面说的陷阱:有的接口不返 403 而是返 200 空数据,只断状态码会误报失败;只断数据又可能漏掉「返 403 但日志泄了数据」。踩过的坑有两个:一是测试账号必须真实拥有各自的资源(A 有 order_A、B 有 order_B),否则「A 打 B 的订单」根本无从构造,越权用例成了空跑;二是 fake_api 里对 self 范围的判断一定要比对 resource_owner == caller,漏了这个比对,假后端自己就把越权放行了,测试全绿却测了个寂寞——真实项目里换成 HTTP 调用后,这个归属校验就落在你的后端上,也正是最该被测的那一行。

七、审计清单:对着报告逐条勾

审计项 报告里必须出现什么 缺失时的后果
权限矩阵 角色×资源×操作的完整矩阵,含「仅自己/无权限」标注 无法穷举,越权用例靠经验漏一片
水平越权用例 换他人资源 ID 后断言 403/空数据 用户 A 查到用户 B 的数据
垂直越权用例 低角色 token 打高角色接口断言 403 普通用户执行了管理员操作
断言层次 状态码 + 响应不含归属数据,双层断言 200 空数据误判为漏、或 403 泄数据漏判
可回归 越权网挂进 CI,每次回归自动跑 改接口后漏洞复发无人知
测试账号 至少两个各持真实资源的账号做交叉 越权请求无从构造,用例空跑

这六项没有一项需要额外预算,只是把散落的权限规则收成一张矩阵,再让 pytest 按矩阵把越权用例自动展开、挂进回归。反过来说,一份只验「登录态有效」、连一条水平越权用例都没有的接口测试报告,它的「全绿」不该被当作权限安全的上线依据。

越权测试最贵的绿灯,是每个接口都验过「登录态有效」,却没人问过:这条数据,凭什么给你看。

你手上的接口测试,有没有一条是专门拿 A 的 token 去打 B 的资源、断言它被拒的?评论区聊聊。

相关文章
|
9天前
|
人工智能 自然语言处理 安全
阿里云千问办公 QwenWork详细介绍:产品核心能力、典型场景、价格及常见问题解答
千问办公是阿里云推出的一站式AI办公平台,主打"不止于对话,更注重交付",依托通义千问旗舰大模型,用户一句话即可完成数据分析、PPT生成、视频剪辑等复杂任务,直接输出可用成果。产品深度打通钉钉生态与企业OA,覆盖桌面端、网页端,提供企业标准版198元/人/月等多档订阅方案,新用户注册即赠2000积分,适配工程师、HR、财务等多职业办公场景,成为能动手干活的"全能AI同事"。
|
9天前
|
人工智能
千问办公官网入口:阿里AI办公QwenWork产品页和免费网页端链接
千问办公官网含两大入口:一是网页端(qwenwork.cn),即开即用,支持浏览器直接访问;二是阿里云产品页 https://t.aliyun.com/U/JNKJuO 提供免费/付费版详情、功能介绍及使用指南。
|
15天前
|
网络协议 Linux iOS开发
【2026实测】Wireshark下载+安装+汉化+使用教程(图文版,巨详细)
Wireshark 是一款免费开源的网络协议分析工具,可实时捕获、解析并可视化数据包,助你诊断网络故障、分析通信协议(如HTTP、DNS、TCP等)。支持Windows/macOS/Linux,含中文界面,新手入门便捷。(239字)
|
9天前
|
人工智能 API 内存技术
刚刚 DeepSeek V4.1 Flash 开启内测,1 分钟教你用上!
刚刚 DeepSeek 内测群发布了 DeepSeek V4.1 Flash 中间版本内测的消息,这次的模型采用了新的结构,原生支持多模态、能力更强、速度更快、且成本更低。
1903 15
|
8天前
|
IDE 开发工具
Qoder 上线 Sonus 模型,Computer Use 能力全面增强
Qoder国际版上线全新内置大模型Sonus(/ˈsoʊnəs/),全球领先,专精超长任务执行与电脑操作(Computer Use)。配合Qoder桌面端0.2.3版本,可自主完成编程、金融建模、科研及表格制作等复杂工作。现全面支持Qoder全系产品,效率提升3.2倍。
1012 1
Qoder 上线 Sonus 模型,Computer Use 能力全面增强
|
14天前
|
人工智能 运维 BI
阿里云千问办公QwenWork深度解析:基于Qwen3.8,六大核心能力重构企业全自动化工作流与计费选型指南
传统AI办公工具大多停留在对话问答、文档摘要、简单文案生成层面,只能完成单点碎片化任务,无法自主拆解复杂业务流程,很难串联多工具、多文档、外部业务系统完成端到端完整工作交付。很多企业在落地AI办公的时候,需要组合多款不同工具,来回切换界面,手动复制粘贴中间结果,智能化改造落地门槛居高不下。千问办公QwenWork是整合多款智能体产品能力打造的一体化企业办公智能体平台,底层基座依托Qwen3.8大模型,打通桌面端Agent、云端Agent、企业协同Agent三种运行形态,不再局限简单问答,接收业务目标之后自主拆解任务步骤,调用各类工具,处理文档、表格、浏览器自动化、数据查询,直接输出可交付的办公
1669 4
|
10天前
|
缓存 人工智能 自然语言处理
阿里云qwen3.8-flash大模型介绍:模型能力、模型价格、免费额度与最新活动
本文是阿里云百炼平台Qwen3.8-Flash大模型的选型接入指南,作为兼顾性能与响应速度的高性价比多模态模型,它支持百万级上下文窗口、全场景多模态输入与完整智能体能力矩阵,适配编程辅助、智能体协作等核心场景。文中同步梳理了最新下调的阶梯定价、夜间4折等优惠活动,搭配OpenAI兼容流式调用示例,帮助开发者低成本快速落地高并发AI应用。
阿里云qwen3.8-flash大模型介绍:模型能力、模型价格、免费额度与最新活动
|
16天前
|
缓存 数据可视化 开发工具
DeepSeek Harness 怎么更新?dsh 更新完整指南:更新本体(npx、npm、源码)与更新插件两种方式
DeepSeek Harness 的更新分两层:本体更新(npx 自动最新、npm update -g、源码 git pull)与插件更新(插件市场点更新、命令行覆盖安装)。本文按「准备 → 更新本体 → 更新插件 → 更新后检查」四步走,覆盖新手常见疑问。
1810 1
DeepSeek Harness 怎么更新?dsh 更新完整指南:更新本体(npx、npm、源码)与更新插件两种方式
|
11天前
|
SQL 人工智能 前端开发
QoderWake 1.0 正式发布:从桌面里的 Agent,到工作现场的数字员工
QoderWake v1.0正式发布:企业级数字员工团队平台。支持“一句话建岗”,预置10类特训岗位;Waker常驻钉钉/飞书群,@即响应、自动协作、跨任务记忆;具备定时/事件/API多触发方式与统一任务看板;已沉淀27.6万条记忆、12.3万项技能,助力组织实现人机协同增效。
819 2
|
8天前
|
缓存 测试技术 API
DeepSeek V4.1 Flash 内测接入:改个模型名即可调用(附代码)
DeepSeek V4.1 Flash 内测不用申请,base_url 不变、改个模型名就能调,9/10 到期。本文讲清接入、计费限流与多模态注意点。
829 0
DeepSeek V4.1 Flash 内测接入:改个模型名即可调用(附代码)

热门文章

最新文章