10分钟智能合约:进阶实战-9.5 其他漏洞审查

16 阅读6分钟

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

其他漏洞审查:低危、信息性及代码质量缺陷

除了高危和中危漏洞,审计报告还需涵盖低危(Low)信息性(Informational) 以及代码质量与可维护性问题。虽然它们通常不直接导致资金损失,但会影响合约的可读性、可升级性、Gas 效率、用户信任度及长期安全性


一、低危漏洞(Low Severity)

低危漏洞在特定条件下可能造成有限影响,但攻击者难以直接利用,或者影响范围很小。

1. 拒绝服务(局部)

  • 定义:攻击者可使特定用户无法使用某个功能,而非整个合约瘫痪。
  • 示例withdraw() 调用 transfer 时,若接收方是恶意合约并 revert,则该用户无法提款,但其他用户不受影响。
  • 审查要点:检查所有涉及外部调用的函数,是否允许用户绕过(如使用 try/catch 或提供替代提款路径)。
  • 修复建议:采用拉取模式(withdraw)或使用 try/catch 隔离失败。

2. 条件竞争(Race Condition)

  • 定义:在非原子操作中,多个交易可能相互干扰,但结果可预测或影响较小。
  • 示例claimReward()updateReward() 之间存在微小时间窗口,导致用户获取略多或略少的奖励。
  • 审查要点:检查非 nonReentrant 且涉及状态变化的函数,是否有多笔交易相互影响的可能。
  • 修复建议:使用原子操作或增加互斥锁(Mutex)。

3. 输入校验不足(非关键)

  • 定义:某些函数的参数未被充分验证,但不会直接导致资金损失。
  • 示例setFee(uint256 fee) 允许将 fee 设为超过 100%,但后续计算可能导致溢出(0.8+ 自动回滚)或用户误解。
  • 审查要点:检查所有 external/public 函数的参数边界。
  • 修复建议:增加 require 限制合理范围(如 fee <= 1e18)。

4. 依赖过时编译器版本

  • 定义:使用 pragma solidity < 0.8.0 且未使用 SafeMath,存在潜在的溢出风险(但 0.8+ 已默认检查)。
  • 审查要点:检查 pragma 声明,确认是否使用最新稳定版(至少 0.8.x)。
  • 修复建议:升级到 0.8.25+ 并测试。

二、信息性漏洞(Informational)

信息性漏洞不直接威胁安全,但影响透明度、监控效率或用户体验

1. 缺少事件(Event)

  • 定义:关键状态变更(如所有权转移、参数调整、大额转账)未发出事件。
  • 审查要点:检查所有 onlyOwner 函数、set 函数及资金变动函数是否包含 emit
  • 修复建议:为每个敏感操作添加事件,并遵循 Indexed 字段原则便于过滤。

2. 拼写错误或命名不规范

  • 定义:变量名、函数名拼写错误,或未遵循 Solidity 命名规范(如 _ 前缀表示内部)。
  • 审查要点:人工检查代码可读性,使用 linter(如 solhint)。
  • 修复建议:统一命名风格,避免缩写或歧义。

3. 未使用的变量或函数

  • 定义:声明了变量或函数但从未使用,可能造成误导。
  • 审查要点:使用 slither--detect unused-* 检测。
  • 修复建议:删除无用代码或添加 _ 前缀(如 __gap 保留槽)。

4. 缺少注释或文档

  • 定义:复杂逻辑(如利率计算、授权流程)未在代码中注释。
  • 审查要点:检查 NatSpec 格式是否完整。
  • 修复建议:为所有 public/external 函数添加 @notice@param@return

5. 硬编码地址

  • 定义:关键合约地址(如预言机、代币)被硬编码,不利于升级。
  • 审查要点:搜索 0x 字符串,确认是否可配置。
  • 修复建议:使用 immutablesetter 函数(带时间锁)管理。

三、代码质量与可维护性

这些建议旨在提升代码的健壮性,降低未来出错概率。

1. Gas 优化

  • 定义:代码结构导致不必要的 Gas 消耗,增加用户成本。
  • 常见问题
    • 循环中使用 storage 读写而非 memory
    • 重复计算相同值。
    • 使用 bool 数组(可用 uint256 位打包)。
    • public 变量自动生成 getter,但可改为 internal 以节省。
  • 审查要点:结合 forge snapshot 对比优化前后的 Gas 消耗。
  • 修复建议:优先使用 memory 变量,减少 SLOAD/SSTORE 操作。

2. 使用 require vs assert

  • 定义require 用于输入验证,assert 用于内部不变量,但误用可能导致 Gas 退还差异或攻击面。
  • 审查要点:检查 assert 是否用于外部条件(错误用法)。
  • 修复建议:除内部检查外,一律使用 require

3. 函数可见性不当

  • 定义:内部函数被标记为 publicexternal,增加攻击面。
  • 审查要点:检查每个函数的可见性是否符合最小权限原则。
  • 修复建议:除非必要,内部函数使用 internalprivate

4. 可升级模式中的存储布局

  • 定义:在可升级合约中,新版本添加变量时可能覆盖旧存储槽。
  • 审查要点:检查是否使用 __gap 预留槽位,且升级时仅追加变量。
  • 修复建议:遵循 OpenZeppelin 升级指南,使用 UUPSTransparent Proxy 模式。

5. 依赖库版本管理

  • 定义:使用的第三方库(如 OpenZeppelin)版本过旧,可能存在已知漏洞。
  • 审查要点:检查 package.jsonfoundry.toml 中的依赖版本。
  • 修复建议:定期更新至最新稳定版,并关注安全公告。

四、审查方法总结

类别审查方法工具支持
低危逻辑人工审查 + 攻击链模拟Foundry 测试
信息性人工审查 + lintersolhint, slither
Gas 优化基准测试与对比forge snapshot, gas-report
代码质量人工审查 + 静态分析slither, aderyn

五、报告模板示例

### [L-01] 提款函数缺少失败隔离

**严重等级**:低危  
**合约**`Vault.sol`  
**代码位置**`Vault.sol#L45`

**描述**`withdraw()` 在循环中调用 `transfer`,如果某个接收方合约 revert,将阻塞该用户后续提款,但不影响其他用户。

**建议**:使用 `try/catch` 捕获异常,或采用拉取模式(`withdraw` 由用户主动调用)。

**状态**:待修复

六、总结

其他漏洞审查的目标是确保合约不仅安全,而且清晰、高效、易于维护。虽然它们不如高危漏洞紧迫,但在长期运营中,良好的代码质量能显著降低未来引入新漏洞的风险。审计团队应将这部分内容作为报告的完整组成部分,并给出可操作的改进建议。