本文总结了智能合约开发中常见的由于开发者疏忽导致的漏洞,例如整数溢出、数组越界访问、重入攻击等。文章列举了多个真实案例,并提供了一个检查清单,帮助开发者和审计人员在开发和审计过程中避免这些低级错误。此外,文章还推荐了Slither、MythX等静态分析工具和Echidna、Foundry-fuzz等模糊测试工具,以帮助开发者在早期发现和修复潜在的漏洞。
从 Web3 开发者到智能合约审计员:开发者在数学、数组、Calldata 及其它方面的失误

我想说这是最“令人愉快”的领域之一,因为这些错误实际上归结为对细节的关注和代码质量(我澄清这一点是因为它可能听起来像犯错以某种方式降低了程序员的技能——事实并非如此)。如果你是没有丰富审计经验的开发者,这些漏洞实际上更容易被发现。
还值得指出的是 https://ethernaut.openzeppelin.com/ 上的挑战很大程度上属于此类漏洞和攻击,所以我强烈建议尝试一下。它们是入门和练习的绝佳方式。每个难题都局限于一小段代码,因此你可以快速完成它们,但你仍然可以获得实践经验。
现在,让我们继续看一些例子。首先,我想在深入研究最近发生的事件之前,先介绍两个经典案例:
- 2016 年发生了首批引人注目的此类攻击之一——并且可以说是最著名的。攻击者利用 The DAO 智能合约中的重入漏洞窃取了大约 6000 万美元。这一事件最终导致社区对以太坊进行硬分叉,以恢复被盗资金:https://blog.chain.link/reentrancy-attacks-and-the-dao-hack/
- 下一个重大事件涉及 Parity 的多重签名钱包。这些钱包使用 delegatecall(在调用合约的上下文中执行库代码)来处理核心逻辑。一个名为 initWallet 的函数——旨在仅在钱包创建期间运行一次——从未检查它被调用了多少次。攻击者利用此疏忽重复重新初始化其他用户的钱包,从而有效地窃取了所有权:https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7
好的,这两个是经典的——但如果我们看一些最近的呢:
- Euler Finance 中的整数下溢导致损失约 1.97 亿美元(2023 年): 新添加的 donateToReserves 函数忘记调用通常的 checkLiquidity(from) 保护,该保护强制执行“负债绝不能超过抵押品”的不变性。这一个疏忽打破了协议的核心安全检查,让攻击者耗尽了巨额资金。阅读更多:euler-compromise-investigation-part-1-the-exploit
- Velocore 漏洞(2024 年)——损失 680 万美元: DEX 的费用乘数参数未经过验证,因此攻击者可以将其调高到 100% 以上并“夸大”计算,从而从其流动性池中吸走 680 万美元。阅读更多:velocores-6-8-million-exploit-fast-and-furious-losses-in-crypto
- Minterest 重入漏洞(2024 年)——损失 140 万美元: 在 flashLoan 回调中,Minterest 首先将 USDY 发送给借款人,然后立即重新进入 lendRUSDY,而没有任何重入保护——让攻击者可以再次借用已提取的资金。阅读更多:explained-the-minterest-hack
- GemPad 重入攻击(2024 年)——被盗 180 万美元: 在 GemPadLock.sol 中,collectFees() 调用传输了一个“陷阱”token,该token重新进入 multipleLock(),创建虚假锁并在同时耗尽实际余额。由于在部分提款后内部余额从未更新,因此攻击者带着 180 万美元的“锁定”流动性逃脱。阅读更多:gempad-1-8m-incident-super-deep-dive
- Abracadabra Money 幻影抵押品漏洞(2025 年)——损失 1300 万美元: GMX V2 CauldronV4 在内存字段中跟踪“飞行中”的抵押品,这些字段在提款时从未减少。因此,攻击者始终可以通过最终偿付能力检查并耗尽资金池。阅读更多:abracadabra-gmx-defi-exploit-explained
- Synthetic Implemented Right (SIR) 存储污染攻击(2025 年)——损失 355 000 美元: 该合约使用 EIP-1153 的瞬态存储来验证 uniswapV3SwapCallback(),然后用用户控制的值覆盖该插槽——“污染”检查并允许未经授权的铸造。阅读更多:synthetics-implemented-right-sir-hack
- 1inch Fusion V1 解析器漏洞(2025 年)——损失约 500 万美元: 一个 calldata 后缀解析错误让攻击者可以覆盖“解析器”地址,强制 Settlement 合约调用恶意解析器。他们耗尽了约 2 000 000 USDC + 1 276 WETH,然后归还了大部分资金——仅保留了少量漏洞赏金小费。阅读更多:yul-calldata-corruption-1inch-postmortem
- Mobius Token 铸币溢出漏洞(2025 年)——造成 215 万美元的损失: deposit() 中一个错位的乘法运算从未除以 1⁰¹⁸,因此攻击者凭空铸造了约 9.7×1⁰³⁸ MOBI token,并将其中的一部分兑换为 215 万美元的稳定币。阅读更多:mobius overflow
- Socket.tech 桥漏洞(2024 年): 在 Socket Gateway token 路由聚合器标准中,攻击者利用了 performAction 函数通过 .call() 调用外部 swapExtraData 而没有任何验证这一事实,允许他替换 calldata 并从用户批准的合约执行 transferFrom。因此,他依次触发了 USDC、WETH、USDT、WBTC、DAI 和 MATIC 的提款,总计约 330 万美元(USDC 250 万美元 + 其他 token)。该漏洞恰恰在于 .call() 之前缺乏输入数据验证,这使得可以完全控制任意代码的执行。阅读更多:socket-tech-incident-analysis
最后,这些漏洞实际上归结为简单的开发者错误:缺少检查、数组/内存/calldata 中的差一错误或粗心的数学运算。它们不是神奇的攻击,但它们可能会造成数千万甚至数亿美元的损失。
好消息是,现代 Solidity (≥0.8) 已经通过还原来保护你免受整数溢出和越界数组访问的影响——但在审计和代码审查期间,我始终会将此清单放在首位和中心位置:
- 重入/检查-效果-交互: 每个进行外部调用的 external/public 函数都应使用 nonReentrant 修饰符或遵循检查 → 效果 → 交互模式。
- 数学/溢出和下溢: 无论你在哪里处理大量数字或小数值,都使用 SafeMath(或依赖 Solidity ≥0.8 的内置检查)。始终仔细检查任何硬编码的转换或乘数,以避免缩放错误。
- 数组索引和边界: 每次数组[i]、切片或 calldata 访问都必须确保 i < array.length。并且还要确保越界尝试干净地还原,而不是静默地返回垃圾。
- 输入验证: 每个参数(数量、乘数、费用、长度等)在使用前都需要 require(…) 保护。对于百分比类型的参数(例如 feeMultiplier),强制执行严格的范围检查,例如 0 <= feeMultiplier <= MAX。
- Calldata/内存/Yul 安全: 在任何内联汇编中,显式计算偏移量和长度,并防止 ptr + len 算术中的下溢/溢出。在解析 calldata 时,在分支之前验证 data.length 是否符合你的预期。
- 一次性初始化: 任何自定义初始化函数(例如,在可升级代理中)都必须受到保护,以便它只能运行一次——使用 OpenZeppelin 的 initializer 修饰符。
- 协议不变性: 确定你的核心不变性(例如“抵押品 ≥ 负债”或“totalSupply 永不减少”),并确保每个公共入口点都保留它们。
- 有界循环: 永远不要在不断增长的数组上无限制地迭代(尤其是在 withdrawAll 或 massUpdate 中)。如果你需要批处理,请强制执行合理的 maxBatchSize,以便用户不会意外耗尽 gas。
- 内存与存储意识: 跟踪每个变量的驻留位置——在内存与存储中存储大型结构可能会产生意想不到的成本和正确性影响。
在开发和审计过程中牢记此清单将有助于在“简单”的错误变成数百万美元的错误之前发现它们。
用于及时发现低级错误的开发者工具
值得庆幸的是,我们现在拥有触手可及的出色工具,可以在这些错误进入生产环境之前将其捕获:
静态分析: 无需运行即可扫描你的代码,并立即标记整个类的问题 - 整数溢出/下溢、次优的 delegatecall 使用、潜在的堆栈缓冲区溢出、错误的数组索引检查等等。
Fuzzing: 生成随机输入来探测你的合约,并发现静态扫描器遗漏的逻辑漏洞。
它们共同构成了一个真正的“全能”防御:静态分析可以确定大多数已知模式,而 模糊测试 动态地提取更深层次的逻辑错误。将 Slither + MythX 添加到你的 CI 管道以进行基于签名的检查,并叠加 Echidna/forge-fuzz 以生成性地测试你的不变量——你将消除几乎所有琐碎但令人沮丧的错误。