我把“待审批”改成了“已通过”:审批流状态参数篡改实战,一张请假条打穿整个OA"

admin 2026-10-21 06:54:34 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

cover_image

我把“待审批”改成了“已通过”:审批流状态参数篡改实战,一张请假条打穿整个OA

原创

逍遥
逍遥

昆仑AI安全实验室

2026年8月16日 09:53
广东

在小说阅读器读本章

去阅读

上个月测一套企业OA系统,目标是一个大型集团的内部办公平台。这系统界面做得挺唬人,审批流、会签、驳回、转交一应俱全,看着比银行还严。结果我在一个请假申请的功能里,抓了个包,把请求体里的\"status\":\"pending\"改成了\"status\":\"approved\",放行。页面刷新后,我的请假申请状态直接变成了“已通过”,部门经理和HR的名字赫然挂在审批记录里——而我连部门经理是谁都还没搞清楚。

就这样,一张伪造的请假条,让我摸到了整个审批流的命门。今天把这套审批流绕过的完整思路、技术细节、挖掘方法和防御方案全部公开。

一、审批流的本质:一道谁都能改的“状态门”

审批流的本质,就是一个状态机。一个标准的多级审批流程,它的状态流转大致是这样的:

待提交 → 待审批 → 已通过/已驳回 → 已归档

每一步流转,都应该有一个服务端控制的动作来触发:员工提交申请,系统把状态从“待提交”改为“待审批”;经理点击“同意”,系统校验经理的权限和角色,再把状态从“待审批”改为“已通过”。

问题出在哪?问题出在很多系统的状态流转不是由服务端动作触发的,而是由前端传给后端的参数直接控制的。

比如一个典型的请假申请接口,请求体可能是这样的:

{  \"leave_id\": \"20260816001\",  \"applicant\": \"zhangsan\",  \"start_date\": \"2026-08-20\",  \"end_date\": \"2026-08-22\",  \"status\": \"pending\",  \"approver\": null,  \"approve_time\": null}

如果前端在提交时可以直接传\"status\":\"approved\",而服务端直接把这个字段写进数据库,那这个审批流就等于没有。更微妙的是,很多系统在“提交”和“审批”两个动作之间,使用了同一个接口,只是请求体不同。攻击者可以用提交接口传一个“已通过”的状态,直接绕过所有人的审批。

二、实战:从一个请假单到整个审批系统的陷落

以下是一个脱敏的真实案例。

目标系统:某制造业集团的内部OA,使用Spring Boot + Vue开发,审批流覆盖请假、报销、采购、合同等十几个业务场景。

第一步:侦察审批接口

我注册了一个普通员工账号,进入请假申请页面,正常填写并提交了一张请假单。Burp Suite抓到了提交请求:

POST /api/v1/leave/apply HTTP/1.1Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...Content-Type: application/json
{  \"leaveType\": \"annual\",  \"startDate\": \"2026-08-20\",  \"endDate\": \"2026-08-22\",  \"reason\": \"personal\",  \"status\": \"pending\"}

响应返回{\"code\":200,\"data\":{\"leaveId\":\"LEAVE20260816001\"}}。

第二步:测试状态参数是否可控

我重新提交了一次,把请求体中的\"status\"改为\"approved\",并在请求体中添加了审批人字段:

{  \"leaveType\": \"annual\",  \"startDate\": \"2026-08-20\",  \"endDate\": \"2026-08-22\",  \"reason\": \"personal\",  \"status\": \"approved\",  \"approver\": \"wangwu\",  \"approveTime\": \"2026-08-16 15:30:00\"}

后端返回200,并且我的请假单详情页直接显示“已通过”,审批人是wangwu(他的身份是部门经理)。系统没有校验我的权限,也没有校验approver是否真的做了审批操作。

第三步:测试其他业务场景

既然请假单能绕过,报销单、采购单、合同审批能不能?我逐一测试:

  • 报销单:把\"status\":\"pending\"改成\"status\":\"approved\",报销单直接进入财务打款状态。
  • 采购单:同样手法,采购申请直接变成“已批准”,金额20万的设备采购单直接生效。
  • 合同审批:把状态改成\"approved\",合同直接进入盖章流程。

全部成功。这个审批系统就是一个筛子,所有业务场景的状态参数都能被前端直接控制。

第四步:获取管理员权限

我继续深入,发现了一个更危险的问题。在审批流里,有一个“管理员待办”接口GET /api/v1/admin/todo/list,它返回所有待审批的高权限单据。而审批接口POST /api/v1/approval/decide的逻辑是:

{  \"flow_id\": \"FLOW20260816001\",  \"decision\": \"approved\",  \"comment\": \"同意\"}

这个接口同样不校验当前用户是否拥有审批权限。我直接对一张采购单的审批流调用了这个接口,传了\"decision\":\"approved\"。采购单通过。我又尝试对一个“系统管理员权限申请”的审批单进行操作,同样通过。这意味着我可以自己批准自己的任何申请,包括提权申请。

最后我甚至发现,审批流中的“审批记录”字段也完全由前端控制。我可以伪造审批人、审批时间、审批意见。整个审批系统不仅被绕过,还被完全操控。

三、审批流绕过的四种典型技术形态

在2026年的业务系统里,审批流绕过已衍生出多种形态,我总结为四种最常见的技术模式:

形态一:状态参数直接篡改。 这是最基础的情况,就像上面的案例。前端请求体里直接包含status或state字段,服务端不做校验直接入库。测试时重点看提交接口和更新接口。

形态二:审批动作接口缺少权限校验。 这种情况里,状态流转有独立的接口(比如/approve、/decide),但接口不校验当前用户是否具有审批权限。攻击者可以自己批准自己的单子,或者批准别人的单子。

形态三:工作流引擎配置不当。 在Flowable、Activiti等BPM引擎驱动的系统里,审批流的节点跳转是由引擎控制的。但如果开发者在后端代码里手动调用了runtimeService.setVariable()来设置状态变量,而变量名和前端传的参数同名,就可能被覆盖。

形态四:状态枚举值与数据库实际值不一致。 前端展示的状态和数据库存储的状态不一致的情况也很常见。比如前端显示“已通过”,数据库里是1;前端显示“待审批”,数据库里是0。攻击者可以构造一个前端不存在的状态值(比如99或-1),导致系统进入未定义状态,某些情况下会触发“默认通过”的逻辑。

四、挖掘审批流漏洞的系统化方法

如果你想在SRC里挖这类漏洞,下面是我的完整流程:

第一步:枚举所有审批场景。 请假、报销、采购、合同、用章、权限申请……每一个场景的接口都要摸清楚。

第二步:抓包分析状态流转。 对每个审批流程,记录从提交到审批到归档的所有请求。重点看:状态字段是谁传的?审批接口的权限校验在哪一层?

第三步:逐一测试状态篡改。 对每个状态字段,尝试修改为其他状态值。比如提交时改成approved,审批时改成rejected,归档时改成pending。观察系统是否接受非法流转。

第四步:测试审批接口的水平越权。 用普通员工账号调用审批接口,尝试审批自己或他人的单据。重点关注那些“审批动作独立接口”的场景。

第五步:组合利用。 审批流绕过本身可能只是中危,但和其他漏洞组合就能升危。比如:

  • 审批绕过 + 报销金额篡改 = 直接骗钱
  • 审批绕过 + 采购单金额篡改 = 伪造采购单骗货
  • 审批绕过 + 文件上传 = 上传WebShell
  • 审批绕过 + 管理员权限申请 = 提权

第六步:检查状态值是否可预测。 如果系统使用数字枚举状态(0/1/2/3),尝试传负数、超大数、或者不在枚举范围内的值,看系统如何处理。

五、防御方案

服务端强制状态机。 状态流转必须由服务端动作触发,不能由客户端直接设置。具体实现方式:

  • 前端只传业务数据(请假类型、日期、原因),不传状态字段。
  • 服务端根据当前单据状态和操作者权限,计算下一个状态。
  • 状态流转逻辑用枚举控制,不能接受任意的字符串或数字。

审批接口严格鉴权。 每个审批动作必须校验:

  • 当前用户是否是审批流程中的合法审批人。
  • 当前用户是否拥有该审批节点的审批权限(角色、部门、层级)。
  • 单据当前状态是否允许该审批动作。

审批记录服务端生成。 审批人、审批时间、审批意见这些字段必须由服务端从会话中获取,不能由前端传入。

异常状态告警。 监控审批流中的非法状态流转,比如跳级审批、未经审批直接通过、状态回退等,发现异常立即告警。

工作流引擎安全配置。 使用BPM引擎时,确保引擎的权限模型与业务权限模型一致。不要在代码里硬编码状态变量赋值,尽量使用引擎提供的审批API。

六、写在最后

审批流是企业的核心控制机制,它决定了谁能花公司的钱、谁能签公司的合同、谁能进公司的门。一旦审批流被绕过,攻击者就不是在“入侵系统”了,而是在“成为系统的一部分”——他以合法用户的身份,用系统的正常功能,做出违背企业规则的操作。这种攻击最可怕的地方在于,它不会触发任何安全告警,因为一切都是“合规”的。

下次测业务系统,别只盯着SQL注入和XSS。找到那个“提交申请”的按钮,看看申请单的状态字段是不是你能控制的。如果是,那你离公司的钱袋子就不远了。

严正声明
本文所述技术仅用于合法授权的安全测试。所有案例均已脱敏处理,仅保留技术原理供学习参考。利用审批流漏洞进行未授权操作属于违法行为,与作者无关。请在SRC平台授权范围内进行测试,遵守法律法规。


我把“待审批”改成了“已通过”:审批流状态参数篡改实战,一张请假条打穿整个OA" 网络安全文章

我把“待审批”改成了“已通过”:审批流状态参数篡改实战,一张请假条打穿整个OA"

本文详细披露了企业OA系统审批流中状态参数可被前端篡改的漏洞。作者通过实战案例,演示了将请假申请状态从pending改为approved绕过审批,并进一步测试报销、采购等场景均成功。文章总结了四种典型绕过形态,提供了系统化的挖掘方法,并给出了服务端强制状态机、严格鉴权等防御方案。核心结论是审批流状态必须由服务端控制,不能信任客户端传入的参数。
Ruby4.0通用RCE反序列化Gadget链" 网络安全文章

Ruby4.0通用RCE反序列化Gadget链"

本文发布Ruby4.0.6通用RCE反序列化链,单次Marshal.load即可执行命令,向下兼容至3.3版本。该链利用RubyGems标准库新发现与旧gadget组合,通过Gem::SpecFetcher触发autoload,结合Time对象异常捕获机制绕过网络请求失败影响,最终实现文件下载与eval执行,无需额外依赖。
如何打造自动化漏洞挖掘Skills:从资产发现、PoC验证到证据链报告" 网络安全文章

如何打造自动化漏洞挖掘Skills:从资产发现、PoC验证到证据链报告"

本文介绍如何通过Skills、MCP和Agent构建自动化漏洞挖掘流水线,强调授权明确、范围可控和可审计性。核心是将高级工程师的方法论沉淀为可复用Skill,通过MCP连接安全工具,由Agent执行资产归一化、漏洞去重、PoC匹配和分级验证。文章提出六步法:授权范围校验、数据接入、告警关联、PoC匹配、分级验证与证据固化、图谱报告输出。关键建议包括结构化PoC为YAML、坚持最小权限原则、以及生成可交互攻击面图谱以回溯证据。
IonStack第三部分:用GhostLock获取Android17Root" 网络安全文章

IonStack第三部分:用GhostLock获取Android17Root"

本文详细介绍了利用GhostLock漏洞(CVE-2026-43499)在Android17上实现全球首个公开root的过程。通过回收栈内存、利用boot_id泄露KASLR地址、劫持ashmem文件操作以及使用pipe_buffer升级读写原语,最终修改cred结构体并绕过SELinux和seccomp,成功获取root权限。该研究展示了在启用CFI和KASLR的现代Android设备上实现提权的复杂技术路径。
评论:0   参与:  0