Skip to content

Latest commit

 

History

History
280 lines (175 loc) · 15.6 KB

Methdology_approach.md

File metadata and controls

280 lines (175 loc) · 15.6 KB

基于污点的方法论整理(by Claude)

0. 这篇文档在讲什么?

我们要把两件本质不同、但需要配合使用的东西理清楚:

  1. 污点模型(taint model)

    • 解决的是:"到底在找什么风险?"
    • 包括:不同抽象层次的「风险模板」
  2. 正向 / 反向 扫描方向(forward / reverse)

    • 解决的是:"让 LLM 用什么姿势来找?"
    • 它只是「提问方式」和「搜索风格」,和污点本身是正交的维度

核心观点:

「要找什么」(污点模型)和「怎么问」(正/反向)是两条互不混淆的轴,
扫描方法 = 污点模板 × 提问策略。


1. 污点分析:所有风险的共同内核

无论是哪种类型的漏洞,抽象到足够底层,其实都在问同一件事:

有没有一条路径,把「不可信 / 不受控的东西」传递到「敏感 / 危险的地方」,
并且这条路径中间缺少足够的约束或净化?

形式化一点可以写成:

如果存在:

  • 某个 Source(不可信输入 / 影响源)
  • 某个 Sink(危险操作 / 危险状态)
  • 一条从 Source → Sink 的数据流 / 控制流路径
  • 且路径上没有足够的 Sanitizer(验证 / 网关 / 保护措施)

那么:漏洞成立。

这就是「污点分析」的统一内核。


2. 六种抽象层次的污点模板

2.1 Invariant:数据层不变量

定义
指系统在任何「逻辑上允许的」状态下都应该成立的数据关系。如果某个时刻这些关系不成立,就说明数据已经被破坏,即是逻辑漏洞或状态污染。

污点框架

  • Source:任何能「修改状态」的操作(转账、增发、清算等)
  • Sink:Invariant 被违反的那个状态(不变量断言失败)
  • Sanitizer:在关键写操作前/后,对 invariant 的校验,或者通过严密的会计逻辑自动保证该关系绝不会被破坏

典型例子

代币系统中,某合约允许管理员直接修改 balance[user],但没有同步调整 totalSupply,也没有任何地方检查它们是否相等。则存在一条「写状态 → 不变量被破坏」的污点路径,最终导致 totalSupply ≠ Σ balances。

抵押借贷系统中,如果清算函数没有正确更新债务和抵押品的关系,可能导致某用户的 collateral_value < debt_value × 清算系数,违反了系统应始终保持的偿付能力不变量。


2.2 Assumption:业务层假设

定义
开发者在设计业务逻辑时「理所当然」认为始终成立、但代码本身未必能完全强制、或者只能部分强制的前提条件。更关注的是「流程顺序 / 角色 / 场景」,不单是数据等式。

污点框架

  • Source:各种外部调用入口、跨交易状态组合、时间 / 区块高度、不同角色的交互
  • Sink:AssumptionViolated 的情形,即业务假设被打破但危险操作仍然执行
  • Sanitizer:流程网关、访问控制(onlyOwner / onlyRole)、时间锁 / 队列 / 多签等

典型例子

借贷协议假设「用户必须先抵押,再调用借款函数」。但实际代码中 borrow() 函数没有检查抵押是否足够,直接假设前端会引导用户先抵押。攻击者直接调用 borrow() 就能在抵押不足的情况下成功借款,导致资金被盗。

DAO 治理系统假设「提案执行前必须经过公示期和投票」。但如果某个紧急执行函数绕过了这些检查,管理员或攻击者就能跳过治理流程直接执行危险操作。


2.3 Hypo / Expression:表达式级锚点

定义
在某个特定位置 / 表达式上,把它视为潜在污点锚点,然后问:「这个点会不会在某些输入/状态下,触发危险行为?」面向某一行或某一段具体表达式,例如一次 call、一次 delegatecall、一次敏感除法、一个价格读取。

污点框架

  • Source:这条表达式依赖的、可能来源于用户 / 外部系统的变量
  • Sink:该表达式本身就是 Sink(例如外部调用 / 敏感写操作 / 权限变更)
  • Sanitizer:表达式前后的安全包裹,如 access control、重入锁、白名单检查、数值上界检查等

典型例子

某函数中存在 externalContract.call{value: amount}("");。如果 amount 直接来自用户参数,状态在转账后才更新余额,且没有重入锁,则存在一条从「用户控制变量」到「危险调用」的污点路径,可能导致重入攻击。

某价格计算中存在 price = reserves0 / reserves1。如果 reserves 来自可被闪电贷操纵的池子,且计算前没有 TWAP 或其他价格平滑机制,则该除法表达式就是一个危险的 Sink。


2.4 Temporal:时序 / 状态机

定义
关注系统在不同阶段 / 状态下的转换逻辑。核心问题是:"能否在不该进入某状态时强行进入?能否跳过必要阶段?能否回退到已结束状态?"

污点框架

  • Source:触发状态转换的外部调用、时间条件、投票结果
  • Sink:状态机进入非法状态 / 跳过关键阶段 / 时间倒流
  • Sanitizer:状态转换网关、阶段锁、不可逆标志位、时间检查

典型例子

众筹合约设计为「未达到目标金额前不能提款」。但如果提款函数只检查了时间截止,没有检查是否达标,则攻击者可以在众筹失败的情况下仍然提走资金,跳过了「达标验证」这个必要阶段。

NFT mint 设计为「白名单阶段 → 公售阶段」的两阶段模式。如果公售函数没有检查当前是否已过白名单期,或者白名单期标志位可以被重置,则可能出现时序混乱,提前开放公售或白名单用户重复 mint。

游戏合约中,玩家状态应该是「注册 → 游戏中 → 已结束」的单向流转。如果缺少状态检查,玩家可能在游戏已结束后重新进入游戏中状态,重复领取奖励。


2.5 Composition:组合性

定义
关注多个操作、多个合约、多个协议交互时产生的非预期组合效应。单独看每个组件可能安全,但特定组合顺序/条件下会触发漏洞。核心问题:"A 和 B 各自安全,但 A→B 或在 C 条件下同时执行 A+B 会出问题吗?"

污点框架

  • Source:多笔交易的组合、闪电贷 + 正常业务、多个 modifier 叠加、继承链上的函数覆盖
  • Sink:组合后产生的意外状态(价格被操纵、权限被绕过、重入窗口被打开)
  • Sanitizer:原子性保证、组合前的一致性检查、明确的执行顺序约束

典型例子

闪电贷 + DEX swap 的组合攻击。单独看,闪电贷协议安全,DEX 的 swap 函数也安全。但攻击者在单笔交易内先用闪电贷借入大量代币,操纵 DEX 池子价格,再执行套利操作,最后归还闪电贷。这个组合产生了「瞬时价格操纵」这个单独操作不会出现的问题。

多重继承导致的权限绕过。合约 A 和 B 都有 onlyOwner 修饰的函数,子合约 C 继承 A 和 B。如果 C 中某个函数覆盖了父合约的实现但没有保留 onlyOwner 检查,则原本需要 owner 权限的操作被绕过。

approve + transferFrom 的组合。用户先 approve 给某个合约,该合约在回调中调用 transferFrom。如果这两个操作的组合没有被充分考虑,可能导致重入或授权滥用。


2.6 Boundary:边界条件

定义
关注极端输入、边界值、溢出、下溢、空集合等边缘情况下的行为。核心问题:"0、MAX、空数组、第一个/最后一个元素时会发生什么?"

污点框架

  • Source:用户可控的数值参数、数组长度、循环边界
  • Sink:除零、下溢导致巨大数值、数组越界、gas 耗尽
  • Sanitizer:边界检查(>0, <MAX)、SafeMath、数组长度验证、gas limit 预估

典型例子

转账函数收取 1% 手续费,计算为 fee = amount * 1 / 100。当 amount = 0 时,fee = 0,可能绕过最小手续费要求,或者在某些业务逻辑中被视为「免费操作」而触发非预期行为。

代币铸造函数没有检查 totalSupply + amount 是否溢出。当 totalSupply 接近 type(uint256).max 时,再铸造会导致溢出回绕到一个很小的值,破坏供应量不变量。

批量操作函数遍历数组 for (uint i = 0; i < users.length; i++)。如果 users 是空数组,循环直接跳过,函数返回成功,可能导致某些必要的初始化或检查被绕过。或者数组过大导致 gas 耗尽,函数无法完成。


3. 把六类风险统一成「污点模板」

基于上面六种类型,我们可以把「要找什么」抽象成一组污点模板:

每个模板都描述:

  • 允许哪些东西被当作 Source
  • 哪些情况被视为危险 Sink
  • 哪些检查 / 控制算作有效 Sanitizer
模板类型 关注点 Source 例子 Sink 例子 Sanitizer 例子
Invariant 数据一致性 任意状态写操作 totalSupply != Σbalances 等不变量被破坏 require / 自动平衡逻辑
Assumption 业务流程 / 场景 跨交易操作、跨角色调用、时间 / 阶段变化 未满足「先A再B」的流程就执行了 B / 危险业务动作 访问控制、流程开关、时间锁、多签等
Hypo / Expression 具体表达式 / 行 表达式输入变量,用户参数 / 外部返回值 这行本身:call / delegatecall / 写权限 / 分红操作 周围的 onlyOwner、锁、额度 / 范围检查等
Temporal 状态机/时序 触发状态转换的外部调用、时间条件、投票结果 状态机进入非法状态 / 跳过关键阶段 / 时间倒流 状态转换网关、阶段锁、不可逆标志位、时间检查
Composition 操作组合 多笔交易组合、闪电贷 + 正常业务、多个 modifier 叠加 组合后产生的意外状态(价格操纵、权限绕过、重入) 原子性保证、组合前一致性检查、执行顺序约束
Boundary 边界条件 用户可控的数值参数、数组长度、循环边界 除零、溢出、下溢、数组越界、gas 耗尽 边界检查、SafeMath、数组长度验证、gas limit 预估

从实现上,可以想象我们维护的是一个「模板库」,所有模板最后都会被翻译成:

某种「Source → Sink 是否有未净化路径?」的判定问题。


4. 正向 / 反向:只是提问方式,不改变「要找什么」

搞清楚污点模型之后,再来看正向 / 反向扫描。

4.1 正向(Forward):询问式

心智模式:

「假设可能有问题,也可能没问题;你帮我从头到尾检查一下,看看是否存在符合此模板的漏洞。」

  • 给 LLM 的信号是:你可以说「没有问题」,但要给理由
  • 分析路径:从 Source 出发,向前追踪数据 / 控制流,看能否到达 Sink,同时检查路径上是否有合格 Sanitizer
  • 适合用途:做「全面体检」,不预设一定有病,只是按模板做检查

举个业务 Assumption 模板上的例子:

模板:假设「用户抵押不足时,不应允许借款操作」。

正向提问:「请基于这个 Assumption,检查代码中所有与借款相关的路径,看是否存在在抵押不足的情况下仍能成功借款的路径。」

LLM 会尝试遍历相关函数,列举调用顺序、条件分支,最后给出「有 / 无」结论。


4.2 反向(Reverse):断言式

心智模式:

「我们已经知道这里存在符合某个模板的漏洞;你的任务是:找到具体是哪里,怎么利用,影响多大。」

  • 给 LLM 的信号是:不允许"轻易宣称没问题",必须努力去构造一条满足模板的路径
  • 分析路径:从预设的目标问题倒推必要条件,再在代码中寻找满足这些条件的路径
  • 适合用途:集中精力在某类风险上"打穿",提高检出率;也适合在看到业务文档后,针对某个特定风险做「命题攻击」

同样用上面借贷的例子:

模板还是:「抵押不足的情况下,不应允许借款。」

反向提问:「系统中已经存在一种场景:用户在抵押不足的情况下仍能成功借款。请你在下面的代码中证明这一点,给出:1)涉及的函数与状态变量;2)调用顺序;3)需要满足的前置条件;4)攻击者如何从 0 到完成攻击。」

LLM 会带着「一定有问题」的心理去检索路径,尝试构造尽量完整、一致的攻击流程。


4.3 关键:方向不改变「污点模板」

不管是正向还是反向:

  • 不改变:污点模板本身(Source、Sink、Sanitizer 的定义)以及这六种抽象层次
  • 只改变:LLM 的「搜索动力」和「解释结构」
    • 正向:强调「是否存在?」
    • 反向:强调「已经存在,请你证明、构造路径」

所以,两者关系可以抽象成:

漏洞扫描方法 = 污点模板(What to find) × 提问方向(How to query LLM)


5. 实际使用时的工作流

可以把整个扫描过程理解为「双重循环」:

  1. 在污点维度上遍历:针对不同抽象层次的模板(Invariant / Assumption / Hypo / Temporal / Composition / Boundary)

  2. 在方向维度上选择策略:有的模板先用正向做「是否存在」筛查;对某些高危模板,再用反向做「你已经中招了,请给出利用场景」

举个简化版工作流例子:

第一步:用一批表达式级 Hypo 模板(比如外部调用、低级别 call 等),做一轮正向扫描,收集所有「疑似危险行」。

第二步:对每个疑似点,再套上对应的 Assumption / Invariant / Temporal 模板,用反向方式让 LLM 主动构造完整攻击路径。

第三步:对 Composition 和 Boundary 这类容易被忽视的模板,专门设计针对性的正向检查清单,确保覆盖各种组合场景和边界情况。

第四步:对明确的 invariant / assumption 风险,再单独写成「规则 + 解释」作为审计报告的一部分。

这样做的好处是:

  • 污点模板层:清晰表达我们到底想要防的是什么(资金安全?权限?数据一致性?业务流程?时序正确性?组合安全?边界健壮性?)
  • 正/反向层:只是「在和 LLM 交互的时候,换一种思路提问」,用来提高覆盖率和检出率

6. 总结:一个统一的心智模型

可以用一句话概括这整篇文档的结构:

  1. 底层统一逻辑:所有安全问题都可以视为一种「污点路径」:不可信 Source → 危险 Sink,中间缺失 Sanitizer

  2. 六种抽象层次的污点模板:

    • Invariant:数据恒等式 / 一致性
    • Assumption:业务流程假设 / 场景约束
    • Hypo/Expression:具体行级危险点
    • Temporal:状态机 / 时序正确性
    • Composition:多操作组合的非预期效应
    • Boundary:极端值 / 边界情况
  3. 正向 / 反向是正交维度:不改变「污点模板」本身,只改变 LLM 的推理姿势和回答结构