合约安全:重入、权限与 OWASP¶
30 秒版(开场)¶
智能合约由链上共识执行,错误通常不可由后端热修复,且常直接控制资产。安全回答不能只背“重入”:应覆盖 访问控制、业务逻辑、预言机、精度、外部调用、签名重放和升级面。CEI 与
ReentrancyGuard是重入防线,但 Go 层风控不能替代链上硬约束。
3 分钟版(精讲深度)¶
- 是什么:风险分类优先参考持续维护的 OWASP Smart Contract Top 10 / SCSVS;SWC 仅用于理解历史编号。
- 为什么:一次漏洞 = 全额损失;Review 是架构师核心职责。
- 怎么做:先定义协议不变量和信任边界,再做最小权限、CEI/重入锁、安全外部调用、预言机与精度防护;配合静态分析、fuzz、invariant 和主网 fork 测试。
10 分钟版(原理 + 图示)¶
sequenceDiagram
participant A as 攻击合约
participant V as Vault
A->>V: withdraw
V->>A: call 转 ETH
A->>V: withdraw 再次(重入)
Checks-Effects-Interactions(CEI)
function withdraw(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount); // Checks
balances[msg.sender] -= amount; // Effects
(bool ok,) = msg.sender.call{value: amount}(""); // Interactions
require(ok);
}
本仓库示例:ReentrancyGuard.sol
OWASP Smart Contract Top 10(2026)
| 类别 | 讲解时应讲的防线 |
|---|---|
| SC01 访问控制 | 每条管理路径都鉴权;RBAC、两步权限转移、Multisig + Timelock |
| SC02 业务逻辑 | 状态机与经济不变量;异常路径、边界状态和组合调用测试 |
| SC03 预言机操纵 | 多源/稳健价格、TWAP、freshness、偏差与流动性检查 |
| SC04 闪电贷促成攻击 | 闪电贷只是资金放大器;修复同交易价格操纵和业务不变量缺口 |
| SC05 输入校验 | 地址、数量、数组长度、deadline、签名参数和状态前置条件 |
| SC06 未检查外部调用 | 检查 low-level call;ERC20 用 SafeERC20 兼容非标准返回 |
| SC07 算术/舍入/精度 | 固定单位、明确舍入方向、处理 dust;关注除法顺序和 decimal 差异 |
| SC08 重入 | CEI + nonReentrant;覆盖跨函数、跨合约和 read-only reentrancy |
| SC09 溢出/下溢 | Solidity 0.8+ 默认检查,但仍审计 unchecked、窄类型转换和 assembly |
| SC10 代理/升级 | 初始化、升级鉴权、存储布局校验、Timelock 与可审计升级流程 |
SWC Registry 自身已明确说明:内容自 2020 年后未充分更新,可能不完整且含错误或关键遗漏。因此可以在场景里,说 “SWC-107 重入”,但不应把 SWC 当作当前完整安全基线,也不应笼统回答“Solidity 0.8+ 全部用 SafeMath”。
权限模型
Ownable:单管理员AccessControl:角色bytes32+onlyRole- Timelock + Multisig 管升级与参数
生产场景¶
- 发送原生 ETH:通常使用
.call{value: ...}("")并检查返回值,再配合 CEI/重入锁;不要依赖transfer的固定 2300 gas 假设 - 调 ERC20:使用
SafeERC20处理不返回 bool 等非标准 token;这与 ETH 的transfer/call是两类问题 - 签名授权:EIP-712 domain 应绑定
chainId、verifyingContract,并校验 nonce 与 deadline,防跨链/跨合约重放 - 闪电贷攻击:单 tx 内价格操纵(见 S-SOLID-07)
- 代理 admin 误留 implementation 函数
排查与工具¶
- Slither、Aderyn、Mythril
- Echidna 模糊测试属性
- 主网 fork 测试(Foundry)
深挖问答¶
- read-only reentrancy? → 跨合约视图在 callback 中读 stale 价格。
- tx.origin 为何禁用? → 钓鱼中间合约。
- delegatecall 风险? → 在错误 context 执行,storage 错乱。
- Go 后端如何配合? → 链下风控 + 链上硬规则;见 S-SOLID-08。
反模式与事故¶
- 先转账后改余额 → 重入
- EOA 单 owner 直接控升级/资金 → 私钥单点;使用两步转移、多签和延时
- 把
private状态当秘密 → 链上 storage 可读取 - 依赖
selfdestruct删除代码/存储 → EIP-6780 后通常只转余额;除“创建同一交易内销毁”等例外,不再是通用删除原语