前言
访问控制失效 (BAC) ,也就是我们俗称的越权漏洞,是目前 Web 领域最常见的安全漏洞之一,尽管 BAC 问题严重,但由于缺乏可靠的预言机 (oracle) 以及难以生成有效的攻击请求,想要利用自动化的手段优雅的检测 BAC 漏洞仍然是当今业界的难点。因此本文的作者提出了 BACFuzz——第一个专门设计用于发现 BAC 漏洞的灰盒模糊测试框架,它主要能检测 PHP Web 应用中的对象级授权失效 (BOLA) 和函数级授权失效 (BFLA)漏洞。
BACFuzz 将 LLM 引导的参数选择以及运行时的反馈和基于 SQL 的预言机检查相结合以检测 BAC 漏洞,使用轻量级的检测工具来捕获用于指导测试生成的信息,同时分析后端 SQL 查询来验证未经授权的输入是否流入受保护的操作,也就是判断控制流是否流入 Sink 点。
背景
OWASP最后一次更新(2023年)的十大Web应用程序安全风险和十大API安全风险中都不约而同的将 BAC 漏洞放在了第一位,可见 BAC 漏洞算是如今最普遍、最严重的安全风险。对 BAC 漏洞的研究屡见不鲜,但有两个问题极大的制约了对于 BAC 漏洞自动化检测的研究:
- 如何设计一个可靠的oracle。
- 如何生成有效的攻击请求。
首先简单理解一下oracle,oracle其实就是一种用于判断漏洞是否真正触发的工具,比如我们检测log4j很多时候就会用dnslog,通过判断dnslog是否接受到来自目标的请求来判断目标是否真的存在漏洞就算是一种检测log4j的oracle。只不过在 BAC 中找到这样一个oracle是很困难的,首先违反BAC通常不会导致崩溃或产生显式错误消息,而仅仅基于HTTP响应代码或消息的启发式方法往往不可靠;其次,API请求通常包含许多具有不同数据类型和依赖关系的参数,导致覆盖完所有的搜索空间极其困难,而生成既有效又有语义的输入对于检测又必不可少,因为格式错误的请求通常会被拒绝。
为了解决oracle的问题,作者采用的方法是利用 SQL 来检测。这来自一个很自然的想法,一旦用户的请求被接受,数据库支持的web应用程序通常会对其进行解析,执行验证和授权检查,然后构造后端SQL查询,如果某些攻击者提前设计好的突变值出现在了结果查询中,这就说明授权检查很可能被绕过了,攻击者成功进行了攻击。同时由于 BACFuzz 是灰盒模糊测试框架,因此它会hook函数来监视与SQL查询相关的原始PHP函数,从而实现精确的反馈和oracle验证。
为了解决攻击请求生成的问题,作者选择只关注Broken Object Level Authorization
(BOLA)(对象级授权失效) 和 Broken Function Level Authorization (BFLA)(函数级授权失效)这两种常见的 BAC 漏洞,并利用 LLM 识别流量中的重要参数。
- BOLA——对象级授权失效
- BOLA 是指访问控制不充分(我们俗称的越权漏洞),导致用户可以操作或访问他们无权查看或修改的数据对象。例如,假设一个 Web 应用存储了来自不同卖家的产品,并且只有相应的卖家才被允许修改这些产品。当一个卖家可以通过发送产品的 ID 引用来修改属于另一个卖家的产品时,该 Web 应用就被认为存在 BOLA 问题。
- BFLA—功能级别授权失效
- 当 Web 应用程序未能对用户访问特定 Web 功能(例如 API 端点)时强制执行适当的授权检查时,就会发生BFLA问题(即未授权漏洞)。例如,假设当非管理员用户访问网页时,用于创建新对象的按钮会被移除,因为只有管理员才被允许创建新对象。如果 Web 服务器接受/执行来自非管理员用户的直接 HTTP 请求来创建对象,则称该应用程序存在 BFLA。
作者在20个基于php的Web应用程序上评估了BACFuzz,成功重现了17个已知BAC问题中的16个,并发现了26个以前未知的漏洞。综上所述,这篇论文的主要贡献如下:
- 一种全新的方法:作者提出了首个专用于自动检测 Web 应用程序中的对象级和函数级 BAC 漏洞的灰盒模糊测试方法。
- 新的漏洞利用策略:作者引入了一个主动检查器模块,该模块应用先进的灰盒模糊测试技术(包括 LLM 引导的参数分析和引用变异)来利用捕获的 HTTP 请求。
- 新颖的 Oracle 验证:作者开发了一个全新的验证模块用于检查应用程序发出的 SQL 查询——如果这些查询中出现变异的输入,并且满足特定于漏洞类型的条件,则认为目标对象或函数存在漏洞。
- 新的 BAC 漏洞数据集:作者整理并发布了一个包含已知 CVE 报告的 BAC 漏洞的 Web 应用程序数据集,以解决 BAC 检测工具缺乏现有基准的问题。
BAC检测中的挑战
为了更好地理解 BAC 漏洞在真实项目中的表现,作者收集并分析了2022年4月至2025年4月间公开的 CVE,重点关注 PHP 开源 Web 应用中可复现的 BAC/IDOR 漏洞,最终筛选出 15 个 CVE,从中总结出了四个 BAC 检测中的挑战:
- C1: 难以识别易受攻击的函数
- Web 应用中函数调用依赖于用户上下文、会话等信息,且路径可能被封装或重定向,导致 fuzz 工具难以自动发现关键函数。例如某些漏洞需要构造特定 URL 才能触发,不是简单扫描路径就能实现。
- C2: 难以识别受限对象
- 要成功触发 IDOR/BOLA 漏洞,必须区分当前用户可访问与不可访问的对象。但很多敏感对象(如 admin_id)是隐藏的,需要特定请求才能看到,难以自动发现。
- C3: 漏洞验证困难
- BAC 是语义型漏洞,不会像崩溃那样明显暴露。很多时候,响应信息模糊、不清晰,难以判断是否真正越权成功。
- C4: 输入空间大、随机性高
- Web 接口参数多且复杂,盲目随机变异难以命中敏感参数。有效 fuzz 应该优先变异更可能导致越权的关键参数,并尽量使用“合理”的值而不是完全随机值。
出于上面的原因,作者限定了研究的适用边界和假设:
- 只关注基于角色的访问控制(RBAC)
- 研究仅聚焦于最常见的 RBAC 模型,即根据用户角色决定是否有权限访问某功能或对象。其他如基于属性或上下文的访问控制不在研究范围内。
- 只检测代码层面的漏洞
- 仅分析代码实现中的访问控制缺陷。权限配置错误(如权限过大的角色分配)属于系统设计问题,不在本研究范围。
- 假设测试环境已预置数据
- Fuzzer 不负责初始化测试数据(如用户注册),假设人类测试者已准备好不同角色的用户和所需数据。
对于上面提出的挑战,作者设计了几种不同的方法来解决这些问题。
层次角色分析
为解决挑战 C1(识别潜在易受攻击的函数),作者提出分层角色分析(Hierarchical Role Analysis)。其核心思想是通过对不同角色账号访问页面时产生的 HTTP 请求进行对比,从而识别出只有高权限用户才会触发的请求,进而检测是否可以被低权限账号非法访问。
该分析的主要目的就是为了找出只有高权限用户才能访问的功能接口(即可能存在越权漏洞的函数调用),用于接下来的垂直访问控制测试(BFLA),主要流程如下:
- 模拟不同角色用户的行为:
- Fuzzer 使用不同角色(如 admin、manager、user)分别登录系统。
- 每个角色访问页面、点击按钮、填写表单,模拟正常操作。
- 浏览器在后台发送的所有 HTTP 请求都会被记录下来。
- 构建请求语料库(request corpus)与参数语料库(param corpus):
- 所有服务器响应为 2xx 的请求会保存在请求语料库中,并按角色分类。
- 同时,从请求 URL 和 body 中提取参数名和值对,建立参数语料库。
- 判断“新请求”标准:
- 比较 URL、请求方法、参数名(不比较参数值),确保语义上唯一。
- 识别潜在敏感请求:
- 某个请求只在高权限角色中出现,在低权限用户中未出现,则可能是越权目标。
- Fuzzer 接下来将使用低权限用户尝试发起这些请求(即 BFLA 测试)。
- 对其它请求进行 BOLA 测试:
- 如果某请求所有用户都能访问,那就通过修改参数值(如 ID)进行横向越权(BOLA)测试。
这里作者引入了一个叫做角色标注机制(Role Labelling)的东西,作用就是对每个请求和参数标注其来源角色。例如,一个请求最初由 admin 用户触发,那么标注为 admin,后续如果 manager 用户也发送了同样请求,则增加 manager 标签,同样处理参数语料库中的 param-value 对,记录它们出现在何种用户角色中。
引用参数变异
为了解决挑战 C2(难以识别受限对象) 和 C4(输入空间大、随机性高), 作者提出引用参数变异(Reference Mutation)来触发 BOLA(水平越权)类漏洞。
首先,工具会收集系统生成数据(Sys-Gen Data),也就是那些系统自动生成、不直接可见但出现在 HTTP 请求中的数据(隐藏表单字段 、JS 中嵌入的参数、页面加载时自动填入的 ID、token、内部引用值),这些参数通常不是用户直接控制的,但正是越权攻击的关键点(如 user_id=2 被非用户2的人使用)。fuzzer 提交表单时会填入特定前缀的“标记值”,如果请求中的值包含这些前缀则属于用户生成数据,否则属于系统生成数据(sys-gen),被收集起来。
接下来,工具会使用大模型进行引用参数识别。并非所有 sys-gen 参数都和功能/对象有关,有些是时间戳、CSRF token、安全标志等,不应盲目变异。所以,fuzzer 使用 LLM 来分析参数名和用途,判断是否可能是引用某个对象/功能,例如,模型可能识别出 admin_id、file_id、order_number 是关键对象,最后会生成三类结果:
- security measures:如
csrf_token、nonce,不能变异,避免被服务器拒绝。 - reference params(引用参数):被模型判定为可能涉及对象的字段(将重点变异);
- less important params:普通参数,用于随机变异;


最后开始真正的引用参数变异,使用之前收集的 param corpus(历史上观测到的参数值)来进行同类型替换,避免使用纯随机值。
- 文本型(如
abc,token_xyz)只与文本型互换 - 数值型(如
1234、2025admin)只与数值型互换;
当然,对不重要参数(less important)仍使用随机变异,有时也混合使用:变异引用参数 + 一些随机参数,保持多样性,比如对于下面的请求:
POST /updateUser
user_id=2&name=abc&csrf_token=xyz
user_id=2 是 sys-gen 参数,经过 LLM 判断后,它是引用参数,fuzzer 将其替换为其他已观察到的 user_id(如 3、5),而非生成 xyz123 这类没意义的值;csrf_token 被标记为安全字段,不会变;而 name=abc 可能被随机改为 name=random123。
SQL 检查
解决挑战 C3(如何验证是否真的发生了访问控制绕过),作者选择利用 SQL 进行检测,即通过监控 Web 应用生成的 SQL 语句,判断是否真的对受保护资源发起了数据库操作。
Broken Access Control(BAC)不像 XSS 那样有明确的可视化结果,它更像一种“语义性错误”,某些请求服务器返回 200 OK,但实际上可能什么都没做,因此,光看响应内容无法判断是否“越权成功”。而 SQL Checking 方法核心思想在于在 Web 应用的后端中插桩(instrumentation),记录它执行的 SQL 查询语句,Fuzzer 检查是否能在 SQL 语句中找到自己变异后的参数值,如果能匹配上,说明服务器执行了数据库操作,从而证明越权成功。
对于不同的漏洞会有不同的检查规则:
- 函数级别越权(BFLA)
- 核心思想在于判断是否触发了仅限特定角色才能执行的操作函数。
- 主要是看请求中变异后的参数值是否出现在 SQL 查询中(比如插入/更新了某条记录),或者提交请求的低权限角色,本不应看到或触发这个请求,为验证条件 2,fuzzer 会根据请求来源(referer)模拟打开用户页面,查看该请求是否出现在该角色的正常页面中
- 对象级别越权(BOLA)
- 核心思想在于判断是否访问了某个不属于该用户的对象。
- 主要是看 SQL 查询中的 WHERE 子句包含了变异后的引用参数值(如 user_id=3)或者被变异的对象(如 user_id=3)不属于该用户角色原本能看到的对象集合,同样的,为验证条件 2,fuzzer 会打开 referer 页面,提取当前用户能看到的所有对象引用,如果变异值不在其中,则说明发生了越权
为提高准确性,fuzzer 不会只检测一次,如果某次检测判断为 BAC,fuzzer 会多次(最多10次)变异并重复提交请求,若多次都出现同样的 SQL 操作行为,说明漏洞是真实的,若之后不再出现,说明可能是偶然或误判。
比如对于下面的请求:
POST /updateUser
user_id=2&name=abc
你是 user_id=2 的普通用户,但 fuzzer 把它变成:
user_id=5
然后发现服务器产生了 SQL:
UPDATE users SET name='abc' WHERE user_id=5;
并且这个 user_id=5 并不在你用户页面能看到的对象中,那么认为成功触发了 BOLA。
BACFuzz 的设计与实现
BACFuzz 由两个主要组件构成:
- Main Driver(主驱动器):
- 模拟用户登录;
- 自动浏览 Web 页面;
- 捕获请求、收集功能和对象;
- 构建 “请求语料库” 和 “参数语料库”。
- Active Checker(主动验证器):
- 处理收集到的请求;
- 根据漏洞类型(BFLA/BOLA)进行有针对性的参数变异;
- 发送变异请求,结合响应和 SQL 查询进行漏洞验证。
当开始运行时,BACFuzz 会设置 URL 以及不同权限的账户,接着进行登录、爬取网页、收集请求,逐一变异请求、发送请求,最后验证是否存在越权行为。
主驱动器的作用是搜集信息,它会自动爬取页面,识别出可以提交数据的表单并拦截 HTTP 请求(带请求参数),最后按角色组织请求数据,供后续 fuzzing 使用。主驱动器会使用 cookies 模拟已登录状态进行页面爬取,主要的策略是DFS,即优先访问最新发现的链接,并且只会去访问 URL 不同的页面(包括路径、查询参数),检测到有 <form>、<input>、<textarea> 的页面才进一步分析。
主动验证器是 BACFuzz 的“攻击执行”模块,它会优先选择包含 reference 参数的请求(因为它们的攻击面更大),选择方式基于带权重的随机选择(权重 = 参数数量),对于不同的漏洞有不同的测试方式:
- BFLA(函数级越权)
- 将管理员专属接口(如
add_user)用低权限账号访问,检查服务器是否执行了请求(如数据库更新),在这个过程中会自动调整一些角色相关的动态参数,如 WordPress 的 nonce。
- 将管理员专属接口(如
- BOLA(对象级越权)
- 用当前角色的请求替换对象引用值(如 user_id),判断是否访问/修改了本不属于该用户的对象。
在响应检查(Feedback Evaluation)的过程中,会通过接收到的反馈来判断是否真的触发了BAC:
- 变异后的值出现在 SQL 中(说明执行了);
- HTTP 响应是 2xx;
- 覆盖了新的代码路径;
- 出现了 500 错误(可能是服务器处理越权时异常);
这些请求会被标记为“攻击面”并进一步分析。为获得精准反馈,BACFuzz 在后端 PHP 代码中进行函数级插桩,使用 PHP 的 UOPZ 库 hook 数据库相关函数,捕获 SQL 查询语句,如 mysqli_query, PDOStatement::execute 等;同时使用 PCOV 库记录代码覆盖,将所有信息写入 JSON,文件名包含请求唯一标识(X-FUZZER-COVID)。
评估
为了全面评估 BACFuzz 的有效性,作者着重回答以下的问题:
- RQ1 BACFuzz可以报告已知的漏洞吗?
- RQ2 BACFuzz能发现新的和有效的漏洞吗?
- RQ3 BACFuzz引入了多少开销?
- RQ4 BACFuzz在生成测试方面有多有效?
针对RQ1,作者表示 BACFuzz 能成功检测出 17 个已知漏洞中的 16 个,唯一漏掉的是 CVE-2023-43663(PrestaShop),因为 fuzzer 发送的参数是 statsbestcustomers =64 ,但 SQL 中出现的是 id_module=64,与原始请求参数不一致,导致无法匹配到。
针对RQ2,作者表示BACFuzz 成功发现了新的越权漏洞,其中 有些是静态分析工具无法检测到的,所有新发现已报告给对应开发者,等待确认,总体误报率较低。
针对RQ3,作者表示使用了 SQL/覆盖信息插桩后,只需检查请求中是否有 X-FUZZER-COVID 头即可开启/关闭,实验发现开启插桩后,系统开销很小,几乎不影响响应性能
针对RQ3,作者表示BACFuzz 产生了大量“未被拒绝(2xx/500)”的请求,更容易触发漏洞或发现系统逻辑缺陷,说明变异后的请求仍能被处理,未直接被权限控制机制拦截;
尽管 SQL 查询可作为漏洞验证的“Oracle”,但仍可能产生误报(False Positive,FP),有两个常见原因:
- 值匹配了,但语义不同(语义误报)
- 例如在 OpenCart 中,发送了
route=0后,捕获的 SQL 是DELETE FROM oc_cart WHERE (api_id > 0 OR customer_id = 1),值0出现在请求中,也出现在 SQL 中,但两者毫无关系,fuzzer 会继续变异成route=1再次发送,还是匹配到1,所以即使值相同,也无法证明是实际漏洞,属于语义误报
- 例如在 OpenCart 中,发送了
- 对象动态变化,造成误解(对象误报)
- 在 SMF 中,fuzzer 起初只看到用户有 topic ID 1,后续测试中系统可能新建了 topic ID 2,但 fuzzer 认为用户没有权限,实际上用户可以访问,导致错误判断为 BOLA。
同时还有两个极其影响有效性的因素:
- Internal Validity(内部有效性)
- 有些应用(如 WordPress、SMF、Prestashop)会记录日志到数据库,如
smf_log、ps_connections,这些日志表总是记录所有请求,可能导致 SQL 匹配误判为漏洞。因此 BACFuzz 支持用户配置忽略表名,避免日志引起误报。
- 有些应用(如 WordPress、SMF、Prestashop)会记录日志到数据库,如
- Construct Validity(构造有效性)
- BACFuzz 所用的漏洞数据集是手工挑选的,虽然覆盖了多种 BAC 模式与典型行为,但可能偏向文档齐全、容易复现的 CVE,无法覆盖所有真实世界的场景。
总结
作者认为通用 Web Fuzzer,如 EvoMaster、Restler、RESTest 等,这些主要用于崩溃漏洞检测,不适用于 BAC 问题,还有一些更关注非崩溃类漏洞的 fuzzer(如 Witcher、EDEFuzz、Atropos),但也无法检测越权问题。一些已知的 BAC 测试工具也只能操作 GET 请求,通过网页响应判断越权,检测面窄、精度低,仅通过静态分析方法难以处理 AJAX 等现代 Web 特性,不适用于动态内容复杂的应用,而该研究通过 SQL 查询实现自动验证,极大的减少了人工的干预。
在作者看来,有几个未来的改进方向:
- 上下文相关的越权(Context-dependent BAC)
- 某些漏洞只有在先执行某些操作后才会出现,例如需要先创建资源、激活功能,才可能越权,BACFuzz 目前不处理这类“有前置条件”的场景
- 信息泄露型越权(Passive/View-type BAC)
- 对于一些非功能调用,比如未经授权读取敏感信息,通常表现为 SELECT 查询,而 BACFuzz 当前只能分析 DML(如 INSERT、UPDATE、DELETE),难以覆盖这种只读型越权。
总的来说,该研究的最大亮点有两个,一个是采用 SQL+插桩的方式来判断越权是否成功,这种方式显然比现在大部分工具单纯看页面的语义相似度或者看返回的状态码要有效的多;另一个亮点是利用 LLM 对于参数进行了分类,对不同分类的参数有不同的处理策略,这也比过去case by case的突变策略要强得多。只不过缺点也很明显,只能检查少数几类很简单的 BAC 问题( BFLA 和 BOLA),其他的漏洞就检查不了了,这一点就蛮幽默的;其次,对于越权这种问题,感觉大部分人想要的是一个要么纯黑盒要么纯白盒的工具,而 BACFuzz选择灰盒Fuzz,而且还只能检查 PHP 这种 dead language 里的 BAC,泛用性实在是太低了,感觉就算开源出来也没多少人会用😂
BACScan: Automatic Black-Box Detection of Broken-Access-Control Vulnerabilities in Web Applications
这篇CCS’25 distinguished paper可以研究一下
收到 这就去认真学习