10分钟智能合约:进阶实战-9.4 中危漏洞审查

15 阅读10分钟

欢迎订阅专栏10分钟智能合约:进阶实战

中危漏洞审查:从“可忽略”到“组合利用”的关键防线

中危漏洞在审计报告中常被低估,但它们是攻击链中最容易被串联利用的“短板”。单个中危漏洞可能只能造成有限影响,但两个或三个中危漏洞组合起来,往往可以构成一条完整的攻击路径,造成严重后果。

本文聚焦于中危漏洞的分类审查方法、实战识别技巧及组合风险评估策略。

一、中危漏洞的定位与分级标准

审计行业对中危漏洞的通用定义:在特定条件下可能导致资金损失或功能异常,但利用门槛较高(需要特定权限、特定市场状态、或与其他漏洞配合)

分级参考标准:

等级判定依据典型场景
中危(Medium)满足部分条件:需要较高信任假设/需要特定外部状态/影响范围有限/存在缓解措施可降低风险特定用户可被 DoS、条件性授权过高、价格精度损失累积
中高危(Medium-High)介于中危和高危之间,可能在高危漏洞配合下升级为严重风险特定情形下的重入(需特定代币合约配合)、治理提案无时间锁但需多签
中低危(Medium-Low)接近低危,但影响经济模型或用户体验Gas 优化不当导致用户成本过高、事件缺失影响监控效率

审查核心原则:不单独评估漏洞等级,而是评估所有中危漏洞叠加后的“组合杀伤力”

二、中危漏洞核心审查清单

类别 A:访问控制相关(中等风险)

漏洞模式危害审查要点
初始化函数可被抢先调用(但已有防护)低-中检查是否使用了 initializer 修饰符,且调用者是否限于部署者
onlyOwner 函数无时间锁确认敏感操作(升级、暂停、参数调整)是否通过 Timelock 执行
权限角色定义过宽检查 DEFAULT_ADMIN_ROLE 是否被赋予过多权限
临时权限未及时撤销审计合约中是否存在“已弃用但仍有效的授权”

实战排查命令

# 1. 搜索所有 onlyOwner 函数
grep -rn "onlyOwner" ./contracts/ --include="*.sol"

# 2. 检查是否有时间锁修饰符
grep -rn "Timelock\|timelock\|timeLock" ./contracts/ --include="*.sol"

# 3. 列出所有具有管理权限的函数
slither . --print function-summary | grep -i "onlyOwner\|onlyRole\|admin"

类别 B:业务逻辑与状态完整性

漏洞模式危害审查要点
舍入误差累积中(长期)检查所有涉及比例分配(如分红、奖励、手续费)的计算,是否将余数留给协议或用户
循环中外部调用未隔离失败检查 for 循环中的 call/transfer 是否使用 try/catch 捕获异常
重入风险(仅限特定代币)某些 ERC-777 或自定义钩子代币可能触发重入;检查 transfer/transferFrom 是否调用 tokensReceived
数组长度无上限检查 push 操作是否可被无限调用,导致 gas limit 耗尽
函数执行 Gas 成本不可预测循环操作是否依赖于外部输入长度,且未设置上限

实战排查命令

# 1. 搜索所有循环中的外部调用
grep -rn "for.*call\|for.*transfer\|for.*send" ./contracts/ --include="*.sol"

# 2. 搜索所有 .push 操作
grep -rn ".push(" ./contracts/ --include="*.sol"

# 3. 搜索所有除法操作(可能导致舍入误差)
grep -rn "/" ./contracts/ --include="*.sol" | grep -v "//" | grep -v "*/"

类别 C:代币标准与兼容性

漏洞模式危害审查要点
对非标准 ERC-20 的假设USDT/BUSD 等代币的 transfer 返回值不标准(返回 void 而非 bool
对可重入代币的处理不当ERC-777/ERC-1155 的 tokensReceived 回调可能造成重入
transferFrom 的返回值未检查某些代币实现返回 false 但不 revert,需用 SafeERC20
代币转账 fee-on-transfer 未考虑转账过程中实际接收金额可能小于 amount,需用 balanceOf 前后差计算
decimals 精度假设错误假设所有代币都是 18 位,但实际可能是 6/8/其他值

典型缺陷示例

// ❌ 危险:未处理非标准 ERC-20 返回值
IERC20(token).transferFrom(msg.sender, address(this), amount);
// 某些代币(USDT)返回 void,此处不会 revert,但金额可能未转成功

// ✅ 安全:使用 SafeERC20
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);

实战排查命令

# 1. 搜索所有 .transfer 调用
grep -rn ".transfer(" ./contracts/ --include="*.sol" | grep -v "address(this).balance\|msg.sender"

# 2. 搜索所有 .transferFrom 调用
grep -rn ".transferFrom(" ./contracts/ --include="*.sol"

# 3. 检查是否使用 SafeERC20
grep -rn "SafeERC20" ./contracts/ --include="*.sol"

类别 D:预言机与时序相关(中危变种)

漏洞模式危害审查要点
TWAP 窗口过短observation 时间窗口 < 5 分钟 → 易受闪电贷短暂操纵
预言机更新频率不足价格更新间隔过长,可能导致套利窗口
使用 block.timestamp 进行结算与时间相关的结算(如奖励计算)可能被矿工微调 ±15 秒影响
使用 block.number 进行时间计算区块时间波动可能造成预期偏差

实战排查命令

# 1. 搜索所有 block.timestamp 的使用
grep -rn "block.timestamp\|now" ./contracts/ --include="*.sol"

# 2. 检查 TWAP 窗口参数
grep -rn "TWAP\|twap\|observation" ./contracts/ --include="*.sol"

三、中危漏洞的“组合升级”路径

这是中危漏洞审查中最关键、最容易被忽视的部分。以下模型用于系统化评估中危漏洞的聚合风险:

模型:攻击链叠加分析

flowchart LR
    A[中危漏洞 A<br>例如: 授权范围过宽] --> D[高危攻击路径]
    B[中危漏洞 B<br>例如: 舍入误差累积] --> D
    C[中危漏洞 C<br>例如: 代币兼容性问题] --> D
    D --> E[严重资金损失]

示例:三条中危路径如何组合成致命攻击链

场景:借贷协议存在以下中危问题:

  1. 授权范围过宽(中危):approve 允许无限额度,且无撤销机制。
  2. 舍入误差累积(中危):利息计算使用向下取整,且未处理余数,长期造成协议损失。
  3. 预言机更新频率低(中危):价格每 30 分钟更新一次,存在套利窗口。

组合攻击路径

  1. 攻击者利用预言机更新窗口,在低流动性 DEX 上操纵价格(需额外闪电贷,但协议未限制)。
  2. 利用授权范围过宽,使恶意合约代表其他用户授权(需配合钓鱼,但用户未设限额)。
  3. 利用舍入误差累积,反复调用 borrow/repay 提取被截留的微小金额(每次可能只提取 0.0001 ETH,但可自动化大量执行)。

结果:原本三个各自评分为中危的漏洞,叠加后形成完整攻击路径,导致资金损失。

审查策略:不仅检查单个漏洞,更要绘制“漏洞地图”,识别哪些中危漏洞可能被串联。

四、中危漏洞的复现与验证

中危漏洞往往需要特定的条件才能触发,因此验证它们的存在是审计中的一项关键挑战。以下是优先序建议与操作参考:

优先级划分(按潜在影响与组合风险排序):

  1. 高优先级:涉及资金流出、可被攻击者控制输入参数的漏洞。
  2. 中优先级:影响核心业务逻辑、可能被矿工或特定角色利用的漏洞。
  3. 低优先级:仅影响代码规范或 Gas 优化的中危项。

对于高、中优先级的漏洞,建议编写 Foundry PoC 进行验证。

实战示例:验证授权范围过宽的攻击路径

// 测试授权范围过宽是否能被利用
function test_OverApproval() public {
    // 1. 部署协议和攻击者合约
    // 2. 模拟用户授权无限额度
    token.approve(protocol, type(uint256).max);
    // 3. 攻击者合约调用 transferFrom 转移资金
    vm.prank(attacker);
    token.transferFrom(user, attacker, userBalance);
    // 4. 验证攻击成功
    assertEq(token.balanceOf(attacker), userBalance);
}

验证要点

  • 是否可在分叉环境中重现。
  • 攻击成本是否低于潜在收益。
  • 是否存在任何前提条件(如特殊权限、特定市场状态)。

对于成本较高或条件较苛刻的攻击路径,记录其理论可行性而非强行复现。

五、中危漏洞的缓解与处置建议

缓解策略适用场景实施成本
添加参数范围限制防止数组过长或输入值过大
增加时间锁敏感操作延迟执行,给予用户反应时间
使用 SafeERC20处理非标准 ERC-20 代币
采用拉取模式(Pull over Push)避免循环推送导致的 DoS中(需架构调整)
引入多签/多角色验证降低单一管理员权限风险
设置授权上限(如 increaseAllowance 而非无限批准)降低授权泄露风险

修复优先级判定矩阵

组合风险与高危漏洞距离建议处置优先级
高组合风险可与其他中危/高危漏洞串联:应在审计修复期内修复
中组合风险需要特定条件(如特定市场状态):建议修复或制定缓解措施
低组合风险单独存在,无串联可能:可长期优化,不影响上线

六、实战案例:多个中危漏洞的聚合利用

案例背景:某收益聚合器协议的审计中发现三个中危问题:

  1. 奖励计算舍入误差(中危):reward = amount * rate / 1e18,余数被截留。
  2. 策略提款无滑点保护(中危):提款时允许攻击者在交易间隔内操纵价格。
  3. onlyOwner 函数无时间锁(中危):管理员可立即修改关键参数。

组合利用路径

  1. 管理员(攻击者控制的恶意多签)无时间锁地修改策略参数,将资金池导向恶意策略。
  2. 利用无滑点保护,攻击者在策略切换后立即进行三明治攻击,提取比预期更多的收益。
  3. 舍入误差累积被攻击者通过大量小额提款反复提取。

最终影响:虽然每个漏洞单独危害有限,但叠加后造成约 15% 的协议资金流失。该案例被标记为综合严重风险,要求项目方一次性修复所有相关漏洞。

审计启示:即使是中危漏洞,如果存在多条串联路径,也应升级其综合风险评级。

七、报告模板参考

中危漏洞的审计报告应包含以下章节:

### 漏洞名称:[简明命名]

- **严重等级**:中危(Medium)
- **影响范围**:[合约名 + 函数名]
- **代码位置**`path/to/contract.sol#L100-L120`

**漏洞描述**:
[简洁描述漏洞的本质和触发条件]

**影响分析**:
[阐述攻击者可以做什么,需要什么条件,可能造成什么后果]

**组合风险评估**:
[是否存在与其他漏洞串联的可能?如果是,说明组合路径]

**修复建议**:
[提供具体的代码修改示例]

**复现步骤**:
[可选:附上 Foundry 测试用例或攻击脚本]

**状态**:[待修复 / 已修复 / 风险接受]

八、审查总结与经验法则

法则说明
不孤立评估漏洞等级多个中危漏洞叠加可能造成严重损失
关注组合路径审查时应绘制漏洞地图,识别攻击链
优先修复可升级路径优先修复那些可能与其他漏洞串联的问题
使用标准库OpenZeppelin 的 SafeERC20ReentrancyGuardTimelockController 等可缓解多数中危问题
持续监控部分中危漏洞(如舍入误差累积)只在长期运行后显现,上线后需持续监测

核心原则中危漏洞审查的目标不是“找问题”,而是“识别问题组合的潜在危害”。一个攻击者不会只利用一个漏洞,他会利用所有可利用的条件。审计人员应以攻击者的视角,将所有中危漏洞视为攻击链中的“拼图”,而非独立的碎片。