智能合约常见漏洞库
这里不是「十大漏洞排行榜」。收录标准只有一条:这个漏洞在真实链上造成过资金损失,而且根因可以用一段代码说清楚。所以每一类都写成了三段式的闭环——只讲原理的文章读完了还是不会写安全代码, 只给一段坏代码又不知道该怎么改,两段放一起才形成闭环。
合约在「已经把钱转出去、但还没更新自己的账本」的那个瞬间,被转账接收方回头再调用了一次。于是同一个余额被反复支取。
攻击者借来巨额资金,在一个交易内把某交易对的现货价格推歪,让依赖这个价格的协议做出错误判断(多放贷、错误清算、错误铸币),然后归还贷款。
特权函数忘了加权限判断,或者权限判断写错(比如用 tx.origin、只校验参数不校验调用者),任何人都能执行本该只有管理员能做的事。
用 tx.origin 判断调用者身份,攻击者就能诱骗受害者调用自己的恶意合约,由恶意合约去「冒充」受害者通过校验。因为 tx.origin 始终是最初发起交易的那个 EOA,整条调用链都骗不过它。
链上没有真正的随机数。用 block.timestamp / blockhash / msg.sender 等链上可见的值当随机源,等于把开奖结果提前公开——矿工(或任何能预测的人)都能动手脚。
同一份签名被重复使用:跨链重放(在 A 链的签名拿到 B 链用)、跨合约重放(同一个签名被另一个合约接受)、或同链重复重放(没记 nonce,同一笔授权执行多次)。
整数除法向下取整,攻击者用「先存 1 wei,再直接转入巨额资产」把份额单价顶高,让后存入者的份额被舍入成 0 —— 他们的钱被前面的人吃掉。
代理模式下代码在实现合约、数据存在代理合约。如果实现合约的变量布局变了(加变量、调顺序、改类型),旧数据会被按新布局解读;如果升级函数没有严格的权限控制,任何人可以换成恶意实现,一次抽空全部资金。
一个本该让所有人都能走通的操作,被某个人(或某个不可避免的状况)永久卡住:循环长度无上限、外部转账失败就整笔 revert、退款通道被恶意占位。
能买不能卖:合约里埋了只有 owner 能解的开关、黑名单、可调税率或额度上限,让普通用户买进去就出不来。这不是技术漏洞,而是故意设计的骗局。
带工具查不出标记的条目,代码里没有任何可疑的写法——没有可疑的权限检查缺失,没有异常的顺序, opcode 分布也完全正常。它们靠的是运行时的状态组合(价格被操纵、签名被复用、 舍入方向被利用),历史上损失金额最大的几起事故都属于这一类。 把「静态扫描通过」当成安全结论,是这里最容易犯的错误。
每一条里都有什么
顺序是刻意排的:先讲清「为什么会出事」,再给出钱的路径,最后才给代码。 反过来先贴代码的话,读的人只会记住「重入要用 nonReentrant」, 但不知道自己在防什么。
写的是「状态更新与外部调用的顺序错了」这类根因,而不是「没有加锁」这类现象。 现象层面的描述会让人把模式当成规则背下来,换个场景就失效。
按「发生位置 → 这一笔做了什么」逐步展开,包含攻击者需要付出的成本。 免费的攻击和需要几十万美金闪电贷的攻击,防护优先级完全不一样。
刻意写成能编译、能部署、语法完全正常的合约。漏洞不会长得像错误代码, 它就是正常的业务逻辑少了一行。
需要攻击合约才能成立的那些(重入、闪电贷)会额外给出攻击者那一侧的代码—— 看到回调函数长什么样,「为什么先转账会出事」就不用背了。
有 3 类漏洞,静态扫描一条也抓不到
它们的共同点:代码语法完全正常,危险来自「这段代码在什么环境下运行」。 工具能检查的是写法,检查不了假设。下面这几类必须靠人看懂业务: