漏洞思维(1):从人的业务规则,到代码世界里的失效假设

前言

前两篇写信息搜集的时候,我慢慢想通了一件事:

互联网上的资产不是凭空出现的,而是业务为了运行,被迫留下来的投影。

企业要让用户访问,就需要域名;要让浏览器信任,就需要证书;要让系统协作,就需要 API、回调、DNS;要持续运营,就会有后台、监控、文档、测试环境。

所以后来我做信息搜集的时候,已经不太喜欢问:

“还有什么工具可以跑?”

而更喜欢问:

“这个业务为了跑起来,必须留下什么?”

但是当我真正开始挖漏洞以后,我发现自己又遇到了几乎一样的问题。

很多教程会告诉我:

  • 越权要改 ID;
  • 支付漏洞要改金额;
  • 验证码要看能不能重复使用;
  • 密码重置要看流程能不能绕;
  • 文件上传要看后端有没有校验;
  • SQL 注入要找用户输入进入 SQL 的位置。

这些东西当然有用。

但它们仍然让我产生一种熟悉的别扭感:

我知道怎么测试,却不知道为什么我要这么测试。

为什么改 ID 会产生越权?

为什么重复请求可能产生优惠券漏洞?

为什么跳过第二步直接调用第三步,有时候就能绕过业务限制?

为什么前端明明限制了一个按钮,直接请求接口以后限制却消失了?

如果我只记:

“看到 ID 就改一下。”

那么我仍然是在背技巧。

我真正想知道的是:

一个正常业务,究竟是在什么地方开始变成漏洞的?

于是我开始换一个角度。

这一次,不从“攻击者能做什么”开始。

而从:

业务本来想保证什么?

开始。



第一部分:先建立“人的业务世界”

这一部分只回答一件事:

在人看来,这个系统正确的时候应该是什么样?

先把现实目标、业务规则和业务不变量想清楚,后面才有标准去判断代码到底有没有做错。


一、先把“业务”这个词说清楚

写到这里,我发现其实还有一个词必须先讲清楚:

什么叫业务?

以前我一听“业务”这个词,会觉得它很虚。

好像是产品经理、运营、公司领导才会说的东西。

但后来我慢慢发现,如果我连“业务”是什么都没有真正理解,后面讲:

业务规则
业务不变量
业务流程
业务漏洞

其实都会变成几个听起来很高级、但脑子里没有具体画面的词。

所以我后来给“业务”下了一个很简单的定义:

业务,就是一个组织为了完成某个现实目标,让不同的人按照一定规则去操作某些对象的一整套过程。

比如学校为什么要做一个请假系统?

不是因为学校想做:

一个请假页面

也不是因为学校想做:

几个 POST 接口

学校真正想解决的是:

学生临时不能上课
↓
需要提出请假
↓
学校需要知道请假的原因和时间
↓
对应老师 / 辅导员需要审核
↓
审核通过以后
这次缺勤才被认为是合理的

这整件事情,才叫:

请假业务

所以:

页面
按钮
接口
数据库

都不是业务本身。

它们只是:

程序为了把现实业务搬到计算机里而做出来的实现。

再比如订单系统。

它真正的业务也不是:

订单列表页
订单详情页
支付接口
退款接口

真正的业务是:

用户挑选商品
↓
确认购买
↓
系统确定商品和价格
↓
用户付款
↓
商家履约
↓
用户收货
↓
特殊情况下退款 / 售后

所以以后我看一个系统,我会先把技术界面拿掉。

不先看:

URL 是什么
参数是什么
接口叫什么

而先问:

这个系统到底在帮谁完成什么现实中的事情?

因为只要回答了这个问题,后面的角色、对象和规则就会慢慢出现。

例如请假业务:

谁?
学生、辅导员

操作什么?
请假申请

做什么?
创建、提交、审核、通过、拒绝

为什么要这样做?
为了让学校能够判断一次缺勤是否合理

于是我发现,一个业务最少可以拆成四样东西:

人
+
对象
+
动作
+
规则

也可以把它理解成:

谁
↓
对什么东西
↓
做什么
↓
在什么规则下做

比如:

学生
↓
操作自己的请假申请
↓
提交
↓
提交以后等待辅导员审核

这里就已经开始出现业务规则了。

所以我现在会把:

业务

理解成:

人按照规则操作对象,最终完成一个现实目标。

而所谓:

业务系统

其实就是程序把这套现实关系搬进了计算机。

这也是为什么漏洞最终一定会回到“业务规则”。

因为攻击者真正突破的,通常不是:

一个页面
一个按钮
一个参数

而是:

现实业务原本规定
某个人
在某种条件下
只能对某个对象
做某些事情

程序却没有把这条规则完整地保证下来。

所以后面再看到:

业务规则

我脑子里不会再把它理解成一个抽象词。

我会先想到:

这个系统到底在帮谁做什么?

为了让这件事正常成立,
有哪些规则必须一直成立?

从这里开始,才真正进入漏洞思维。


二、漏洞到底是什么

以前我理解漏洞,容易把它想成:

程序写错了。

后来我觉得这个解释太宽了。

因为程序里每天都有 Bug,但不是每个 Bug 都是安全漏洞。

按钮错位是 Bug。

页面颜色不对是 Bug。

计算结果偶尔显示错误也是 Bug。

但安全漏洞通常有一个更明显的特点:

系统原本想维持的一条安全规则,被用户突破了。

比如学校成绩系统。

它真正的业务规则并不是:

学生可以查询成绩。

完整规则其实是:

学生可以查询自己的成绩,但不能查询其他学生的成绩。

这里面其实存在一条隐藏规则:

用户 A
只能访问
属于用户 A 的成绩

如果有一天:

用户 A
可以访问
属于用户 B 的成绩

那么真正发生的事情不是:

参数被修改了。

而是:

系统原本应该维持的业务约束被破坏了。

这就是我现在理解漏洞的第一句话:

漏洞,本质上是系统应该维持的安全约束,在某种输入、身份、状态或流程下失效了。



三、业务不是由页面组成的,而是由规则组成的

站在普通用户角度看一个系统,我们看到的是页面:

登录
首页
成绩查询
修改资料
提交申请
退出登录

但是站在业务人员角度,这套系统其实是一堆规则。

比如学校系统。

1. 登录

业务规则:

只有能够证明自己身份的人
才能进入对应账号

2. 查询成绩

业务规则:

学生只能查看自己的成绩
教师可以查看自己负责班级的成绩
管理员可以查看全部成绩

3. 修改成绩

业务规则:

学生不能修改成绩
普通教师只能修改自己课程的成绩
特定管理员拥有更高权限

4. 提交申请

业务规则:

只有满足条件的学生才能申请
同一申请不能无限重复提交
提交之后进入待审核状态
审核完成后不能随意重新修改

突然会发现:

一个系统真正复杂的地方,不是有多少页面,而是有多少规则。

页面只是规则的外壳。

API 是规则的执行入口。

数据库则负责保存规则运行之后产生的状态。

所以以后看一个功能,我认为第一个问题不应该是:

这里有什么参数?

而应该是:

这个功能在业务上想保证什么?



四、把必须始终成立的约束叫作业务不变量

前一节讲“业务规则”,我理解的是:

正常业务应该怎样运行。

例如谁可以做什么、在什么条件下允许做、流程应该怎么往下走。

但是继续往下,我开始接触到一个非常有用的概念:

不变量。

业务不变量不是简单等于“业务不能做什么”。

更准确地说,它描述的是:

正常业务运行过程中,无论用户怎么操作,都必须始终成立的约束。

为了做安全分析,我经常会把它换一个角度问:

什么事情绝对不能发生?

比如:

学生永远不能查看其他学生的私人信息。

这是一条不变量。

一张只能使用一次的优惠券,不能被成功使用两次。

也是。

订单支付金额必须等于服务器计算出的订单金额。

也是。

密码重置只能修改已经通过身份验证的那个账号。

也是。

普通用户不能获得管理员权限。

还是。

于是漏洞测试突然变得特别清楚。

我不再首先问:

有没有越权?

而是先写出:

应该永远成立:
A 只能访问 A 的数据。

然后故意寻找:

有没有一种情况,
能让这个规则不成立?

漏洞挖掘,其实开始变成了一种:

寻找业务不变量反例的过程。



五、为什么漏洞分析之前要先理解业务

前面一路追下来,我先得到了一个很重要的东西:

业务目标
↓
业务规则
↓
业务不变量

也就是说,我开始知道:

在人看来,这个系统“正确的时候”应该是什么样。

这也是为什么业务分析有价值。

它不是为了让我变成产品经理,而是为了先给漏洞判断找到一个标准。

如果我连:

什么应该允许
什么必须拒绝
什么约束必须始终成立

都不知道,

那后面就算看到接口行为异常,我也很难判断它到底是不是安全问题。

但到这里还只解决了一半。

因为业务规则最终不能只存在于人的理解里。

真正执行这些规则的,是代码。

所以接下来我要继续追:

人脑里的业务规则,到底是怎么被翻译成程序判断的?



第二部分:进入“代码的业务世界”

人的业务世界已经告诉我“正确答案应该是什么”。

接下来我要追的是:

代码到底靠什么,把这些业务规则真正执行出来?


六、业务规则最终必须被翻译成代码

但这里还有一层特别重要。

业务人员说:

一人只能领取一次优惠券。

计算机根本听不懂。

开发必须把它翻译成代码。

可能变成:

收到领取请求
↓
检查用户有没有领取过
↓
没有
↓
发放优惠券
↓
记录“已经领取”

业务说:

学生只能看自己的成绩。

开发可能翻译成:

用户请求成绩
↓
获取 studentId
↓
查询数据库
↓
返回成绩

问题就出现在这里。

业务规则是人类语言,而最终执行规则的是代码。

从:

业务想要什么

变成:

程序具体怎么判断

中间需要经过一次“翻译”。

而安全漏洞,经常就出现在这个翻译过程中。


这里是全文的分界:从“人的业务世界”进入“代码的业务世界”

写到这里,我发现前面的内容其实一直在回答:

人认为这个系统应该怎么工作?

也就是:

人的业务世界

现实目标
↓
业务规则
↓
业务不变量

例如:

学生只能查看自己的成绩

在人看来,这句话非常自然。

但计算机不能直接执行“只能查看自己的成绩”。

开发必须把它拆成程序真正能够判断的问题:

当前用户是谁?
↓
请求的是哪份成绩?
↓
这份成绩属于谁?
↓
当前用户和目标成绩是什么关系?
↓
允许 / 拒绝

所以从这里开始,我的问题发生了变化。

前面我在问:

正确世界应该是什么样?

现在我要问:

代码到底靠什么维持这个正确世界?

可以把两边理解成:

人的业务世界
→ 告诉我“正确结果应该是什么”

代码的业务世界
→ 真正决定“这次请求到底允不允许”

这也是整篇文章真正进入代码层的地方。

后面我要继续追的,不再只是业务本身,而是:

代码需要知道哪些事实?

这些事实从哪里来?

程序相信哪些事实?

程序为什么认为这些判断已经足够?

这些假设能不能被用户打破?

所以我现在会把这个分界记成一句话:

业务不变量告诉我系统必须维持什么;代码实现告诉我系统靠什么去维持它。

到这里,我又把“找漏洞”这件事想得更具体了一点。

以前我会觉得:

找漏洞
=
找代码哪里写错了

现在我觉得更准确的理解应该是:

找漏洞,就是进入代码世界,检查开发为了实现人的业务规则,所写出来的事实获取、条件判断和状态操作,是否真的满足了业务不变量这个安全标准。

例如人的业务世界规定:

学生只能查看自己的成绩

我先把它转换成业务不变量:

学生 A
绝对不能查看
学生 B 的成绩

接下来进入代码世界。

代码为了实现这条规则,必须完成类似这样的过程:

获取当前用户身份
↓
确定目标成绩对象
↓
读取目标成绩属于谁
↓
比较当前用户和资源所有者
↓
允许 / 拒绝

于是我真正要检查的就不是:

这里有没有一个 ID 可以改

而是:

代码为了维持这条业务不变量
到底做了哪些判断?

这些判断够不够?

判断依赖的事实从哪里来?

有没有漏掉必要条件?

最终代码行为是否真的符合业务不变量?

所以两边真正连接起来的是:

人的业务世界
↓
提出业务规则
↓
提炼业务不变量
↓
================
代码的业务世界
↓
获取事实
↓
条件判断
↓
状态 / 业务动作
↓
================
再用业务不变量检查结果

如果:

业务不变量要求:
A 不能看 B

代码实际也做到:

A → B
拒绝

说明:

代码实现
符合
业务不变量

如果代码实际变成:

A → B
允许

那么:

代码实现
没有维持住
业务不变量
↓
人的业务逻辑
≠
代码实际逻辑
↓
漏洞

所以我现在会把业务不变量理解成:

连接人的业务世界和代码世界的一把“安全尺子”。

人的业务世界告诉我:

什么结果才是正确的

代码世界告诉我:

程序实际上是怎么做的

业务不变量负责:

拿这两个结果进行对照

而漏洞,就是这种对照中出现的:

安全相关的不一致


七、代码为了实现规则,必须相信一些东西

再往下追,我发现一个特别重要的东西:

信任。

假设系统收到:

studentId=20260001

服务器必须决定:

这个 studentId 能不能相信?

订单接口收到:

price=100

服务器也必须判断:

这个价格是谁算出来的?

权限接口收到:

role=user

服务器必须知道:

角色信息来自数据库,还是用户自己提交的?

每一个业务功能背后,其实都有很多类似的问题:

我相信谁告诉我的用户身份?

我相信谁告诉我的价格?

我相信谁告诉我的订单状态?

我相信谁告诉我的资源属于谁?

我相信谁告诉我这一步已经完成?

我相信谁告诉我验证码已经验证?

而这里就出现了我认为漏洞思维里特别核心的一句话:

安全问题往往不是“有没有检查”,而是“系统把什么当成了可信事实”。

这正是前面“人的业务世界”和“代码业务世界”开始产生具体差异的地方。

人的业务世界只会说:

学生只能查看自己的成绩

而代码真正要做的是判断:

你是谁?
这份成绩是谁的?
我凭什么相信这两个事实?

所以“信任”不是一个突然出现的新概念。

它是在继续回答:

代码到底靠什么维持业务不变量?



八、漏洞经常来自错误的隐含假设

继续往下想,我发现很多漏洞背后都有一句开发人员没有真正说出口的话。

比如:

用户应该不会修改这个参数。

或者:

用户必须先经过前面的页面才能来到这里。

或者:

这个接口只有前端会调用。

或者:

正常情况下一个人不会同时点击两次。

或者:

前端已经限制最大金额了。

或者:

普通用户看不到管理员按钮,所以不会调用管理员接口。

这些东西,我现在习惯叫:

隐含假设。

系统设计者觉得它理所当然成立,于是没有真正用服务器端规则去保证。

攻击者做的事情,很多时候特别简单:

故意让这个假设不成立。

所以漏洞形成的完整链条就出来了:

业务目标
↓
业务规则
↓
程序实现
↓
程序依赖某个假设
↓
这个假设实际上可以被用户影响
↓
攻击者让假设失效
↓
业务规则被突破
↓
漏洞产生

这条链,是我认为理解漏洞最重要的一条链。

因为它把全文重新闭合了:

人的业务世界定义规则
↓
业务不变量给出安全标准
↓
代码尝试实现这些标准
↓
代码依赖事实和假设
↓
用户让其中一个假设失效
↓
代码世界偏离人的业务世界
↓
业务不变量被破坏
↓
漏洞


第三部分:把整篇文章收回来

到这里不再增加新概念,只做两件事:

  1. 把前三篇放回同一条学习主线;
  2. 用我自己的话,把这一篇从头到尾再串一次。

九、把前三篇放回同一条主线

写到这里,我发现前三篇其实一直在追同一个东西:

为什么?

第一篇问:

企业为什么会暴露资产?

因为业务要运行。

第二篇问:

为什么能够通过公开信息发现这些资产?

因为业务运行一定会留下痕迹。

而这一篇继续问:

为什么正常业务会变成漏洞?

因为业务规则最终必须被程序实现,而程序又必须依赖身份、对象、状态、数据、顺序和信任去做判断。

只要其中某个本应成立的条件没有被真正保证,业务不变量就可能失效。

所以这三篇真正训练的从来不是:

工具
漏洞名称
Payload
固定测试动作

而是不断追:

这个东西为什么存在?

系统为什么这样判断?

它凭什么相信这个事实?

这条规则到底靠什么保证?

我越来越觉得:

理解系统为什么能够正常成立,比单纯记住“这里应该试什么”更重要。



十、最后,我用自己的理解把整篇文章再串一次

我现在理解,所谓业务,其实就是人为了实现某个现实目标、解决某个现实问题而设计出来的一套流程和规则。

比如:

学生查成绩
用户下单
申请审核
优惠券领取
密码重置

这些东西首先存在于:

人的需求
制度
产品设计
业务人员和开发人员的理解

但是程序本身根本不懂:

学生只能看自己的成绩

一张优惠券只能使用一次

没有审核通过的申请不能直接生效

这些都是人的业务语言。

所以开发人员必须把:

人的业务世界

翻译成:

代码的业务世界

也就是说,真正把业务执行出来的不是人的想法,而是代码。

所以我现在也明白了,为什么做漏洞分析之前要先理解业务。

因为如果我连:

这个系统正常情况下到底应该怎么工作

都不知道,

那我就没有办法判断:

程序实际做出来的结果
到底是不是错的

所以我先通过业务分析去找:

核心业务对象

再找:

围绕这个对象
哪些规则绝对不能被破坏

这些规则就是:

业务不变量

业务规则更多是在告诉我:

正常情况下
谁可以做什么

而业务不变量是在告诉我:

无论用户怎么操作
哪些安全约束都必须一直成立

例如:

A 不能查看 B 的私人数据

一张只能使用一次的优惠券
不能成功使用两次

未审核通过的申请
不能直接生效

这样我就先拿到了:

业务世界里的“正确答案”

接下来才真正开始进入代码世界。

开发为了实现:

学生只能查看自己的成绩

代码就必须知道:

当前用户是谁?

目标成绩属于谁?

当前用户和这份成绩是什么关系?

为了实现:

订单必须按照正确金额支付

代码就必须知道:

真正的订单金额是多少?

为了实现:

验证码只能验证指定账号

代码就必须知道:

这个验证结果到底属于哪个账号?

所以代码为了维持业务不变量,必须依赖很多事实:

身份
对象归属
状态
金额
验证结果
流程是否完成

这时候就会出现一个非常关键的问题:

代码到底把什么当成了可信事实?这些事实是谁告诉它的?

如果本来应该由服务器确认的事实,

却直接相信了客户端,

例如:

studentId
price
role
status
verified

那么人的业务世界里原本认为理所当然成立的规则,

在代码世界里可能根本没有被真正保证。

再往下就是:

隐含假设

开发可能认为:

用户不会修改这个参数

用户一定会按照页面顺序操作

前端看不到管理员按钮
普通用户就不会调用管理员接口

正常用户不会同时发送两次请求

这些在人脑里听起来都很合理。

但如果代码没有真正保证,

那么攻击者做的事情其实就是:

故意让这些假设不成立。

所以整篇文章最后就可以串成:

现实业务目标
↓
业务规则
↓
业务不变量
↓
开发把规则翻译成代码
↓
代码依赖身份、对象、状态、顺序、数据和信任做判断
↓
某个条件被遗漏
或者某个事实被错误信任
↓
人的业务逻辑
≠
代码真正执行的业务逻辑
↓
业务不变量被破坏
↓
漏洞出现

所以我现在再看开头那句话:

漏洞,本质上是系统应该维持的安全约束,在某种输入、身份、状态或流程下失效了。

就能真正理解它了。

如果从最下面看:

代码层

是漏洞真正发生的具体位置。

如果往上一层看:

业务不变量层

是被破坏的安全约束。

如果继续往最上面看:

人的业务世界

就是为什么这条约束本来必须存在。

所以我现在理解:

业务分析告诉我“正确世界应该是什么样”;代码分析告诉我“程序实际上靠什么维持这个世界”;漏洞分析,就是寻找这两个世界之间能够让业务不变量失效的差异。

我做业务分析,不是为了把自己变成产品经理。

而是为了先知道:

系统本来应该保证什么

然后再去问:

代码真的保证了吗?

我现在觉得,这才是整篇文章真正想讲清楚的东西。


结语

如果让我最后只留下一个公式,我会写成:

业务目标
↓
业务规则
↓
业务不变量
↓
程序实现
↓
身份 / 对象 / 状态 / 顺序 / 数据 / 信任
↓
隐含假设
↓
假设失效
↓
不变量被破坏
↓
漏洞

所以下一次面对一个陌生系统,我不想先问:

“这里能打什么漏洞?”

我更想先问:

这个系统究竟在努力保证哪些事情始终成立、哪些事情永远不能发生?

然后再问:

代码凭什么认为这些规则一定不会被突破?

我现在认为,真正的漏洞思维,就是从这两个问题开始的。


注:在 SRC 场景中,理解系统可以无限深入,但实际验证必须始终限制在明确授权的资产、账号、功能和测试规则之内。思维可以往外推,测试不能越界。


最终总图:全文漏洞思维 ASCII 流程

┌──────────────────────────────┐
│        现实业务目标          │
│  这个系统为什么存在?        │
└──────────────┬───────────────┘
               │
               ▼
┌──────────────────────────────┐
│          业务规则            │
│  正常情况下应该怎么运行?    │
└──────────────┬───────────────┘
               │
               ▼
┌──────────────────────────────┐
│        业务不变量            │
│ 哪些安全约束必须始终成立?   │
│ 哪些事情绝对不能发生?       │
└──────────────┬───────────────┘
               │
               │  作为“安全标准”
               ▼
════════════════════════════════
         从人的业务世界
         进入代码业务世界
════════════════════════════════
               │
               ▼
┌──────────────────────────────┐
│         程序具体实现         │
│ 为了实现规则,代码需要什么? │
└──────────────┬───────────────┘
               │
               ▼
┌──────────────────────────────┐
│          获取事实            │
│                              │
│ 当前用户是谁?               │
│ 目标对象是谁的?             │
│ 当前状态是什么?             │
│ 金额是多少?                 │
│ 前一步是否完成?             │
└──────────────┬───────────────┘
               │
               ▼
┌──────────────────────────────┐
│          信任来源            │
│                              │
│ 这些事实是谁告诉服务器的?   │
│ 客户端?数据库?Session?     │
│ 服务端计算结果?             │
└──────────────┬───────────────┘
               │
               ▼
┌──────────────────────────────┐
│          条件判断            │
│                              │
│ 身份是否正确?               │
│ 对象是否属于当前用户?       │
│ 状态是否允许?               │
│ 顺序是否正确?               │
│ 权限关系是否成立?           │
└──────────────┬───────────────┘
               │
               ▼
┌──────────────────────────────┐
│        执行业务动作          │
│                              │
│ 允许 / 拒绝                  │
│ 查询 / 修改                  │
│ 状态转换                     │
│ 发券 / 扣款 / 审核           │
└──────────────┬───────────────┘
               │
               ▼
┌──────────────────────────────┐
│      用业务不变量进行对照    │
│                              │
│ 代码实际行为                 │
│        VS                    │
│ 人的业务世界要求             │
└──────────────┬───────────────┘
               │
        ┌──────┴──────┐
        │             │
        ▼             ▼
┌───────────────┐  ┌────────────────┐
│   两者一致    │  │    两者不一致   │
│               │  │                │
│ 规则被正确维持│  │ 安全约束可能失效│
└───────────────┘  └────────┬───────┘
                             │
                             ▼
                 ┌────────────────────┐
                 │   继续追失效原因   │
                 │                    │
                 │ 条件遗漏?         │
                 │ 错误信任?         │
                 │ 状态判断缺失?     │
                 │ 流程假设错误?     │
                 │ 权限关系遗漏?     │
                 │ 并发 / 时间问题?  │
                 └─────────┬──────────┘
                           │
                           ▼
                 ┌────────────────────┐
                 │      假设失效      │
                 └─────────┬──────────┘
                           │
                           ▼
                 ┌────────────────────┐
                 │  业务不变量被破坏  │
                 └─────────┬──────────┘
                           │
                           ▼
                 ┌────────────────────┐
                 │        漏洞        │
                 └────────────────────┘

如果再把整张图压缩成一句话:

业务分析
→ 告诉我“正确世界应该是什么样”

业务不变量
→ 给我一把衡量安全的尺子

代码分析
→ 告诉我程序实际上靠什么维持这个世界

漏洞分析
→ 找到代码实际行为和业务不变量之间的安全差异
posted @ 2026-09-04 18:27  0xMouise  阅读(10)  评论(0)    收藏  举报