著者: Jiujiu & Lisa
編集者: シェリー
背景
2025年3月30日、SlowMist MistEyeセキュリティ監視システムの監視によると、イーサリアムチェーン上のレバレッジ取引プロジェクトSIR.trading(@leveragesir)が攻撃を受け、30万ドル以上の資産が失われました。 SlowMist セキュリティ チームはインシデントを分析し、次のような結果を共有しました。
(https://x.com/SlowMist_Team/status/1906245980770746449)
関連情報
攻撃者のアドレス:
https://etherscan.io/address/0x27defcfa6498f957918f407ed8a58eba2884768c
脆弱な契約のアドレス:
https://etherscan.io/address/0xb91ae2c8365fd45030aba84a4666c4db074e53e7#コード
攻撃トランザクション:
https://etherscan.io/tx/0xa05f047ddfdad9126624c4496b5d4a59f961ee7c091e7b4e38cee86f1335736f
前提条件
Solidity バージョン 0.8.24 (2024 年 1 月リリース) では、EIP-1153 に基づく一時ストレージ機能が導入されました。これは、開発者に低コストでトランザクション効率の高い方法でデータを一時的に保存できるように設計された新しいデータ ストレージの場所です。
一時ストレージは、ストレージ、メモリ、コールデータと並ぶ新しいデータの場所です。その主な特徴は、データが現在のトランザクションの実行中のみ有効であり、トランザクションの終了後に自動的にクリアされることです。一時ストレージへのアクセスと変更は、次の 2 つの新しい EVM 命令によって実行されます。
- TSTORE(キー、値): 256 ビットの値 value を、一時ストレージ内の指定されたキー key に対応するメモリに格納します。
- TLOAD(key): 一時ストレージ内の指定されたキー key に対応するメモリから 256 ビットの値を読み取ります。
この機能の主な特徴は次のとおりです。
- 低いガスコスト: TSTORE と TLOAD のガスコストは固定で 100 であり、ウォーム ストレージ アクセスと同等です。比較すると、通常のストレージ操作 (SSTORE) では、最初の書き込み (0 から 0 以外) で最大 20,000 ガス、更新で少なくとも 5,000 ガスのコストがかかります。
- トランザクション内の永続性: 一時的に保存されたデータは、すべての関数呼び出しとサブ呼び出しを含むトランザクション全体で有効なままです。これは、呼び出し間で一時的な状態を共有する必要があるシナリオに適しています。
- 自動クリア: トランザクションが完了すると、一時ストレージは自動的にゼロにリセットされるため、手動でクリーンアップする必要がなくなり、開発者のメンテナンス コストが削減されます。
根本的な原因
このハッキングの根本的な原因は、関数呼び出しの終了後に tstore 内の一時ストレージの値がクリアされないことです。これにより、攻撃者はこの機能を利用して特定の悪意のあるアドレスを作成し、権限チェックを回避してトークンを転送できるようになります。
攻撃手順
1. 攻撃者はまず 2 つの悪意のあるトークン A と B を作成し、次に UniswapV3 上にこれら 2 つのトークンのプールを作成し、流動性を注入します。ここで、トークン A は攻撃契約です。
2. 次に、攻撃者は Vault コントラクトの初期化関数を呼び出して、A トークンを担保トークン、B トークンを負債トークンとして、レバレッジ取引市場 APE-21 を作成します。
3. その後すぐに、攻撃者は Vault コントラクトの mint 関数を呼び出し、負債トークン B を預けてレバレッジ トークン APE を作成します。
ミント関数を追っていくと、負債トークン B をミントレバレッジトークンに預ける必要がある場合、渡す必要のある collateralToDepositMin パラメータの値は 0 に等しくできないことがわかりました。その後、B トークンは UniswapV3 を介して担保トークン A と交換され、Vault に転送されます。ここで、攻撃者が以前に作成した UniswapV3 プールのアドレスが初めて一時的に保存されます。
UniswapV3 プールがスワップ操作を実行すると、Vault コントラクトの uniswapV3SwapCallback 関数がコールバックされます。この関数は、まず tload を使用して、以前に一時的に保存された指定のキー 1 に対応するメモリから値を取得し、呼び出し元が UniswapV3 プールであるかどうかを確認します。次に、負債トークン B をミンターのアドレスから転送して、レバレッジ トークン APE をミントし、最後に、ミントされた金額の 2 回目の一時保存を実行し、指定のキー 1 に対応するメモリに保存して、ミント関数の戻り値として使用します。ここで鋳造する必要がある量は攻撃者によって事前に計算され制御されており、その値は 95759995883742311247042417521410689 です。
4. 次に、攻撃者は Keyless CREATE2 Factory コントラクトの safeCreate2 関数を呼び出して、コントラクト アドレス 0x00000000001271551295307acc16ba1e7e0d4281 (2 番目の一時ストレージの値と同じ) を持つ悪意のあるコントラクトを作成します。
5. 次に、攻撃者は悪意のあるコントラクトを使用して、Vault コントラクトの uniswapV3SwapCallback 関数を直接呼び出し、トークンを転送します。
uniswapV3SwapCallback関数はtload(1)を使用して、呼び出し元がUniswapV3プールであるかどうかを確認するためです。ただし、前回の鋳造操作では、指定されたキー 1 に対応するメモリ内の値が鋳造量 95759995883742311247042417521410689 として保存されており、mint 関数が呼び出された後もメモリ内の値はクリアされません。そのため、この時点では uniswapPool のアドレスは 0x00000000001271551295307acc16ba1e7e0d4281 として取得され、呼び出し元の ID チェックが誤って通過することになります。
攻撃者は、転送する必要のあるトークンの数も事前に計算し、最終的な鋳造量を指定された値 1337821702718000008706643092967756684847623606640 として構築しました。同様に、今回は uniswapV3SwapCallback 関数の呼び出しの最後に、指定されたキー 1 に対応するメモリに値を保存するために 3 回目の一時保存が実行されます。この値は、呼び出し元に対する後続のチェックに合格するために、攻撃コントラクト (A トークン) のアドレスの値 0xea55fffae1937e47eba2d854ab7bd29a9cc29170 と同じである必要があります。
6. 最後に、攻撃者は攻撃コントラクト(トークン A)を介して Vault コントラクトの uniswapV3SwapCallback 関数を直接呼び出し、Vault コントラクト内の他のトークン(WBTC、WETH)を転送して利益を得ることができます。
ミストトラック分析
オンチェーンのマネーロンダリング防止および追跡ツールMistTrackの分析によると、攻撃者(0x27defcfa6498f957918f407ed8a58eba2884768c)は、17,814.8626 USDC、1.4085 WBTC、119.871 WETHを含む約30万ドル相当の資産を盗みました。
WBTC は 63.5596 WETH に交換され、USDC は 9.7122 WETH に交換されました。
その後、合計 193.1428 WETH が Railgun に転送されました。
さらに、攻撃者の初期資金は、Railgun から転送された 0.3 ETH から提供されました。
要約する
この攻撃の核心は、プロジェクト内の一時ストレージでは、関数が呼び出された直後に保存された値がクリアされず、トランザクション期間中は保存されるという事実を攻撃者が悪用し、コールバック関数の権限検証を回避して利益を得ることです。 SlowMist セキュリティ チームは、プロジェクト所有者が対応するビジネス ロジックに従って関数呼び出しが終了した直後に tstore(key, 0) を使用して一時ストレージ内の値をクリアすることを推奨しています。さらに、同様の状況を回避するために、プロジェクトの契約コードを監査し、セキュリティ テストをより厳密に実行する必要があります。