mcpField
W
web3-audit

web3-audit

github.com/elementalsoulsセキュリティ5.3k285 回インストール今日更新
リポジトリ

web3-audit について

Web3プロトコルを調査するセキュリティ研究者向けのスマートコントラクト監査スキルです。10種類のバグ分類を軸にコードレビュー項目をまとめ、事前の対象選定とFoundryによるPoC作成を案内します。

主な機能

  1. 01監査前の対象選定シグナル
  2. 02スマートコントラクトの10種類のバグ
  3. 03関連関数のアクセス制御確認
  4. 04Solidityの検索パターン
  5. 05FoundryのPoCテンプレート
  6. 06実際に報奨金が支払われた脆弱性事例

活用シーン

  1. 01コードレビュー前のプロトコル選定
  2. 02会計処理とアクセス制御の確認
  3. 03オラクルとERC4626ボールトのロジック確認
  4. 04Foundryでの概念実証の作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/elementalsouls/claude-bughunter --skill web3-audit

インストール後、Claude Code で/web3-auditを入力して読み込まれたか確認します。

詳細

Web3 スマートコントラクト監査

10 のバグクラス。着手前の中止シグナル。Foundry PoC テンプレート。実際の報奨金獲得事例。


着手前の中止シグナル(コードレビューの前に必ず確認)

ZKsync の教訓:TVL 3 億 2,200 万ドル + OZ 監査 + 75 万 LOC + 5 セッション = 発見ゼロ。大規模で十分に監査されたブリッジは、非常に攻略が困難です。

  1. TVL < 50 万ドル → 最大報奨金が、労力に見合わないほど低い
  2. トップクラスの監査が 2 件以上(Halborn、ToB、Cyfrin、OpenZeppelin)実施されているシンプルなプロトコル → バグはすでに発見済み
  3. プロトコルが 500 行未満で、単一の A→B→C フロー → 攻撃対象領域が最小限
  4. 計算式:max_realistic_payout = min(10% × TVL, program_cap) — 1 万ドル未満なら見送る

ソフト中止条件: 現行バージョンに対する OZ/ToB/Cyfrin の監査があり、かつコードベースが 50 万 LOC を超える場合 → 1 件見つかるかどうかで 40 時間以上かかる見込みです。報奨金の下限が 5 万ドルを超え、かつそのプロトコル固有の専門知識がある場合に限り、進めてください。

ターゲットのスコアリング(6/10 以上なら着手):

  • TVL > 1,000 万ドル:+2
  • Critical の報奨金が 5 万ドル以上の Immunefi プログラム:+2
  • 現行バージョンに対するトップクラスの監査がない:+2
  • デプロイから 30 日未満:+1
  • 以前に調査したことがあるプロトコル:+1
  • ソースコードと natspec コメントがある:+1
  • アップグレード可能なプロキシ:+1

唯一のルール

「すべての関連関数を読むこと。vote() にモディファイアがあるなら、poke()、reset()、harvest() も確認する。関連関数にモディファイアがないこと、それ自体がバグです。」

このルールだけで、Critical の発見事例全体の 19% を説明できます。


1. 会計状態の不整合

Critical バグクラスの第 1 位 — Immunefi の Critical 全体の 28%。

内容

同期を保つべき 2 つの状態変数があります。あるコードパスで A を更新しても、B の更新が抜けています。その後、コードが両方を読み取り、古い B に基づいて判断します。

Real Value = A - B
If A updated but B isn't → Real Value appears larger → phantom value
根本原因のパターン

パターン 1:架空の利回り(Yeet プロトコル — 重複報告 35 件)

function startUnstake(uint256 amount) external {
    totalSupply -= amount;  // decremented BEFORE transfer
    // aToken.balanceOf(this) still reflects old value
    // yieldAmount = aToken.balanceOf - totalSupply = phantom yield
}

パターン 2:高速パスで状態更新がスキップされる(Alchemix V3)

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(...);
}

パターン 3:更新順序が誤っている(Alchemix)

function deposit(uint256 amount) external {
    _shares = (amount * totalShares) / totalAssets;  // calculated BEFORE deposit
    totalAssets += amount;   // assets added AFTER shares calculated → wrong rate
}
grep パターン
# Find all accounting variables
grep -rn "totalSupply\|totalShares\|totalAssets\|totalDebt\|cumulativeReward\|rewardPerShare" contracts/

# Find all early returns in claim/redeem functions
grep -rn "\breturn\b" contracts/ -B3 | grep -B3 "if\b"

# For each early return: which state updates in normal path are skipped?

2. アクセス制御

Critical の第 2 位 — Critical 全体の 19%。2024 年だけで 9 億 5,300 万ドルが失われています。

パターン 1:関連関数にモディファイアがない
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
}
パターン 2:チェックの誤り(存在確認と所有権確認の取り違え)
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
}
パターン 3:作用しないモディファイア(if と require の違い)
// 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"); _;
パターン 4:未初期化のプロキシ
function initialize(address _owner) public {  // MISSING: initializer modifier
    owner = _owner;  // anyone can call → become owner
}
// Fix: constructor() { _disableInitializers(); }
grep パターン
# Find 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

# Ownership check: existence vs ownership?
grep -rn "_requireOwned\|ownerOf\|_isApprovedOrOwner\|_checkAuthorized" contracts/ -B5

# Silent modifiers
grep -rn "modifier\b" contracts/ -A8 | grep -B3 "if (" | grep -v "require\|revert"

# Uninitialized initializer
grep -rn "function initialize\b" contracts/ -A3
grep -rn "_disableInitializers()" contracts/
実際の報奨金獲得事例
プロトコル報奨金バグ
Wormhole1,000 万ドルUUPS プロキシが未初期化 → 誰でも initialize() を呼び出せる
ZeroLendなしsplit() が所有権確認ではなく、存在確認を使用
Alchemixなしpoke() に onlyNewEpoch がない → FLUX が無限に増加
Parity1 億 5,000 万ドル相当が凍結ライブラリ内の initWallet() にアクセス制御がない

3. 不完全なコードパス

Critical の第 3 位 — Critical 全体の 17%。

関数ファミリー比較テスト
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:更新関数に返金処理がない(ThunderNFT)
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;
}
パターン 2:一部約定したトークンが残留する(Plume)
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
}
パターン 3:mint() が deposit() にあるチェックを回避する(MetaPool)
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
}
grep パターン
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/ -A10

4. オフバイワンと境界条件

High の第 4 位 — High 全体の 22%。たった 1 文字の変更で、大きな影響が生じます。

根本原因
// 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
}
あらゆる比較で行う思考テスト

すべての if (A > B) について、「A == B のとき何が起こるか?」それは正しい動作か?

確認すべき 6 つの境界箇所
  1. 期間/エポックの境界:期間終了時に > と >= のどちらを使っているか
  2. 時間ベースのロック:block.timestamp == deadline のとき、ロックされるか解除されるか
  3. ループの終了条件:break で > と >= のどちらを使っているか
  4. 配列インデックスの境界:i <= array.length(i < array.length にすべき)
  5. 数量/残高の境界:>= amount で全額の引き出しが可能になるか
  6. 丸め処理/精度:ゼロ以外になるべき出力が 0 になる入力はないか
grep パターン
# Boundaries in comparisons
grep -rn "Period\|Epoch\|Round\|Deadline\|period\|epoch\|deadline" contracts/ -A3 | grep "[<>][^=]"

# Loop breaks
grep -rn "\bbreak\b" contracts/ -B10

# Off-by-one in array access
grep -rn "\.length\s*-\s*1\|i\s*<=\s*.*\.length\b" contracts/

5. オラクル/価格操作

報告全体の 12%。個別の報奨金としては最大規模。Mango で 1 億 1,700 万ドル、Curve で 7,000 万ドル。

バグ A:鮮度チェックがない(最も一般的)
// 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");
バグ B:信頼区間チェックがない(Pyth)
// 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 = untrustworthy
バグ C:TWAP の期間が短すぎる(フラッシュローンで操作可能)
// 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)
バグ D:単一ソースのオラクル
// VULNERABLE: only Uniswap spot price
uint price = getUniswapSpotPrice(token);  // flash loan manipulatable

// CORRECT: Chainlink primary, Uniswap TWAP as fallback, require close agreement
grep パターン
# Missing staleness check
grep -rn "latestRoundData" contracts/ -A5 | grep -v "updatedAt\|timestamp"

# Pyth price usage — confidence interval checked?
grep -rn "getPriceUnsafe\|getPrice\b" contracts/ -A8 | grep -v "conf\|confidence"

# TWAP windows — short TWAP flag
grep -rn "secondsAgo\|TWAP\|cardinality" contracts/ -A5

6. ERC4626 VAULT 攻撃

為替レートの操作(残高がほぼゼロのボールト)
// 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
}
ERC4626 の転送(シェアは移動するが、ステーク/ロックの記録は移動しない)
// 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`
}
Grep パターン
grep -rn "function transfer\|function transferFrom" contracts/ -A15
grep -rn "function deposit\|function mint\|function withdraw\|function redeem" contracts/ -A10

7. 再入攻撃

2016年〜現在。CEI パターンで防止できます。現在も DeFi で発見されています。

種類
  • 単一関数:状態が更新される前に、攻撃者が同じ関数を再度呼び出します
  • 関数間:状態が古いまま、関連する別の関数を再度呼び出します
  • コントラクト間:別のプロトコルへのコールバックを介して再度呼び出します
  • 読み取り専用:攻撃者が利用する古いデータを返す view 関数を再度呼び出します
根本原因のパターン
// 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);
}
Grep パターン
# External calls before state updates
grep -rn "\.call{value\|safeTransfer\|transfer(" contracts/ -B10 | grep -v "require\|revert"

# Missing nonReentrant modifier on critical functions
grep -rn "function withdraw\|function redeem\|function claim" contracts/ -A2 | grep -v "nonReentrant"

# Storage slot for reentrancy guard
grep -rn "nonReentrant\|ReentrancyGuard\|_notEntered" contracts/

8. フラッシュローン攻撃

フラッシュローンを介したオラクル操作
// 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 profits
価格オラクルの妥当性チェック(確認すべき点)
grep -rn "getReserves\|getAmountsOut\|slot0\b" contracts/ -A5
# spot price from reserves = manipulatable with flash loan
# slot0 = Uniswap V3 spot price = manipulatable

9. 署名のリプレイ

ノンスの欠如
// 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);
}
Chain ID の欠如
// 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 chain
Grep パターン
grep -rn "ecrecover\|ECDSA\.recover" contracts/ -B20
# Check: does the signed hash include nonce + chainId + contract address?

grep -rn "nonce\|_nonces\|nonces\[" contracts/

10. プロキシ/アップグレードの問題

ストレージの衝突
// Implementation and proxy share storage layout
// Proxy slot 0: _owner
// Implementation slot 0: _initialized
// → writing to _initialized overwrites _owner
未初期化の実装
// If implementation can be initialized directly → anyone becomes owner of implementation
// Attack: call initialize() on implementation contract → call upgradeTo() → replace logic
ユーザー制御アドレスへの delegatecall
function execute(address target, bytes calldata data) external onlyOwner {
    target.delegatecall(data);  // target is validated, but what if owner is compromised?
}
Grep パターン
# UUPS initialization protection
grep -rn "function initialize\b\|_disableInitializers\|initializer" contracts/

# Delegate call
grep -rn "delegatecall\b" contracts/ -B3 -A5

# Storage layout — proxy uses EIP-1967 slots?
grep -rn "0x360894\|EIP1967\|_IMPLEMENTATION_SLOT" contracts/

FOUNDRY POC テンプレート

// 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");
    }
}
主な Foundry チートコード
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
テストの実行
# Run specific test
forge test --match-test test_exploit -vvvv

# Run with fork
forge test --match-test test_exploit -vvvv --fork-url $MAINNET_RPC

# Gas report
forge test --gas-report

# Coverage
forge coverage --report summary

関連スキルとチェーン

  • meme-coin-audit — 対象が DeFi プロトコルではなく、ミームコイン/SPL トークンの場合に使用します。ワークフローの基本方針:事前調査での中止シグナルは異なります。このスキルの「TVL < $500K ならスキップ」という基準は、ミームコインには適用されません。ミームコインではラグチェック(ミント権限、凍結権限、LP ロック)が監査のすべてとなるため、代わりに meme-coin-audit に振り分けます。
  • triage-validation — コントラクトの指摘事項を Immunefi に提出できる状態になった場合に使用します。ワークフローの基本方針:Immunefi には独自のレポート形式がありますが、triage-validation の影響検証とチェーン全体にわたる検証の規律も引き続き適用されます。提出前に Foundry PoC に対して 7Q ゲートを実施してください。
  • report-writing — Immunefi のレポート本文を作成する場合に使用します。ワークフローの基本方針:Foundry PoC、根本原因のコードスニペット、定量化された経済的影響を含む report-writing の Immunefi テンプレートが、このスキルの指摘事項を反映する本文の骨格となります。
  • offensive-osint — プロトコルのオフチェーン攻撃対象領域(フロントエンド、管理 API、RPC ゲートウェイ)を監査する場合に使用します。ワークフローの基本方針:オンチェーン監査はこのスキルの担当です。プロトコルの Web2 コンポーネント(Web フロントエンド、管理パネル、インデクサー API)は、偵察のため offensive-osint に振り分けます。
  • bb-methodology — そもそも調査に着手するかどうかを判断する場合に使用します。ワークフローの基本方針:bb-methodology の PART 0 で、対象の案件が Web3 バグバウンティ/プライベート監査/スマートコントラクトレビューであることを確認します。このスキルの事前調査での中止シグナルは、その案件種別の標準スコアリング基準に代わるものです。

オペレーター向けメモ(Claude-BugHunter)

ベンダー提供の基盤に対する、案件由来および2026年固有の追加事項です。 実際の承認済み案件から得た知見と、このリポジトリの31以上のスキル領域にわたる ライブテストでのフェーズ2検証に基づいています。上流のコンテンツは「何」を扱い、 このレイヤーは「うまくいく場合と、うまくいかない場合」を扱います。

2026年も報奨金の対象となっているバグの種類

フラッシュローン攻撃は、Immunefiで依然として最も高額な報奨金の対象です(2024~2026年の報奨金額トップ5)。経済的な仕組み、つまり「5,000万ドルを借り入れ、価格オラクルを操作し、プールの資金を抜き取り、返済する」という攻撃が繰り返されるのは、新しいプロトコルが、フラッシュローンによる不均衡のもとでは成り立たないコンポーザビリティの前提を置いたまま、次々と公開されているためです。

リエントランシーは今も報奨金の対象です。新しいプロトコルでERC-777、フック、コールバックが使われ続けているためです。この種のバグはもうなくなったと思い込まないでください。2023~2025年の報奨金対象コーパスには、Checks-Effects-Interactionsパターンに従っているコードベースで発見されたリエントランシーのバグが40件以上含まれています(関数をまたぐリエントランシーや、状態更新の途中で呼び出されるview関数を介した読み取り専用リエントランシーなど)。

オラクル操作:依然として高額な報奨金の対象ですが、難度は上がっています。現在、ほとんどのプロジェクトがChainlink価格フィードを使っています。攻撃対象は、ほとんどのプロジェクトが併用する第2のオラクルです(流動性の低いUniswap V2ペアのTWAP、プロトコル独自の内部オラクル、古い値を使うフォールバック経路など)。プライマリフィードだけでなく、フェイルオーバーの経路を監査してください。

同梱コンテンツの作成後に登場したもの
  • EIP-1153(一時ストレージ) — 2024年に導入されました。新たなリエントランシーの種類が生まれています。同一トランザクション内でキャッシュされた一時ストレージの読み取り値が永続ストレージの値とずれる可能性があります。tload/tstoreの使用箇所について、外部呼び出し後の読み取りを監査してください。
  • EIP-7702(2025年のPectraハードフォーク) — EOAからスマートアカウントへのアップグレードが追加されました。再委任によってATOに似た仕組みが可能になります。一度だけ署名したEOAが攻撃者の制御するコントラクトに委任し、その後、委任先をまたいで署名がリプレイされる可能性があります。
  • アカウント抽象化(ERC-4337バンドラー) — ペイマスターによるスポンサーシップの悪用や、バンドラーへのグリーフィングが問題になります。送信者を厳格な許可リストで制限していないペイマスターコントラクトは、初回の呼び出しで資金を抜き取られる可能性があります。
  • ZKロールアップブリッジのバグ — ロールアップをまたいだ証明のリプレイ、オフチェーンの証明生成者の侵害、シーケンサーによる検閲が強制取り込みのエッジケースにつながる問題などがあります。
  • LST/LRTのデペッグ動態 — 損失が発生した際も1対1のペッグを前提とするリキッドステーキングおよびリキッドリステーキングトークンが問題になります。オラクルはペッグを前提とする一方で、市場ではデペッグが反映され、清算ロジックが破綻します。
2026年のツールスタック

テストフレームワークには、引き続きFoundryが使われています。不変条件テストにはforge test --gas-report --debug、プロパティベーステストにはforge fuzz、ストレージレイアウトの監査にはforge inspectを使います。静的解析とファジングにはSlitherとEchidnaを組み合わせます。小規模なコントラクトの記号実行にはMythrilを使います。メインネットの状態を対象に攻撃を再現する場合は、フォークとシミュレーションにtenderly.coを使うのが最も操作性に優れています。

Solanaでは、anchorフレームワーク、sealevel-attacksコーパス(anchorメンテナーが厳選したPoC)、soteria-sec/sec3スキャナーを使います。Move(Aptos、Sui)では、move-proverとaptos-cliのaptos move testを使います。

クロスチェーンでは、hyperlaneとLayerZeroがそれぞれ監査ツールのリポジトリを公開しています。ブリッジのバグを調べるには、片方のエンドポイントだけでなく、両方をシミュレートする必要があります。

事前調査で見極めるべき打ち切りのサイン

TVLが50万ドル未満なら、報奨金の最低額が高い場合を除き、監査に時間をかける価値はありません。すでに監査会社が監査している場合も、見落とされた点を見つけない限り、投資対効果は低くなります。監査レポートの対象外事項のセクションを確認し、明示的に監査対象から除外されたもの(オラクル、ガバナンス、オフチェーンのコンポーネント、フロントエンド、管理者の経路など)を調べてください。

マルチシグの署名者が5人を超え、タイムロックが48時間を超える場合、ラグプルのリスクは低くなります。発見事項がチームの悪意を前提とするなら影響度は低く、Immunefiの「centralization risk」の除外規定に基づいて、対象外となる可能性も高いです。プログラムの概要を確認してください。ほとんどのImmunefiプログラムでは、管理者の悪意を前提とする発見事項は、明示的に評価を下げるか、対象外としています。

報告に関する規律

ImmunefiではFoundry PoCが必須です。PoCのない提出は自動的に却下されます。手動セットアップ(「まずこれをデプロイしてから、あれを呼び出す」)が必要なPoCを添付した提出は、通常、重大度が引き下げられます。PoCは、影響を証明する単一の forge test 呼び出しである必要があり、流出した残高、ミントされたトークン、破損した状態に対する明示的な assertEq を含めてください。

重大度の主張には、Immunefiの重大度マトリックスをそのまま使用してください。独自の重大度を作らないでください。このマトリックスは、TVLに対する直接的な経済的損失の割合を基準としています。500Kドルのプロトコルに対するクリティカルと、500Mドルのプロトコルに対するクリティカルでは、報奨額が異なります。クリティカルと主張する前に、プログラム固有の重大度割り当てを確認してください。

コメント