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

简介: 本文详解如何将零散的越权测试升级为可自动回归的系统性防护网:通过构建角色×资源×操作的权限矩阵,用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 的资源、断言它被拒的?评论区聊聊。

相关文章
|
27天前
|
人工智能 测试技术
换了模型、改了 Prompt,怎么知道线上没变差?用影子流量回放给 AI 功能做一次改版体检
本文揭示AI改版常见陷阱:离线用例全通过,线上却悄然退化。根源在于手写样本覆盖窄,难触真实长尾问法。提出“影子流量回放”方案——将脱敏后的真实线上请求,在影子环境同步运行新旧版本,通过拒答翻转、实体丢失、长度比等可解释指标精准识别退化,让改版在切流前照见真实效果。
|
27天前
|
安全 测试技术 定位技术
复盘一次线上漏测:问题不在『测试没覆盖到』,而在『需求根本没提』——一份漏测归因报告怎么改写了评审清单
本文揭示线上漏测主因常是“需求缺失”(占比约40%),而非测试遗漏。补用例仅治标,真正高效解法是将根因分析反哺需求评审——把高频漏洞转化为评审清单中的固定追问,如“边界值与超限行为是否定义?”。此举从下游堵水转向上游关阀,实现一次投入、持续拦截。
|
27天前
|
自然语言处理 安全 测试技术
别再只测『答得对不对』:给大模型应用建一套 Prompt 注入红队回归集,把越权/泄密挡在上线前
本文揭示RAG客服应用因缺乏Prompt注入防护而致系统提示词泄露的事故,指出问题根源在于测试只关注“答得对”,却忽视“会不会答不该答的”。提出将注入测试升级为可回归的红队用例集:结构化存于jsonl,覆盖四类注入;用pytest参数化断言输出、工具调用与拒答行为;接入CI自动拦截。安全不是模型天赋,而是靠可执行、可演进的断言守出来的。
别再只测『答得对不对』:给大模型应用建一套 Prompt 注入红队回归集,把越权/泄密挡在上线前
|
27天前
|
人工智能 缓存 数据库
常青翻新|Mock 不是万能替身:Dummy/Stub/Spy/Mock/Fake 五种 Test Double 的边界与踩坑
本文厘清测试替身的五大分类(Dummy/Stub/Spy/Mock/Fake),指出滥用“万能Mock”导致测试脆弱或假绿的根源:混淆“提供数据”与“验证交互”。强调选型核心原则——**先明确断言对象(数据?交互?真实行为?),再决定替身类型**,并结合AI场景(如Agent轨迹验证、假模型替代)赋予经典模式新生命力。
|
24天前
|
人工智能 数据挖掘 开发工具
RAG 不一定需要大模型重排:Jev 能不能做 Context Filtering?
RAG中检索易召回冗余内容,Jev作为决策层可精准筛选高相关Chunk,替代简单Top-K输入。它支持多维度判断(如版本、时效性),提升Context质量与LLM答案准确性,降低幻觉与Token成本。(239字)
RAG 不一定需要大模型重排:Jev 能不能做 Context Filtering?
|
1月前
|
人工智能 供应链 测试技术
DeepSeek Harness火了,但你知道怎么用它生成测试用例吗?
DeepSeek Harness是开源AI测试助手,一行命令即可启动。它能自动解析API文档,10分钟生成50+覆盖等价类、边界值与异常场景的测试用例,准确率高但需人工复核8条左右。专为测试工程师设计,大幅提升用例设计效率,降低重复劳动。
|
3月前
|
JSON API PHP
全国油价查询免费 API 接口详解与调用实战(PHP / Python)
本文详解接口盒子提供的免费全国油价API:支持查询31省汽油(89/92/95/98等)、柴油、CNG/LNG等全品类实时价格及涨跌幅度,含多价区(如四川分一、二价区),每10个工作日更新,附PHP/Python调用示例与避坑指南。
487 0
|
4月前
|
监控 Docker 数据库管理
闲鱼秒拍脚本,自动捡漏工具,python低价筛选最新筛选
本文介绍一套基于Python的闲鱼自动化秒拍系统,涵盖请求签名逆向、异步监控、智能筛选、去重、Selenium自动下单及多渠道通知等完整链路。技术栈含aiohttp、selenium、hashlib等,强调学习研究用途,附详细部署与风控注意事项。(239字)
1203 1