A user can increase their chance of winning a rap battle and, thus, the bet staked with it, by using someone else's more skilled rapper, due to a lack of relevant checks on the ownership of the oneShot NFT.
When a user wants to take part in a rap battle, they can do so via the entrypoint RapBattle.sol::goOnStageOrBattle(uint256 _tokenId, uint256 _credBet)
.
Here is the code of this function:
If we take a closer look at this function, there are two scenarios:
a user has already entered the stage, and awaits a challenger
no one is on the stage, so anyone calling the function will wait for a challenger
In the first case, the else branch of the control statement is executed, calling the internal _battle
function.
The _battle
function retrieves the information about the challenging rapper using uint256 challengerRapperSkill = getRapperSkill(_tokenId);
, and, at no point in the execution flow, checks that msg.sender
owns the NFT represented by this _tokenId
.
With this in mind, it is possible to use any skilled rapper that is already approved to challenge other players. Using more skilled rappers will increase the attacker's chance of winning the battle and, thus, winning the bet.
This approach is shown with the following test case, which can be added to the OneShotTest.t.sol
:
A user can significantly increase their chance of winning a rap battle without having to stake their oneShot NFT in order to increase its stats. Any user can simply exploit this vulnerability and use a pre-approved more skilled rapper NFT to battle, and have a better chance of winning the bet.
Manual review, VSCode, Foundry
The smart contract should check that both the challenged rapper (check already in place) and challenging rapper (exploited here) are inserted into a rap battle by their respective owners.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.