web3-audit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWEB3 SMART CONTRACT AUDIT
WEB3 智能合约审计
10 bug classes. Pre-dive kill signals. Foundry PoC template. Real paid examples.
10类漏洞、审计前筛选排除信号、Foundry PoC模板、真实赏金案例。
PRE-DIVE KILL SIGNALS (check BEFORE any code review)
审计前筛选排除信号(代码审查前必查)
ZKsync lesson: $322M TVL + OZ audit + 750K LOC + 5 sessions = 0 findings. Large well-audited bridges are extremely hard.
- TVL < $500K → max payout capped too low for effort
- 2+ top-tier audits (Halborn, ToB, Cyfrin, OpenZeppelin) on simple protocol → bugs already found
- Protocol < 500 lines, single A→B→C flow → minimal attack surface
- Formula: — if < $10K, skip
max_realistic_payout = min(10% × TVL, program_cap)
Soft kill: OZ/ToB/Cyfrin audit on current version + codebase > 500K LOC → expect 40+ hours for maybe 1 finding. Only proceed if bounty floor > $50K AND you have protocol-specific expertise.
Target scoring (go if >= 6/10):
- TVL > $10M: +2
- Immunefi program with Critical >= $50K: +2
- No top-tier audit on current version: +2
- < 30 days since deploy: +1
- Protocol you've hunted before: +1
- Source code + natspec comments: +1
- Upgradeable proxies: +1
ZKsync教训:3.22亿美元TVL + OpenZeppelin审计 + 75万行代码 + 5轮审计 = 0漏洞发现。大型且经过严格审计的桥接协议极难找到漏洞。
- TVL < 50万美元 → 最高赏金上限过低,投入产出比不足
- 2次及以上顶级审计(Halborn、ToB、Cyfrin、OpenZeppelin)的简单协议 → 漏洞已被发现
- 协议代码少于500行,仅单一A→B→C流程 → 攻击面极小
- 计算公式:— 若低于1万美元,直接跳过
实际最高可获赏金 = min(10% × TVL, 项目赏金上限)
软性排除条件:当前版本经过OpenZeppelin/ToB/Cyfrin审计 + 代码量超过50万行 → 需投入40小时以上才可能找到1个漏洞。仅当赏金下限高于5万美元且你具备该协议领域的专业知识时,才继续推进。
目标评分(≥6/10则值得投入):
- TVL > 1000万美元:+2
- Immunefi项目Critical级漏洞赏金≥5万美元:+2
- 当前版本未经过顶级审计:+2
- 部署时间<30天:+1
- 曾挖掘过该协议:+1
- 提供源代码+natspec注释:+1
- 可升级代理:+1
THE ONE RULE
核心规则
"Read ALL sibling functions. Ifhas a modifier, checkvote(),poke(),reset(). The missing modifier on the sibling IS the bug."harvest()
This single rule explains 19% of all Critical findings.
"阅读所有关联函数。如果有修饰器,务必检查vote()、poke()、reset()。关联函数缺失修饰器本身就是漏洞。"harvest()
这条规则解释了19%的Critical级漏洞发现。
1. ACCOUNTING STATE DESYNCHRONIZATION
1. 账目状态不一致
#1 Critical bug class — 28% of all Criticals on Immunefi.
排名第1的Critical级漏洞类别 — 占Immunefi平台所有Critical级漏洞的28%。
What It Is
漏洞定义
Two state variables supposed to stay in sync. One code path updates A but forgets B. Later code reads both and makes decisions based on stale B.
Real Value = A - B
If A updated but B isn't → Real Value appears larger → phantom value两个本应保持同步的状态变量,某条代码路径更新了变量A却忘记更新变量B。后续代码读取这两个变量时,会基于过时的B做出错误决策。
实际价值 = A - B
若A更新但B未更新 → 实际价值被高估 → 产生虚假价值Root Cause Patterns
根源模式
Variant 1: Phantom Yield (Yeet protocol — 35 duplicate reports)
solidity
function startUnstake(uint256 amount) external {
totalSupply -= amount; // decremented BEFORE transfer
// aToken.balanceOf(this) still reflects old value
// yieldAmount = aToken.balanceOf - totalSupply = phantom yield
}Variant 2: Fast Path Skips State Update (Alchemix V3)
solidity
function claimRedemption(uint256 tokenId) external {
if (transmuter.balance >= amount) {
transmuter.transfer(user, amount);
_burn(tokenId);
return; // EARLY RETURN — cumulativeEarmarked, _redemptionWeight, totalDebt never updated
}
// Slow path: updates all state vars correctly
alchemist.redeem(...);
}Variant 3: Update Happens in Wrong Order (Alchemix)
solidity
function deposit(uint256 amount) external {
_shares = (amount * totalShares) / totalAssets; // calculated BEFORE deposit
totalAssets += amount; // assets added AFTER shares calculated → wrong rate
}变体1:虚假收益(Yeet协议 — 35份重复报告)
solidity
function startUnstake(uint256 amount) external {
totalSupply -= amount; // 转账前先减少总供应量
// aToken.balanceOf(this)仍反映旧值
// 收益金额 = aToken.balanceOf - totalSupply = 虚假收益
}变体2:快速路径跳过状态更新(Alchemix V3)
solidity
function claimRedemption(uint256 tokenId) external {
if (transmuter.balance >= amount) {
transmuter.transfer(user, amount);
_burn(tokenId);
return; // 提前返回 — cumulativeEarmarked、_redemptionWeight、totalDebt从未更新
}
// 慢速路径:正确更新所有状态变量
alchemist.redeem(...);
}变体3:更新顺序错误(Alchemix)
solidity
function deposit(uint256 amount) external {
_shares = (amount * totalShares) / totalAssets; // 存款前先计算份额
totalAssets += amount; // 资产添加晚于份额计算 → 汇率错误
}Grep Patterns
Grep检索模式
bash
undefinedbash
undefinedFind all accounting variables
查找所有账目相关变量
grep -rn "totalSupply|totalShares|totalAssets|totalDebt|cumulativeReward|rewardPerShare" contracts/
grep -rn "totalSupply|totalShares|totalAssets|totalDebt|cumulativeReward|rewardPerShare" contracts/
Find all early returns in claim/redeem functions
查找claim/redeem函数中的所有提前返回语句
grep -rn "\breturn\b" contracts/ -B3 | grep -B3 "if\b"
grep -rn "\breturn\b" contracts/ -B3 | grep -B3 "if\b"
For each early return: which state updates in normal path are skipped?
针对每个提前返回:检查正常路径中的哪些状态更新被跳过?
---
---2. ACCESS CONTROL
2. 访问控制
#2 Critical — 19% of Criticals. $953M lost in 2024 alone.
排名第2的Critical级漏洞 — 占Critical级漏洞的19%。仅2024年就因这类漏洞损失9.53亿美元。
Variant 1: Missing Modifier on Sibling Function
变体1:关联函数缺失修饰器
solidity
function vote(uint256 tokenId) external onlyNewEpoch(tokenId) { // guarded
function reset(uint256 tokenId) external onlyNewEpoch(tokenId) { // guarded
function poke(uint256 tokenId) external { // NO GUARD → infinite FLUX inflation
}solidity
function vote(uint256 tokenId) external onlyNewEpoch(tokenId) { // 有防护
function reset(uint256 tokenId) external onlyNewEpoch(tokenId) { // 有防护
function poke(uint256 tokenId) external { // 无防护 → 无限FLUX通胀
}Variant 2: Wrong Check (Existence vs Ownership)
变体2:错误校验(存在性 vs 所有权)
solidity
function split(uint256 tokenId, uint256 amount) external {
_requireOwned(tokenId); // checks if token EXISTS, not if caller OWNS it
_burn(tokenId);
_mint(msg.sender, amount); // attacker steals tokens they don't own
}solidity
function split(uint256 tokenId, uint256 amount) external {
_requireOwned(tokenId); // 检查代币是否存在,而非调用者是否拥有代币
_burn(tokenId);
_mint(msg.sender, amount); // 攻击者窃取不属于自己的代币
}Variant 3: Silent Modifier (if vs require)
变体3:静默修饰器(if vs require)
solidity
// VULNERABLE — non-admin silently gets through:
modifier onlyAdmin() {
if (msg.sender == admin) {
_; // body only executes for admin, but non-admin doesn't revert
}
}
// CORRECT: require(msg.sender == admin, "Not admin"); _;solidity
// 存在漏洞 — 非管理员可静默通过:
modifier onlyAdmin() {
if (msg.sender == admin) {
_; // 仅管理员可执行函数体,但非管理员不会触发回滚
}
}
// 正确写法:require(msg.sender == admin, "Not admin"); _;Variant 4: Uninitialized Proxy
变体4:未初始化代理
solidity
function initialize(address _owner) public { // MISSING: initializer modifier
owner = _owner; // anyone can call → become owner
}
// Fix: constructor() { _disableInitializers(); }solidity
function initialize(address _owner) public { // 缺失:initializer修饰器
owner = _owner; // 任何人都可调用 → 成为所有者
}
// 修复方案:constructor() { _disableInitializers(); }Grep Patterns
Grep检索模式
bash
undefinedbash
undefinedFind sibling function families — do ALL have the same modifier set?
查找关联函数组 — 是否所有函数都有相同的修饰器集合?
grep -rn "function vote|function poke|function reset|function update|function claim|function harvest" contracts/ -A2
grep -rn "function vote|function poke|function reset|function update|function claim|function harvest" contracts/ -A2
Ownership check: existence vs ownership?
所有权校验:是存在性还是所有权?
grep -rn "_requireOwned|ownerOf|_isApprovedOrOwner|_checkAuthorized" contracts/ -B5
grep -rn "_requireOwned|ownerOf|_isApprovedOrOwner|_checkAuthorized" contracts/ -B5
Silent modifiers
静默修饰器
grep -rn "modifier\b" contracts/ -A8 | grep -B3 "if (" | grep -v "require|revert"
grep -rn "modifier\b" contracts/ -A8 | grep -B3 "if (" | grep -v "require|revert"
Uninitialized initializer
未初始化的initialize函数
grep -rn "function initialize\b" contracts/ -A3
grep -rn "_disableInitializers()" contracts/
undefinedgrep -rn "function initialize\b" contracts/ -A3
grep -rn "_disableInitializers()" contracts/
undefinedReal Paid Examples
真实赏金案例
| Protocol | Payout | Bug |
|---|---|---|
| Wormhole | $10M | Uninitialized UUPS proxy → anyone calls initialize() |
| ZeroLend | n/a | split() uses existence check, not ownership check |
| Alchemix | n/a | poke() missing onlyNewEpoch → infinite FLUX inflation |
| Parity | $150M frozen | No access control on initWallet() in library |
| 协议 | 赏金 | 漏洞类型 |
|---|---|---|
| Wormhole | 1000万美元 | 未初始化的UUPS代理 → 任何人都可调用initialize() |
| ZeroLend | 未公开 | split()使用存在性校验而非所有权校验 |
| Alchemix | 未公开 | poke()缺失onlyNewEpoch修饰器 → 无限FLUX通胀 |
| Parity | 1.5亿美元资产冻结 | 库中的initWallet()无访问控制 |
3. INCOMPLETE CODE PATH
3. 代码路径不完整
#3 Critical — 17% of Criticals.
排名第3的Critical级漏洞 — 占Critical级漏洞的17%。
The Function Family Comparison Test
函数组对比测试法
1. List all state changes in function A (deposit/place/create)
2. List all state changes in function B (withdraw/update/cancel)
3. For each state change in A: does B have the corresponding reverse?
4. For each token transfer in A: does B have the corresponding refund?
If A does X but B doesn't do the reverse of X → BUG.1. 列出函数A(deposit/place/create)中的所有状态变更
2. 列出函数B(withdraw/update/cancel)中的所有状态变更
3. 针对A中的每一项状态变更:B是否有对应的反向操作?
4. 针对A中的每一笔代币转账:B是否有对应的退款操作?
如果A执行了X但B未执行X的反向操作 → 存在漏洞。Variant 1: Update Function Missing Refund (ThunderNFT)
变体1:更新函数缺失退款逻辑(ThunderNFT)
solidity
function place_order(OrderInput calldata order) external {
token.safeTransferFrom(msg.sender, address(this), order.price); // takes tokens
orders[orderId] = order;
}
function update_order(OrderInput calldata updatedOrder) external {
// BUG: NO REFUND for sell orders when price decreases → tokens permanently stuck
orders[orderId] = updatedOrder;
}solidity
function place_order(OrderInput calldata order) external {
token.safeTransferFrom(msg.sender, address(this), order.price); // 收取代币
orders[orderId] = order;
}
function update_order(OrderInput calldata updatedOrder) external {
// 漏洞:当售价降低时,卖出订单无退款逻辑 → 代币永久锁定
orders[orderId] = updatedOrder;
}Variant 2: Partial Fill Token Stuck (Plume)
变体2:部分填充导致代币锁定(Plume)
solidity
function swapForETH(uint256 amountIn) external {
token.safeTransferFrom(msg.sender, address(this), amountIn);
uint256 filled = dex.swap(amountIn); // partial fill possible
_refundExcessEth(amountIn - filled); // BUG: refunds ETH only, not ERC20
}solidity
function swapForETH(uint256 amountIn) external {
token.safeTransferFrom(msg.sender, address(this), amountIn);
uint256 filled = dex.swap(amountIn); // 可能出现部分填充
_refundExcessEth(amountIn - filled); // 漏洞:仅退款ETH,未退款ERC20代币
}Variant 3: mint() Bypasses Check That deposit() Has (MetaPool)
变体3:mint()绕过deposit()的校验逻辑(MetaPool)
solidity
function deposit(uint256 assets, address receiver) public override {
shares = _deposit(assets, receiver); // includes receipt validation
}
function mint(uint256 shares, address receiver) public override {
assets = convertToAssets(shares);
_mint(receiver, shares); // MISSING: _deposit() validation → mints without receiving assets
}solidity
function deposit(uint256 assets, address receiver) public override {
shares = _deposit(assets, receiver); // 包含收据验证
}
function mint(uint256 shares, address receiver) public override {
assets = convertToAssets(shares);
_mint(receiver, shares); // 缺失:_deposit()验证 → 未接收资产就铸造份额
}Grep Patterns
Grep检索模式
bash
grep -rn "function place_\|function create_\|function add_\|function open_" contracts/ -A5
grep -rn "function update_\|function modify_\|function cancel_" contracts/ -A5
grep -rn "safeApprove\b" contracts/ # safeApprove without zero-reset before
grep -rn "delete\b" contracts/ -B5 -A5 # delete before operation completes
grep -rn "function deposit\|function mint\|function withdraw\|function redeem" contracts/ -A10bash
grep -rn "function place_\|function create_\|function add_\|function open_" contracts/ -A5
grep -rn "function update_\|function modify_\|function cancel_" contracts/ -A5
grep -rn "safeApprove\b" contracts/ // 未先重置为0的safeApprove操作
grep -rn "delete\b" contracts/ -B5 -A5 // 操作完成前执行delete
grep -rn "function deposit\|function mint\|function withdraw\|function redeem" contracts/ -A104. OFF-BY-ONE & BOUNDARY CONDITIONS
4. 边界溢出与边界条件
#4 High — 22% of Highs. Single character change. Massive impact.
排名第4的High级漏洞 — 占High级漏洞的22%。仅需修改单个字符,影响巨大。
Root Cause
根源
solidity
// VeChain Stargate — post-exit reward drain:
function _claimableDelegationPeriods(address delegator) internal view returns (uint256) {
if (endPeriod > nextClaimablePeriod) { // BUG: should be >=
return 0; // exited users get nothing
}
return nextClaimablePeriod - lastClaimedPeriod; // rewards for period AFTER exit
}solidity
// VeChain Stargate — 退出后奖励被盗:
function _claimableDelegationPeriods(address delegator) internal view returns (uint256) {
if (endPeriod > nextClaimablePeriod) { // 漏洞:应为>=
return 0; // 退出用户无法获得任何奖励
}
return nextClaimablePeriod - lastClaimedPeriod; // 退出后仍可获取奖励
}Mental Test for Every Comparison
所有比较逻辑的心智校验
For every: "What happens when A == B?" Is that correct?if (A > B)
对于每一处:"当A == B时会发生什么?"这是否符合预期?if (A > B)
6 Boundary Locations to Check
需检查的6类边界位置
- Period/Epoch boundaries: vs
>at period end>= - Time-based locks: does lock or unlock?
block.timestamp == deadline - Loop break conditions: with
breakvs>>= - Array index boundaries: (should be
i <= array.length)i < array.length - Amount/balance boundaries: allows exact full withdrawal?
>= amount - Rounding/precision: can any input produce 0 output that should be non-zero?
- 周期/纪元边界:周期结束时使用vs
>>= - 时间锁:是锁定还是解锁?
block.timestamp == deadline - 循环终止条件:使用vs
>触发>=break - 数组索引边界:(应为
i <= array.length)i < array.length - 金额/余额边界:是否允许全额提取?
>= amount - 舍入/精度:是否存在输入会导致本应非零的输出变为0?
Grep Patterns
Grep检索模式
bash
undefinedbash
undefinedBoundaries in comparisons
比较逻辑中的边界
grep -rn "Period|Epoch|Round|Deadline|period|epoch|deadline" contracts/ -A3 | grep "[<>][^=]"
grep -rn "Period|Epoch|Round|Deadline|period|epoch|deadline" contracts/ -A3 | grep "[<>][^=]"
Loop breaks
循环终止
grep -rn "\bbreak\b" contracts/ -B10
grep -rn "\bbreak\b" contracts/ -B10
Off-by-one in array access
数组访问中的边界溢出
grep -rn ".length\s*-\s1|i\s<=\s*.*.length\b" contracts/
---grep -rn ".length\s*-\s1|i\s<=\s*.*.length\b" contracts/
---5. ORACLE / PRICE MANIPULATION
5. 预言机/价格操纵
12% of all reports. Largest individual payouts. $117M Mango, $70M Curve.
占所有报告的12%。单笔赏金最高。Mango损失1.17亿美元,Curve损失7000万美元。
Bug A: Missing Staleness Check (most common)
漏洞A:缺失时效性检查(最常见)
solidity
// VULNERABLE:
(, int256 price,,,) = priceFeed.latestRoundData();
return uint256(price); // If Chainlink node goes down, stale price returned indefinitely
// CORRECT:
(, int256 price,, uint256 updatedAt,) = priceFeed.latestRoundData();
require(block.timestamp - updatedAt <= MAX_PRICE_AGE, "Stale price");
require(price > 0, "Invalid price");solidity
// 存在漏洞:
(, int256 price,,,) = priceFeed.latestRoundData();
return uint256(price); // 若Chainlink节点宕机,将无限期返回过时价格
// 正确写法:
(, int256 price,, uint256 updatedAt,) = priceFeed.latestRoundData();
require(block.timestamp - updatedAt <= MAX_PRICE_AGE, "Stale price");
require(price > 0, "Invalid price");Bug B: Missing Confidence Interval (Pyth)
漏洞B:缺失置信区间校验(Pyth)
solidity
// VULNERABLE:
PythStructs.Price memory p = pyth.getPriceUnsafe(priceFeed);
return p.price; // ignores p.conf (confidence interval)
// CORRECT:
require(p.conf * 10 <= uint64(p.price), "Price too uncertain");
// conf > 10% of price = untrustworthysolidity
// 存在漏洞:
PythStructs.Price memory p = pyth.getPriceUnsafe(priceFeed);
return p.price; // 忽略p.conf(置信区间)
// 正确写法:
require(p.conf * 10 <= uint64(p.price), "Price too uncertain");
// conf > 价格的10% = 不可信Bug C: TWAP Too Short (flash loan manipulatable)
漏洞C:TWAP窗口过短(可被闪电贷操纵)
solidity
// VULNERABLE: 60-second TWAP
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = 60; secondsAgos[1] = 0;
// Flash loan can shift price for entire 60s window
// CORRECT: 1800s minimum TWAP (30 min)solidity
// 存在漏洞:60秒TWAP
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = 60; secondsAgos[1] = 0;
// 闪电贷可在整个60秒窗口内操纵价格
// 正确写法:最小1800秒TWAP(30分钟)Bug D: Single-Source Oracle
漏洞D:单一数据源预言机
solidity
// VULNERABLE: only Uniswap spot price
uint price = getUniswapSpotPrice(token); // flash loan manipulatable
// CORRECT: Chainlink primary, Uniswap TWAP as fallback, require close agreementsolidity
// 存在漏洞:仅使用Uniswap现货价格
uint price = getUniswapSpotPrice(token); // 可被闪电贷操纵
// 正确写法:以Chainlink为主要数据源,Uniswap TWAP为备用,需确保两者价格接近Grep Patterns
Grep检索模式
bash
undefinedbash
undefinedMissing staleness check
缺失时效性检查
grep -rn "latestRoundData" contracts/ -A5 | grep -v "updatedAt|timestamp"
grep -rn "latestRoundData" contracts/ -A5 | grep -v "updatedAt|timestamp"
Pyth price usage — confidence interval checked?
Pyth价格使用 — 是否校验置信区间?
grep -rn "getPriceUnsafe|getPrice\b" contracts/ -A8 | grep -v "conf|confidence"
grep -rn "getPriceUnsafe|getPrice\b" contracts/ -A8 | grep -v "conf|confidence"
TWAP windows — short TWAP flag
TWAP窗口 — 短TWAP标记
grep -rn "secondsAgo|TWAP|cardinality" contracts/ -A5
---grep -rn "secondsAgo|TWAP|cardinality" contracts/ -A5
---6. ERC4626 VAULT ATTACKS
6. ERC4626 金库攻击
Exchange Rate Manipulation (near-empty vault)
汇率操纵(近乎空金库)
solidity
// VULNERABLE — first depositor attack:
// 1. Attacker deposits 1 wei → gets 1 share
// 2. Attacker donates large amount directly (transfer, not deposit)
// 3. Exchange rate: 1 share = (1 + donation) assets
// 4. Victim deposits → rounds down to 0 shares → free donation to attacker
// CORRECT: virtual shares (OpenZeppelin v4.9+)
function _decimalsOffset() internal view virtual override returns (uint8) {
return 9; // add 1e9 virtual shares + assets to prevent manipulation
}solidity
// 存在漏洞 — 首次存款攻击:
// 1. 攻击者存入1 wei → 获取1份额
// 2. 攻击者直接捐赠大量资产(transfer而非deposit)
// 3. 汇率:1份额 = (1 + 捐赠额)资产
// 4. 受害者存款 → 份额向下取整为0 → 资产无偿捐赠给攻击者
// 正确写法:虚拟份额(OpenZeppelin v4.9+)
function _decimalsOffset() internal view virtual override returns (uint8) {
return 9; // 添加1e9虚拟份额+资产以防止操纵
}ERC4626 Transfer (moves shares but not stake/lock records)
ERC4626转账(仅转移份额但未转移质押/锁定记录)
solidity
// VULNERABLE: shares transferred, but lock records stay with original owner
// → shares stuck, can't redeem → permanent freeze (Belong pattern)
function transfer(address to, uint256 amount) external override {
_transfer(msg.sender, to, amount); // moves shares
// MISSING: transfer lock record from msg.sender to `to`
}solidity
// 存在漏洞:份额已转移,但锁定记录仍属于原所有者
// → 份额被锁定,无法赎回 → 永久冻结(Belong模式)
function transfer(address to, uint256 amount) external override {
_transfer(msg.sender, to, amount); // 转移份额
// 缺失:将锁定记录从msg.sender转移至`to`
}Grep Patterns
Grep检索模式
bash
grep -rn "function transfer\|function transferFrom" contracts/ -A15
grep -rn "function deposit\|function mint\|function withdraw\|function redeem" contracts/ -A10bash
grep -rn "function transfer\|function transferFrom" contracts/ -A15
grep -rn "function deposit\|function mint\|function withdraw\|function redeem" contracts/ -A107. REENTRANCY
7. 重入攻击
2016–present. CEI pattern prevents it. Still found in DeFi.
2016年至今。CEI模式可预防,但仍在DeFi中被发现。
Variants
变体
- Single-function: attacker re-enters same function before state updated
- Cross-function: re-enters a sibling function with stale state
- Cross-contract: re-enters via a callback to another protocol
- Read-only: re-enters a view function that returns stale data used by attacker
- 单函数重入:攻击者在状态更新前重新进入同一函数
- 跨函数重入:重新进入关联函数,利用过时状态
- 跨合约重入:通过回调重新进入另一协议
- 只读重入:重新进入视图函数,获取攻击者可利用的过时数据
Root Cause Pattern
根源模式
solidity
// VULNERABLE (effects after interaction):
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
(bool success,) = msg.sender.call{value: amount}(""); // INTERACTION first
require(success);
balances[msg.sender] -= amount; // EFFECT after → reentrancy window
}
// CORRECT (CEI — Checks, Effects, Interactions):
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount); // CHECK
balances[msg.sender] -= amount; // EFFECT
(bool success,) = msg.sender.call{value: amount}(""); // INTERACTION last
require(success);
}solidity
// 存在漏洞(交互后更新状态):
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
(bool success,) = msg.sender.call{value: amount}(""); // 先执行交互
require(success);
balances[msg.sender] -= amount; // 后更新状态 → 存在重入窗口
}
// 正确写法(CEI — 校验、状态更新、交互):
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount); // 校验
balances[msg.sender] -= amount; // 状态更新
(bool success,) = msg.sender.call{value: amount}(""); // 最后执行交互
require(success);
}Grep Patterns
Grep检索模式
bash
undefinedbash
// 状态更新前的外部调用
grep -rn "\.call{value\|safeTransfer\|transfer(" contracts/ -B10 | grep -v "require\|revert"
// 关键函数缺失nonReentrant修饰器
grep -rn "function withdraw\|function redeem\|function claim" contracts/ -A2 | grep -v "nonReentrant"
// 重入防护的存储槽
grep -rn "nonReentrant\|ReentrancyGuard\|_notEntered" contracts/External calls before state updates
8. 闪电贷攻击
—
通过闪电贷操纵预言机
grep -rn ".call{value|safeTransfer|transfer(" contracts/ -B10 | grep -v "require|revert"
solidity
// 攻击流程:
// 1. 从Aave借入1亿美元闪电贷
// 2. 在Uniswap池抛售代币 → 压低现货价格
// 3. 协议读取Uniswap现货价格 → 接受抵押不足的贷款
// 4. 以低价抵押品借入最大额度
// 5. 偿还闪电贷,保留利润Missing nonReentrant modifier on critical functions
价格预言机合理性检查(需关注内容)
grep -rn "function withdraw|function redeem|function claim" contracts/ -A2 | grep -v "nonReentrant"
bash
grep -rn "getReserves\|getAmountsOut\|slot0\b" contracts/ -A5
// 从储备获取现货价格 = 可被闪电贷操纵
// slot0 = Uniswap V3现货价格 = 可被操纵Storage slot for reentrancy guard
9. 签名重放
—
缺失随机数(Nonce)
grep -rn "nonReentrant|ReentrancyGuard|_notEntered" contracts/
---solidity
// 存在漏洞:
function permit(address owner, address spender, uint256 value,
uint256 deadline, uint8 v, bytes32 r, bytes32 s) external {
bytes32 hash = keccak256(abi.encodePacked(owner, spender, value, deadline));
// 缺失:未包含nonce → 同一签名可重复使用
require(ecrecover(hash, v, r, s) == owner);
}8. FLASH LOAN ATTACKS
缺失链ID
Oracle Manipulation via Flash Loan
—
solidity
// Attack flow:
// 1. Borrow $100M from Aave flash loan
// 2. Dump token in Uniswap pool → crash spot price
// 3. Protocol reads Uniswap spot → undercollateralized loans accepted
// 4. Borrow max against cheap collateral
// 5. Repay flash loan, keep profitssolidity
// 存在漏洞:签名在主网、测试网及所有分叉链均有效
bytes32 hash = keccak256(abi.encodePacked(params));
// 缺失:hash中未包含block.chainid → 在任意链上均可生效Price Oracle Sanity Checks (what to look for)
Grep检索模式
bash
grep -rn "getReserves\|getAmountsOut\|slot0\b" contracts/ -A5bash
grep -rn "ecrecover\|ECDSA\.recover" contracts/ -B20
// 检查:签名哈希是否包含nonce + chainId + 合约地址?
grep -rn "nonce\|_nonces\|nonces\[" contracts/spot price from reserves = manipulatable with flash loan
10. 代理/升级问题
slot0 = Uniswap V3 spot price = manipulatable
存储冲突
---solidity
// 实现合约与代理合约共享存储布局
// 代理存储槽0:_owner
// 实现合约存储槽0:_initialized
// → 写入_initialized会覆盖_owner9. SIGNATURE REPLAY
未初始化的实现合约
Missing Nonce
—
solidity
// VULNERABLE:
function permit(address owner, address spender, uint256 value,
uint256 deadline, uint8 v, bytes32 r, bytes32 s) external {
bytes32 hash = keccak256(abi.encodePacked(owner, spender, value, deadline));
// MISSING: nonce not included → same signature usable multiple times
require(ecrecover(hash, v, r, s) == owner);
}solidity
// 若实现合约可直接初始化 → 任何人都可成为实现合约的所有者
// 攻击方式:调用实现合约的initialize() → 调用upgradeTo() → 替换逻辑Missing Chain ID
委托调用至用户可控地址
solidity
// VULNERABLE: signature valid on mainnet AND testnet AND all forks
bytes32 hash = keccak256(abi.encodePacked(params));
// MISSING: block.chainid not in hash → works on any chainsolidity
function execute(address target, bytes calldata data) external onlyOwner {
target.delegatecall(data); // target已验证,但如果所有者被攻陷呢?
}Grep Patterns
Grep检索模式
bash
grep -rn "ecrecover\|ECDSA\.recover" contracts/ -B20bash
// UUPS初始化防护
grep -rn "function initialize\b\|_disableInitializers\|initializer" contracts/
// 委托调用
grep -rn "delegatecall\b" contracts/ -B3 -A5
// 存储布局 — 代理是否使用EIP-1967存储槽?
grep -rn "0x360894\|EIP1967\|_IMPLEMENTATION_SLOT" contracts/Check: does the signed hash include nonce + chainId + contract address?
FOUNDRY POC模板
grep -rn "nonce|_nonces|nonces[" contracts/
---solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/VulnerableContract.sol";
contract ExploitTest is Test {
VulnerableContract target;
address attacker = makeAddr("attacker");
address victim = makeAddr("victim");
function setUp() public {
// 分叉主网至指定区块
vm.createSelectFork("mainnet", BLOCK_NUMBER);
// 部署或加载目标合约
target = VulnerableContract(TARGET_ADDRESS);
// 为账户充值
deal(address(token), attacker, INITIAL_BALANCE);
deal(address(token), victim, VICTIM_BALANCE);
}
function test_exploit() public {
console.log("Attacker balance before:", token.balanceOf(attacker));
vm.startPrank(attacker);
// 步骤1:设置攻击条件
// 步骤2:执行攻击
// 步骤3:验证攻击效果
vm.stopPrank();
console.log("Attacker balance after:", token.balanceOf(attacker));
assertGt(token.balanceOf(attacker), INITIAL_BALANCE, "Exploit failed");
}
}10. PROXY / UPGRADE ISSUES
Foundry核心作弊码
Storage Collision
—
solidity
// Implementation and proxy share storage layout
// Proxy slot 0: _owner
// Implementation slot 0: _initialized
// → writing to _initialized overwrites _ownersolidity
vm.prank(address) // 下一次调用来自指定地址
vm.startPrank(address) // 所有调用来自指定地址,直到stopPrank()
vm.deal(address, amount) // 设置ETH余额
deal(token, address, amount) // 设置ERC20余额
vm.warp(timestamp) // 设置block.timestamp
vm.roll(blockNumber) // 设置block.number
vm.createSelectFork("mainnet", blockNumber) // 分叉主网
vm.expectRevert(bytes) // 下一次调用应触发回滚
vm.label(address, "name") // 为追踪输出添加标签
vm.assume(condition) // 模糊测试:丢弃条件为false的输入Uninitialized Implementation
运行测试
solidity
// If implementation can be initialized directly → anyone becomes owner of implementation
// Attack: call initialize() on implementation contract → call upgradeTo() → replace logicbash
undefineddelegatecall to User-Controlled Address
运行指定测试
solidity
function execute(address target, bytes calldata data) external onlyOwner {
target.delegatecall(data); // target is validated, but what if owner is compromised?
}forge test --match-test test_exploit -vvvv
Grep Patterns
带分叉运行测试
bash
undefinedforge test --match-test test_exploit -vvvv --fork-url $MAINNET_RPC
UUPS initialization protection
生成gas报告
grep -rn "function initialize\b|_disableInitializers|initializer" contracts/
forge test --gas-report
Delegate call
生成覆盖率报告
grep -rn "delegatecall\b" contracts/ -B3 -A5
forge coverage --report summary
---Storage layout — proxy uses EIP-1967 slots?
相关技能与链
grep -rn "0x360894|EIP1967|_IMPLEMENTATION_SLOT" contracts/
---- — 当目标是meme币/SPL代币而非DeFi协议时。流程差异:审计前筛选排除信号不同 — 本技能的"TVL<50万美元则跳过"不适用于meme币,这类代币的审计核心是 rug检查(铸币权限、冻结权限、LP锁定);此类场景应使用
meme-coin-audit技能。meme-coin-audit - — 当合约漏洞发现准备提交至Immunefi时。流程要点:Immunefi有自己的报告格式,但
triage-validation中对影响验证、链端到端测试的要求仍适用;提交前需针对Foundry PoC运行7Q校验。triage-validation - — 撰写Immunefi报告正文时。流程要点:
report-writing的Immunefi模板(包含Foundry PoC、根源代码片段、量化经济影响)是本技能漏洞发现内容的载体框架。report-writing - — 审计协议的链下攻击面(前端、管理API、RPC网关)时。流程要点:链上审计是本技能的职责;协议的任何Web2组件(前端、管理面板、索引器API)需交由
offensive-osint技能进行侦察。offensive-osint - — 决定是否开始审计时。流程要点:
bb-methodology的第0部分确认参与类型(Web3漏洞赏金/私有审计/智能合约审查);本技能的审计前筛选排除信号替代该参与类型的标准评分规则。bb-methodology
FOUNDRY POC TEMPLATE
操作说明(Claude-BugHunter)
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/VulnerableContract.sol";
contract ExploitTest is Test {
VulnerableContract target;
address attacker = makeAddr("attacker");
address victim = makeAddr("victim");
function setUp() public {
// Fork mainnet at specific block
vm.createSelectFork("mainnet", BLOCK_NUMBER);
// Deploy or load target
target = VulnerableContract(TARGET_ADDRESS);
// Fund accounts
deal(address(token), attacker, INITIAL_BALANCE);
deal(address(token), victim, VICTIM_BALANCE);
}
function test_exploit() public {
console.log("Attacker balance before:", token.balanceOf(attacker));
vm.startPrank(attacker);
// Step 1: Setup conditions
// Step 2: Execute exploit
// Step 3: Verify impact
vm.stopPrank();
console.log("Attacker balance after:", token.balanceOf(attacker));
assertGt(token.balanceOf(attacker), INITIAL_BALANCE, "Exploit failed");
}
}基于实际项目经验 + 2026年专属补充内容。 来自真实授权项目的经验 + 本仓库31+技能领域测试的第二阶段验证。上游内容覆盖"是什么";本层内容覆盖"何时有效vs何时无效"。
Key Foundry Cheatcodes
2026年仍能获得赏金的漏洞类别
solidity
vm.prank(address) // next call from address
vm.startPrank(address) // all calls from address until stopPrank()
vm.deal(address, amount) // set ETH balance
deal(token, address, amount) // set ERC20 balance
vm.warp(timestamp) // set block.timestamp
vm.roll(blockNumber) // set block.number
vm.createSelectFork("mainnet", blockNumber) // fork mainnet
vm.expectRevert(bytes) // next call should revert
vm.label(address, "name") // label for trace output
vm.assume(condition) // fuzz: discard inputs where false闪电贷攻击仍是Immunefi平台赏金最高的类别之一(2024-2026年排名前5)。其经济原理——借入5000万美元、操纵价格预言机、掏空资金池、偿还贷款——持续出现,因为新协议不断上线,而它们的可组合性假设在闪电贷导致的失衡场景下并不成立。
重入攻击仍能获得赏金,因为新协议不断使用ERC-777/钩子/回调。不要认为这类漏洞已消失——2023-2025年的赏金案例库包含40+针对CEI模式代码的重入漏洞(跨函数重入、状态变更过程中调用视图函数导致的只读重入)。
预言机操纵:赏金仍高,但难度更大。大多数项目现在使用Chainlink价格数据源;攻击目标是大多数项目同时使用的次要预言机(低流动性Uniswap V2池的TWAP、协议内部预言机、过时的 fallback路径)。审计故障转移链,而非仅关注主数据源。
Running Tests
自上游内容发布后的新增内容
bash
undefined- EIP-1153(临时存储) — 2024年引入。新型重入类别:同一交易中临时存储的读取会与持久存储不同步。审计任何/
tload使用场景,检查外部调用后的读取操作。tstore - EIP-7702(2025年Pectra硬分叉) — 新增EOA到智能账户的升级功能。通过重新授权实现类似ATO的新攻击原语:EOA一次签名即可授权给攻击者控制的合约,然后在授权间进行签名重放。
- 账户抽象(ERC-4337打包器) — 支付方赞助滥用和打包器恶意攻击。未强制严格发送者白名单的支付方合约会在首次调用时被掏空。
- ZK-rollup桥漏洞 — 跨rollup的证明重放、链下 prover攻陷、 sequencer审查导致的强制包含边缘案例。
- LST/LRT脱钩动态 — 流动质押和流动再质押代币假设在亏损场景下保持1:1挂钩;预言机假设挂钩,但市场反映脱钩,清算逻辑失效。
Run specific test
2026年工具栈
forge test --match-test test_exploit -vvvv
Foundry仍是测试框架。使用进行不变量测试;进行基于属性的测试;进行存储布局审计。Slither + Echidna用于静态分析+模糊测试。Mythril用于小型合约的符号执行。tenderly.co用于分叉+模拟(复现主网状态下攻击的最佳UX)。
forge test --gas-report --debugforge fuzzforge inspect对于Solana:使用anchor框架、sealevel-attacks案例库(anchor维护者整理的PoC)、soteria-sec / sec3扫描器。对于Move(Aptos、Sui):使用move-prover、aptos-cli的。
aptos move test对于跨链:Hyperlane和LayerZero均有审计工具仓库;桥漏洞需要模拟两端点,而非仅一端。
Run with fork
审计前筛选排除信号的适用场景
forge test --match-test test_exploit -vvvv --fork-url $MAINNET_RPC
TVL低于50万美元的项目不值得投入审计时间,除非赏金下限很高。审计公司已覆盖的项目=低ROI,除非你能发现他们遗漏的内容——查看审计报告的范围排除部分,了解他们明确未审计的内容(预言机、治理、链下组件、前端、管理路径)。
多签签名者>5 + 时间锁>48小时=低rug风险;如果你的漏洞发现需要假设团队恶意,那么其影响程度低,且可能因Immunefi的"中心化风险"排除条款而超出范围。阅读项目简介——大多数Immunefi项目明确降级或拒绝基于管理员恶意假设的漏洞发现。
Gas report
报告规范
forge test --gas-report
Immunefi要求提交Foundry PoC。无PoC的提交会被自动拒绝。需手动设置的PoC("先部署这个,再调用那个")通常会被降级——PoC应是单次调用即可证明影响,包含明确的验证被盗余额/铸造代币/损坏状态。
forge testassertEq严重程度声明必须严格使用Immunefi的严重程度矩阵;不要自定义严重程度。该矩阵基于TVL的直接经济损失百分比——针对50万美元协议的Critical级漏洞与针对5亿美元协议的Critical级漏洞赏金不同。提交前阅读项目的具体严重程度分配规则。
Coverage
—
forge coverage --report summary
---—
Related Skills & Chains
—
- — When the target is a meme coin / SPL token rather than a DeFi protocol. Workflow primitive: pre-dive kill signals diverge — this skill's "TVL < $500K skip" doesn't apply to meme coins where the rug check (mint authority, freeze authority, LP lock) is the entire audit; route to
meme-coin-auditinstead.meme-coin-audit - — When a contract finding is ready to be filed on Immunefi. Workflow primitive: Immunefi has its own report format, but the impact-validated, chain-end-to-end discipline of
triage-validationstill applies; run the 7Q gate against the Foundry PoC before submitting.triage-validation - — When writing the Immunefi report body. Workflow primitive:
report-writing's Immunefi template (with Foundry PoC, root cause code snippet, quantified economic impact) is the body skeleton this skill's findings feed into.report-writing - — When auditing a protocol's off-chain attack surface (frontend, admin API, RPC gateways). Workflow primitive: on-chain audit is this skill's job; any web2 component of the protocol (web-frontend, admin panel, indexer API) routes to
offensive-osintfor recon.offensive-osint - — When deciding whether to dive at all. Workflow primitive: PART 0 of
bb-methodologyconfirms engagement (web3 bug bounty / private audit / smart-contract review); this skill's pre-dive kill signals replace the standard scoring rubric for that engagement type.bb-methodology
—
Operator Notes (Claude-BugHunter)
—
Engagement-derived + 2026-specific additions to the vendored foundation. Wisdom from real authorized engagements + Phase 2 verification across this repo's 31+ skill-area live tests. The upstream content covers the WHAT; this layer covers the WHEN-IT-WORKS-vs-WHEN-IT-DOESN'T.
—
Bug classes still paying in 2026
—
Flash-loan attacks remain top-paid on Immunefi (top 5 in 2024-2026 by bounty). The economic primitive — borrow $50M, manipulate price oracle, drain pool, repay — keeps reappearing because new protocols keep shipping with composability assumptions that don't hold under flash-loaned imbalance.
Reentrancy IS still paying because new protocols keep shipping with ERC-777 / hooks / callbacks. Don't assume the class is dead — the 2023-2025 paid corpus contains 40+ reentrancy bugs against post-Checks-Effects-Interactions codebases (cross-function reentrancy, read-only reentrancy via view functions called during state-mid-flight).
Oracle manipulation: still paid heavily but harder. Most projects use Chainlink price feeds now; the attack target is the SECONDARY oracle most projects also use (TWAP from a low-liquidity Uniswap V2 pair, the protocol's own internal oracle, a stale fallback path). Audit the failover chain, not just the primary feed.
—
What's new since the vendored content was written
—
- EIP-1153 (transient storage) — introduced in 2024. New reentrancy classes: transient-storage reads cached across the same transaction can desync from persistent storage. Audit any /
tloadusage for read-after-external-call.tstore - EIP-7702 (Pectra hard fork 2025) — added EOA-to-smart-account upgrades. New ATO-like primitives via re-delegation: an EOA signed-once can delegate to a contract that the attacker controls, then signature replay across delegations.
- Account abstraction (ERC-4337 bundlers) — paymaster sponsorship abuse and bundler griefing. Paymaster contracts that don't enforce strict sender allowlists drain on first call.
- ZK-rollup bridge bugs — proof-replay across rollups, off-chain prover compromise, sequencer censorship leading to forced-inclusion edge cases.
- LST/LRT depeg dynamics — liquid-staking and liquid-restaking tokens that assume 1:1 peg under loss conditions; oracle assumes peg, market reflects depeg, liquidation logic breaks.
—
Tool stack for 2026
—
Foundry remains the test framework. for invariant testing; for property-based testing; for storage-layout audits. Slither + Echidna for static + fuzz. Mythril for symbolic execution on smaller contracts. tenderly.co for forking + simulation (best UX for replicating attacks against mainnet state).
forge test --gas-report --debugforge fuzzforge inspectFor Solana: anchor framework, sealevel-attacks corpus (curated PoCs by anchor maintainers), soteria-sec / sec3 scanner. For Move (Aptos, Sui): move-prover, aptos-cli .
aptos move testFor cross-chain: hyperlane and LayerZero each have audit-tooling repos; bridge bugs require simulating both endpoints, not just one.
—
Where pre-dive kill signals matter
—
TVL under $500K isn't worth the audit time unless the bounty floor is high. Audit firm already covered it = low ROI unless you find what they missed — look at the audit-report scope-exclusion section for what they EXPLICITLY didn't audit (oracles, governance, off-chain components, frontend, the admin path).
Multisig signers > 5 + timelock > 48h = low rug-pull risk; if your finding requires team-malicious assumptions, it's low-impact and likely out of scope per Immunefi's "centralization risk" exclusion. Read the program brief — most Immunefi programs explicitly downgrade or reject findings that assume admin malice.
—
Reporting discipline
—
Immunefi requires Foundry PoC. Submission without PoC is auto-rejected. Submission with a PoC that requires manual setup ("first deploy this, then call that") usually gets downgraded — the PoC should be a single invocation that proves the impact, with explicit on the drained balance / minted token / corrupted state.
forge testassertEqSeverity claims must use Immunefi's severity matrix exactly; don't invent severities. The matrix gates on direct economic loss percentage of TVL — a critical against a $500K protocol pays differently than a critical against a $500M one. Read the program's specific severity assignment before claiming Critical.
—