一、C1 控制流#
| 术语 |
含义 |
出处 |
| 重入(reentrancy) |
函数执行到一半时被再次进入,此时状态是半更新的 |
1 |
| CEI |
检查 → 生效 → 交互。⭐ 第一道防线,重入锁只是第二道 |
1 |
| 跨函数重入 |
进入点和出手点是不同的函数。⚠️ 单函数加锁完全无效 |
1、2 |
| 跨合约重入 |
两个合约共享状态,各自加了各自的锁 |
1 |
| 只读重入 |
⭐ 不写任何状态也能被打穿:外部在中间态读到了不自洽的值 |
3 |
| ERC-777 钩子 |
tokensToSend 在扣款之前回调转出方;钩子任何人可注册 |
2 |
| 控制权移交 |
⭐ 在 EVM 里,向合约地址转账 = 调用它的代码 |
1 |
二、C2 权限与身份#
| 术语 |
含义 |
出处 |
| delegatecall |
借别人的代码在自己的存储上下文里执行 |
4 |
| 未初始化的实现合约 |
⭐ 实现合约是一个活合约,可被任何人初始化并夺取 |
5 |
_disableInitializers() |
构造函数里锁死实现合约自身的初始化 |
5 |
| 混淆代理人 |
⭐⭐ 一个有权限的实体被诱导替没有权限的人执行操作 |
6 |
| 函数选择器碰撞 |
⚠️ 4 字节 = 2³² ≈ 43 亿,普通电脑几分钟可穷举 |
6 |
| 零值即有效值 |
mapping 对不存在的 key 返回零值 ⟹ 零值必须表示"不存在" |
7 |
| 僵尸权限 |
当初有理由、现在没人记得的授权 |
22 |
| 利用门槛 |
利用漏洞所需的能力,决定损失速度(复制粘贴级 ⟹ 全民哄抢) |
7 |
三、C3 数值#
| 术语 |
含义 |
出处 |
| 类型范围 vs 业务范围 |
⭐⭐ 算术运行在类型范围上,除非你显式对齐 |
8 |
| 校验代码里的溢出 |
⚠️ 溢出发生在用于判断的算式里 ⟹ 防线消失而一切正常 |
8、9 |
batchOverflow |
cnt × value 溢出成 0 ⟹ 余额检查恒真 |
9 |
| 0.8 不检查类型转换 |
⭐uint128(x) 越界静默截断,不 revert ⟹ 用 SafeCast |
9 |
| 错误的检查 |
比没有检查更危险——它把语言层的兜底也关掉了 |
11 |
| 舍入攻击 |
舍入方向反了 ⟹ 极小额反复调用,累积的、无声的损失 |
19 |
| 域分隔 |
叶子 H(0x00‖x)、内部节点 H(0x01‖l‖r) |
10 |
| 证明必须绑定三样 |
根匹配 + 叶子确实是叶子 + 位置唯一 |
10 |
四、C4 外部数据#
| 术语 |
含义 |
出处 |
| 闪电贷 |
⭐⭐ 本身完全安全。它把攻击者集合从"有几百万的人"变成"所有会写 Solidity 的人" |
12 |
| 现货价操纵 |
恒定乘积池推到 k 倍需 y(√k − 1);k=2 ⟹ 41% 储备 |
12 |
| 两次读数之间的差 |
⭐ 不只问"能否被操纵",还要问"读两次之间能否被改变" |
13 |
| 往返不变量 |
⭐ 存入后立即取出必须亏钱。五行代码,覆盖面极大 |
13、19 |
| 捐赠攻击 |
直接 transfer 抬高 totalAssets,不铸造份额、不触发任何事件 |
15 |
| 抽象泄漏 |
⭐ 接口给的是一个数,你用它做的是一个判断——差距就是没写下来的假设 |
14 |
| 正确地报告了错的东西 |
⚠️ 预言机没坏、没人撒谎、没有 bug,钱照样没了 |
14 |
| 清算可行性 |
清算需要有人接盘。卖不掉的抵押品,清算只是无法执行的承诺 |
16 |
| 多层依赖脆弱性 |
攻击最弱一环,收益按最上层算 |
16 |
五、C5 经济与博弈#
| 术语 |
含义 |
出处 |
| 可租用的权力 |
⭐⭐ 任何能在一个区块内获得的权力都不是权力 |
17 |
| 治理攻击 |
闪电贷买投票权 ⟹ 投票 ⟹ 通过 ⟹ 执行,全在一笔交易内 |
17 |
| 投票权快照 |
⭐ 时点必须早于提案创建 ⟹ 闪电贷彻底失效 |
17 |
| 应急通道 |
⚠️ 一条合法的、绕过全部保护的路径。应只能"做减法" |
17 |
| 自我清算 |
主动把自己弄成深度不健康,吃最高档清算折扣 |
18 |
| 约定 vs 机制 |
⭐ “所有 X 都要做 Y"应实现成”不做 Y 就无法成为 X" |
18 |
| 递增激励的反向利用 |
“情况越糟奖励越高” ⟹ 有人会把情况弄糟 |
18 |
| 第一存款人攻击 |
空金库存 1 wei + 捐赠 ⟹ 后续存款人份额向下取整成 0 |
19 |
| 虚拟份额偏移 |
OZ 的 _decimalsOffset:offset=6 ⟹ 攻击成本 ×10⁶ |
19 |
六、C6 工具链#
| 术语 |
含义 |
出处 |
| 信任基础(TCB) |
⭐⭐ 代码 + 编译器 + 依赖库 + 部署流程 + 前端 + 开发机 |
20、25 |
| 编译器 bug |
⚠️ 源码正确、字节码错误。读源码发现不了 |
20 |
| 可复现构建 |
重新编译并与链上字节码逐字节比对 |
20 |
| 验证运行时行为 |
⭐ 安全测试要验证字节码的实际行为,不是源码写了什么 |
20 |
| 供应链攻击 |
攻击者不攻击你,他攻击你信任的东西 |
25 |
七、C7 密钥与签名流程#
| 术语 |
含义 |
出处 |
| 名义门槛 vs 实际门槛 |
⭐⭐ 按"共享攻击面"给密钥分组。Ronin 名义 5/9,实际 1/1 |
22 |
| 盲签 |
⭐签的是一个哈希,对它含义的理解全部来自一个可能被攻破的界面 |
23 |
operation = 1 |
⭐ Safe 交易里的 DELEGATECALL。等于把整个钱包交出去 |
23、25 |
| 签名的语义鸿沟 |
签名证明"某把私钥同意了某个哈希",不证明那个人理解了它 |
23 |
| 异常的常态化 |
⭐ 事故最可靠的前兆,是一个被大家习惯了的小毛病 |
24 |
| 定向投毒 |
⚠️ 恶意代码只对特定目标激活 ⟹ 抽查和用户反馈结构上失效 |
25 |
| 引用类输入 |
⭐ address / account / index / selector —— 也是需要被验证的输入 |
21 |
| 结构性 vs 程序性防御 |
前者不依赖人(白名单、限额、时间锁),后者依赖人正确执行 |
25 |
| 检测时间 |
一个可以设计的安全参数。六天 ⟹ 追回无望;几分钟 ⟹ 可止损 |
22 |
八、⭐ 反复出现的十条#
1. ⭐⭐ CEI 是第一道防线,锁是第二道 [1][20]
2. ⭐⭐ 凡是"本该只发生一次"的事,必须有显式状态位保证 [4]
3. ⭐⭐ 零值必须表示"不存在" [7]
4. ⭐⭐ 算术运行在类型范围上,不是业务范围上 [8][9]
5. ⭐⭐ 任何单区块内可改变的量,不能单独决定资金去向 [12][17]
6. ⭐⭐ "所有 X 都要做 Y" ⟹ "不做 Y 就无法成为 X" [18]
7. ⭐⭐ 引用(地址/账户)也是需要验证的输入 [6][21]
8. ⭐⭐ 限额是唯一在"验证已失守"后还起作用的机制 [7][10][21]
9. ⭐⭐ 结构性防御 > 程序性防御 [25]
10. ⭐⭐ 漏洞常常长在两份代码之间,而审计按合约划范围 [2][3][7]
相关:上线前检查清单 · 复盘来源与工具