smart-contract-vulnerabilities について
SolidityやDeFiシステムを調査する監査担当者向けのスマートコントラクトセキュリティ応用スキルです。リエントランシー、アクセス制御、MEV、署名リプレイ、CREATE2などの攻撃パターンと対策、分析ツールをまとめています。
主な機能
- 01リエントランシーと算術欠陥のパターン
- 02アクセス制御とdelegatecallのリスク
- 03乱数、MEV、フラッシュローン攻撃
- 04署名リプレイとCREATE2の事例
- 05緩和策と監査ツール
活用シーン
- 01コントラクトコードのリエントランシーとアクセス制御の確認
- 02プロキシのストレージ配置とセレクター衝突の評価
- 03トランザクション順序操作と署名リプレイの検証
- 04静的解析、ファジング、形式検証の計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill smart-contract-vulnerabilitiesインストール後、Claude Code で/smart-contract-vulnerabilitiesを入力して読み込まれたか確認します。
同梱ファイルSOLIDITY_VULN_PATTERNS.md
詳細
SKILL:スマートコントラクトの脆弱性 — エキスパート向け攻撃プレイブック
AI LOAD INSTRUCTION:スマートコントラクト監査の高度な手法です。再入(単一関数、関数間、コントラクト間、読み取り専用)、整数オーバーフロー、アクセス制御、delegatecall、乱数操作、フラッシュローン、署名リプレイ、フロントランニング/MEV、CREATE2 の悪用を扱います。ベースモデルでは、微妙なコントラクト間再入や、プロキシパターンにおけるストレージレイアウトの衝突を見落とします。
0. 関連するルーティング
- defi-attack-patterns:脆弱性が DeFi プロトコルのエクスプロイトの一部である場合(フラッシュローン、オラクル操作、ガバナンス攻撃)
- deserialization-insecure:対象がブロックチェーンデータをデシリアライズするオフチェーンインフラストラクチャである場合
高度なリファレンス
次の内容が必要な場合は、SOLIDITY_VULN_PATTERNS.md も読み込んでください。
- 各脆弱性クラスの脆弱なコードパターンと修正済みコードパターンの比較
- 脆弱性を引き起こすガス最適化の落とし穴
- スロット計算を含む、プロキシパターンのストレージ衝突の例
1. 再入
スマートコントラクトで最もよく知られた脆弱性です。外部呼び出しによって実行制御が移り、呼び出し前に状態が更新されていない場合、呼び出し先が再入できます。
1.1 古典的な再入(単一関数)
Victim.withdraw()
├── checks balance[msg.sender] > 0 ✓
├── msg.sender.call{value: balance}("") ← external call
│ └── Attacker.receive()
│ └── Victim.withdraw() ← re-enters before state update
│ ├── checks balance[msg.sender] ← still > 0!
│ └── sends ETH again
└── balance[msg.sender] = 0 ← too late
1.2 関数間再入
2 つの関数が状態を共有している場合、攻撃者はコールバック中に別の関数へ再入します。
| 手順 | 実行 | 状態 |
|---|---|---|
| 1 | withdraw() を呼び出す → 外部呼び出し | 残高はまだ正の値 |
| 2 | 攻撃者のフォールバック関数が transfer(attacker2) を呼び出す | 残高はリセット前に使用される |
| 3 | transfer が古い残高を読み取る → 資金を移動する | attacker2 がトークンを受け取る |
| 4 | 元の withdraw が完了し、残高をゼロにする | 被害が発生 |
1.3 コントラクト間再入
コントラクト A がコントラクト B を呼び出し、B がコントラクト A(または A の古い状態を読み取るコントラクト C)を呼び戻します。複数のコントラクトで状態を共有する DeFi プロトコルでは、特に危険です。
1.4 読み取り専用再入
再入された関数は view 関数であり、第三者コントラクトが価格計算に使用します。被害を受けるコントラクトの状態は変更されませんが、更新前の中間状態が読み取り側を誤解させます。
実例:remove_liquidity() のコールバック中に Curve プールの get_virtual_price() を読み取る → 価格が水増しされる → 依存するレンディングプロトコルで利益を得る。
緩和策
| パターン | 保護レベル |
|---|---|
| Checks-Effects-Interactions(CEI) | 中核となる防御策。外部呼び出しの前に状態を更新する |
ReentrancyGuard(OpenZeppelin) | ミューテックスロック。同一トランザクション内の再入を防ぐ |
| プル決済パターン | 状態変更関数での外部呼び出しをなくす |
| すべての公開関数に CEI とガードを適用 | 関数間再入に対する多層防御 |
2. 整数オーバーフロー/アンダーフロー
Solidity 0.8 より前
算術演算は黙ってラップアラウンドします:uint8(255) + 1 == 0、uint8(0) - 1 == 255。
| 攻撃 | 例 |
|---|---|
| 残高アンダーフロー | 残高より大きい amount で balances[attacker] -= amount を実行 → 巨大な残高になる |
| 供給量オーバーフロー | totalSupply + mintAmount がラップアラウンドする → 上限チェックを回避する |
| タイムロック回避 | lockTime[msg.sender] + extend が過去の時刻にラップアラウンドする → 早期解除 |
Solidity 0.8 以降
デフォルトのチェック付き算術演算では、オーバーフロー時にリバートします。ただし、unchecked{} ブロックを使うとリスクが再発します。
unchecked {
// "gas optimization" — but if i can be influenced by user input, overflow returns
for (uint i = start; i < end; i++) { ... }
}
SafeMath の回避シナリオ
- 型変換:SafeMath によるチェックの前に
uint256からuint128へ変換して切り詰める - アセンブリブロック:
mstore/addによって Solidity レベルのチェックを回避する - 除算前の中間乗算でのオーバーフロー:
a * bがオーバーフローする(a * b) / c
3. アクセス制御
tx.origin と msg.sender
| プロパティ | msg.sender | tx.origin |
|---|---|---|
| 値 | 直接の呼び出し元 | トランザクションを開始した EOA |
| 認証に安全か | はい | いいえ — フィッシングコントラクトが tx.origin を引き継げる |
攻撃:所有者をだまして攻撃者のコントラクトを呼び出させる → 攻撃者のコントラクトが、所有者の tx.origin を使って被害を受けるコントラクトを呼び出す。
よくあるパターン
| 問題 | 影響 |
|---|---|
重要な関数に onlyOwner がない | 誰でも管理者関数を呼び出せます |
selfdestruct が保護されていない | 誰でもコントラクトを破棄し、ETH を強制送金できます |
delegatecall が保護されていない | 攻撃者が被害者のコンテキストで任意のコードを実行します |
| 可視性の指定がない(0.6.0 より前) | 関数の可視性はデフォルトで public になります |
| ゼロアドレスのチェックがない | 所有権が address(0) に移転されます |
4. 乱数の操作
オンチェーンの乱数ソースは、マイナー/バリデーターに予測される可能性があります。
| ソース | 予測可能性 |
|---|---|
block.timestamp | マイナーには操作できる時間が約15秒あります |
blockhash(block.number - 1) | 実行時には誰もが知っています |
blockhash(block.number) | 現在のブロックハッシュは不明なため、常に0を返します |
block.difficulty / block.prevrandao | マージ後は、ビーコンチェーンの値が既知になります |
コミット・リビールの回避:リビールフェーズにタイムアウトや担保の要件がない場合、攻撃者は不利な結果になったときにリビールしないことを選べます(選択的中断攻撃)。
5. DELEGATECALL の脆弱性
delegatecall は、呼び出し先のコードを呼び出し元のストレージコンテキストで実行します。ストレージスロットの配置は完全に一致している必要があります。
ストレージ配置の衝突
Proxy (storage): Implementation (code):
slot 0: owner slot 0: someVariable
slot 1: implementation slot 1: anotherVariable
実装が someVariable(スロット0)に書き込むと、プロキシの owner が上書きされます。攻撃者がスロット0に書き込む実装関数を呼び出すと、プロキシの所有者になります。
関数セレクターの衝突
4バイトの関数セレクターは衝突することがあります。プロキシの admin() のセレクターが実装の transfer() と衝突すると、プロキシで admin() を呼び出した際に transfer() のロジックが実行されます。
ツール:cast selectors <bytecode>(Foundry)を使うと、セレクターを列挙できます。
6. フロントランニング/MEV
トランザクション順序の操作
Victim submits DEX swap tx (visible in mempool)
├── Front-runner: buy token before victim (raise price)
├── Victim tx executes at worse price
└── Back-runner: sell token after victim (profit from spread)
= Sandwich attack
保護パターン
| 防御策 | 仕組み |
|---|---|
| コミット・リビール | リビールまでトランザクションの意図を隠します |
| Flashbots/プライベートメンプール | ブロックビルダーに tx を直接送信します |
| スリッページ保護 | minAmountOut を設定し、MEV による抽出を制限します |
| タイムロック | 実行を遅らせ、予測可能性を低減します |
7. 署名のリプレイ
ノンスの欠落
有効な署名を再利用して、同じ操作(例:送金)を複数回実行します。
クロスチェーンリプレイ
同じアドレスで同じコントラクトが複数のチェーンにデプロイされていると、署名はすべてのチェーンで有効になります。署名メッセージに block.chainid を含める必要があります。
EIP-712 の実装エラー
| エラー | 結果 |
|---|---|
chainId を含む DOMAIN_SEPARATOR がない | クロスチェーンリプレイ |
| デプロイ時にドメインセパレーターをキャッシュする | chainId が変わるハードフォーク後に機能しなくなります |
| 構造体ハッシュにノンスがない | 署名のリプレイ |
無効な署名に対して ecrecover が address(0) を返す | == address(0) の所有者チェックを通過します |
8. SELF-DESTRUCT と ETH の強制送金
selfdestruct(recipient) は、すべてのコントラクト ETH を recipient に強制送金します。receive() と fallback() を回避し、拒否できません。
ロジックで address(this).balance に依存するコントラクト(例:require(balance == expected))が破綻します。
EIP-6780(Dencun)以降、selfdestruct は ETH を送金するだけです。コード/ストレージが削除されるのは、作成と同じ tx 内で呼び出された場合に限られます。
9. CREATE2 と決定論的アドレスの悪用
CREATE2 のアドレス = keccak256(0xff ++ deployer ++ salt ++ keccak256(initCode))。
| 攻撃 | 手法 |
|---|---|
| 事前入金の悪用 | アドレスを予測 → デプロイ前にトークン/ETH を送信 → selfdestruct → 同じアドレスに異なるコードを再デプロイ |
| 事前承認の悪用 | 予測したアドレスにトークンの承認を設定 → 悪意のあるコントラクトをデプロイ → 承認済みトークンを流出させる |
| メタモルフィックコントラクト | CREATE2 → selfdestruct → 同じ salt で異なる initCode を使って CREATE2(EIP-6780 より前) |
10. フラッシュローン攻撃パターン
Single transaction:
├── Borrow large amount (no collateral)
├── Manipulate state (price oracle, governance, etc.)
├── Extract profit from manipulated state
├── Repay loan + fee
└── Keep profit
要点:シーケンス全体がアトミックに成功する必要があります。そうでなければ、tx 全体が取り消されます。
11. ショートアドレス攻撃
EVM は ABI エンコードされた calldata の不足バイトをゼロで埋めます。19 バイトのアドレスで transfer(address, uint256) を呼び出すと、uint256 の amount が 8 ビット左にずれ、256 倍になります。
緩和策:calldata の長さを検証します。最新の Solidity コンパイラーではチェックが追加されています。
12. ツール
| ツール | 目的 | 使用方法 |
|---|---|---|
| Slither | 静的解析、脆弱性の検出 | プロジェクトのルートで slither . |
| Mythril | シンボリック実行、パス探索 | myth analyze contract.sol |
| Echidna | プロパティベースのファジング | 不変条件を定義し、違反がないかファジング |
| Foundry (Forge) | テストフレームワーク、ファジング、ガス分析 | forge test --fuzz-runs 10000 |
| Hardhat | 開発、テスト、デプロイ | npx hardhat test |
| Certora | 形式検証 | 仕様を記述し、プロパティを証明または反証 |
| 4naly3er | ガス最適化と脆弱性レポートの自動生成 | CI との統合 |
13. 決定木
Auditing a smart contract?
├── Is it a proxy pattern?
│ ├── Yes → Check storage layout collision (Section 5)
│ │ ├── Compare slot assignments between proxy and implementation
│ │ ├── Check for function selector collision
│ │ └── Verify initializer cannot be called twice
│ └── No → Continue
├── Does it make external calls?
│ ├── Yes → Check reentrancy (Section 1)
│ │ ├── State updated before call? → CEI pattern OK
│ │ ├── ReentrancyGuard present? → Check all entry points
│ │ ├── Cross-function state sharing? → Cross-function reentrancy risk
│ │ └── View functions read during callback? → Read-only reentrancy
│ └── No → Continue
├── Does it handle tokens/ETH?
│ ├── Yes → Check integer overflow (Section 2)
│ │ ├── Solidity < 0.8? → All arithmetic suspect
│ │ ├── unchecked{} blocks? → Verify no user-influenced values
│ │ └── Casting between uint sizes? → Truncation risk
│ └── Also check self-destruct force-send (Section 8)
├── Does it use signatures?
│ ├── Yes → Check replay (Section 7)
│ │ ├── Nonce included? → Verify incremented
│ │ ├── ChainId included? → Cross-chain safe
│ │ └── ecrecover result checked for address(0)? → OK
│ └── No → Continue
├── Does it use on-chain randomness?
│ ├── Yes → Predictable (Section 4)
│ │ └── Recommend Chainlink VRF or commit-reveal with bond
│ └── No → Continue
├── Does it interact with DeFi protocols?
│ ├── Yes → Load [defi-attack-patterns](../defi-attack-patterns/SKILL.md)
│ │ ├── Flash loan vectors
│ │ ├── Oracle manipulation
│ │ └── MEV exposure
│ └── No → Continue
├── Does it use CREATE2?
│ ├── Yes → Check deterministic address exploitation (Section 9)
│ └── No → Continue
└── Run automated tools (Section 12)
├── Slither for static analysis
├── Mythril for symbolic execution
└── Echidna for fuzzing invariants