智能合约是区块链上的核心执行体,它不像传统 Web 应用那样可以修补、热更新或下线回滚。
合约一旦部署,逻辑就是永久的,资产就是实时绑定的。
在主网上线的每一秒,都可能被数千个自动化 Bot 和黑客脚本实时监控。
作为开发者或审计者,我们必须清楚——攻击从不来自语法错误,而来自设计逻辑上的漏洞。
因此在进入具体的漏洞分析之前,我们必须建立一张清晰的「攻击图谱」。
我们将攻击行为从技术维度系统拆解为7 大类攻击类型,每类都对应不同的攻击目标、原理、执行路径与防御策略。
攻击类型 | 攻击目标 | 原理简述 | 常见案例 |
---|---|---|---|
1. 重入攻击 | 状态变量一致性 | 外部合约回调,合约状态未及时更新 | The DAO / dForce |
2. 权限控制失效 | 管理权 / 控制权 | 不正确的权限修饰符或初始化逻辑 | bZx / Parity Wallet |
3. 计算精度错误 | 资产数值失控 | 整数溢出 / 精度误差 / 运算顺序不当 | YAM / Balancer |
4. 预言机操控 | 定价机制失效 | 操控喂价合约、操作池子数据导致数据偏差 | Mango / Harvest |
5. 外部依赖风险 | 链下数据引爆 | 对外部系统(VRF、Oracle、合约)信任过高 | Synthetix / Chainlink 滥用 |
6. 升级/代理缺陷 | 存储/逻辑错乱 | Proxy 存储布局错位、delegatecall 滥用 | Curve / OpenZeppelin 示例 |
7. 部署初始化 | 默认状态攻击 | 合约部署时未初始化、未锁权限 | Parity 多签 / Ownable 未设置 |
接下来我们详细讲解每一类攻击:
当合约向外部地址发送 ETH(如使用 call{value:}
),如果该地址是合约,它的 receive()
或 fallback()
函数可能在状态变量尚未更新前重新调用原合约,从而达到反复操作未锁状态的目的。
多次提款
重复 mint
多轮 claim 奖励
User.withdraw()
→ msg.sender.call{value: X}()
→ AttackContract.fallback()
→ 再次调用 withdraw()
→ msg.sender.call{value: X}()
... 循环直到提款耗尽
The DAO(2016):攻击者反复调用提款逻辑,提走 30% 资金
dForce(2020):重入 USDT 合约,绕过冻结检查
状态变量更新应在外部调用之前(Checks-Effects-Interactions)
使用 ReentrancyGuard
修饰器封锁再次进入
引入 pull-payment
模式,将提款推迟执行
使用不安全的权限验证(如 tx.origin
)
未使用修饰符限制关键函数(如 mint、pause)
构造函数命名错误或未正确初始化所有者
攻击者部署恶意合约诱导用户转账 → 利用 tx.origin
拿到控制权
直接调用 initialize()
→ 抢占 owner
提案治理中嵌入恶意逻辑 → 偷权+转钱
Parity Wallet 多签:初始化函数未锁,被恶意调用 selfdestruct
bZx 协议:权限管理错误 → 任意提资产、变更参数
永远使用 msg.sender
进行权限判断
初始化函数设置 onlyInitializing
+ initializer
修饰器
使用 OpenZeppelin Ownable
/ AccessControl
系统化权限设计
所有关键操作需 onlyOwner
/ 多签控制
Solidity 中整数类型有固定大小(如 uint256
),0.8.0 版本后默认开启溢出检测,但开发者仍需注意以下风险:
多次乘法后除法顺序错误
精度不足导致错误资产分配
未考虑 decimals
精度混乱问题
YAM Protocol:指数增长逻辑整数溢出,失败后治理无法提案修复
Balancer 池:处理复利时精度误差导致套利空间
避免复杂嵌套计算,提取中间变量
所有合约中统一使用 18 decimals
资产表示
对分配型计算使用 x.mul(y).div(z)
顺序
高精度逻辑使用 FixedPointMathLib
项目方常通过链上池或外部合约获取 Token 定价。但若该数据源可以被操控(如通过闪电贷调控池子余额),则可以恶意操控价格。
利用 flashloan 操纵流动性池价格
通过低流动性 token 偏移 AMM 报价
喂价合约允许任何账户更新价格
Mango Markets:攻击者操控账户资产价值,借出大量无抵押 USDC
Harvest Finance:反复操纵价格后套利提走资产
使用 Chainlink 等去中心化预言机
采用时间加权平均价格(TWAP)而非即时价格
引入价格上限/滑点保护机制
当合约依赖第三方数据(如 VRF 随机数、链上喂价合约、Oracle),一旦这些依赖合约被替换、暂停或被操控,可能间接引发攻击。
合约逻辑调用 VRF,但未校验 requestID 匹配
合约过度信任 msg.sender == oracle
VRF 合约部署者本身恶意更改返回值
Uniswap 早期流动性挖矿项目暴露对外部状态的信任问题
NFT 盲盒开奖使用 block.timestamp
导致可预测性
所有 VRF / Oracle 接口应校验请求方 ID
外部依赖合约需白名单化 & 定期审计
异步逻辑增加 nonces
校验,避免 replay
使用代理合约(如 UUPS / Transparent Proxy)时,合约存储 layout 必须一致,否则 delegatecall
会将逻辑合约的存储与 proxy 的存储结构打乱,造成严重逻辑错乱。
升级合约后变量 slot 错位 → 权限丢失
代理合约错误设置 implementation 地址 → 调用错误逻辑
initialize() 可被重复调用 → 权限重设
Curve 使用 Vyper 编译版本兼容性错误导致 pool 实现合约变量错位
多个项目在使用 UUPS 时遗漏 upgrade 安全性判断
使用 OpenZeppelin 官方推荐升级模式
定义 __gap
预留 slot 空间
所有可升级合约必须测试覆盖 initialize
+ 升级后状态验证
增设 onlyProxy
校验逻辑
部署完成后,若初始化逻辑未被锁定,攻击者可调用 initialize()
、setOwner()
等函数,接管合约或进行恶意操作。
Parity Wallet:初始化未调用,被恶意初始化+销毁
合约部署后未设置 onlyOwner
,结果任意人调用 mint()
铸币
使用 OpenZeppelin initializer
修饰器
所有初始化函数加部署时自动调用逻辑
合约上线前进行 Etherscan 验证 + read/write 调试验证状态变量是否写入
所有攻击行为,均可归类为 7 大路径
每类攻击都有其“漏洞模式” + “触发机制” + “利用路径”
审计者不是死盯语法,而是系统构建“攻击地图”+“风险断点链”
后续章节将逐一拆解每类攻击,配合复现合约、修复对比、Gas 成本分析
回顾你最近写过的合约,尝试对照这 7 类做一次 checklist 风险扫描
安装并运行 slither
对项目进行静态分析:
npm install -g slither-analyzer
slither ./
用图形工具画出你项目的权限调用链
(例如:谁能 mint → 谁能 transferOwnership → 谁能 call sensitive 函数)
合约提款逻辑里隐藏的死亡递归
攻击合约复现 The DAO 闪崩事件
3 种修复方案 + 防御对比 + 代码演练
从下一章开始,我们将进入真正的安全攻防实战训练阶段。
你将开始写出“漏洞合约”、部署“攻击者合约”、复现真实链上攻击路径。
你准备好从开发者,转变为具备黑客视角的审计工程师了吗?
我们下一章正式开战。