Platform: Code4rena
Start Date: 09/02/2024
Pot Size: $60,500 USDC
Total HM: 17
Participants: 283
Period: 12 days
Judge:
Id: 328
League: ETH
Rank: 144/283
Findings: 3
Award: $14.67
π Selected for report: 0
π Solo Findings: 0
π Selected for report: CodeWasp
Also found by: 0x13, 0xAlix2, 0xAsen, 0xCiphky, 0xE1, 0xLogos, 0xaghas, 0xlemon, 0xlyov, 0xvj, ADM, Aamir, BARW, Bauchibred, DMoore, DanielArmstrong, Draiakoo, Fulum, GhK3Ndf, Josh4324, Kalogerone, KmanOfficial, Krace, KupiaSec, Limbooo, MidgarAudits, MrPotatoMagic, PedroZurdo, Pelz, Tendency, _eperezok, adam-idarrha, al88nsk, alexxander, alexzoid, aslanbek, blutorque, btk, c0pp3rscr3w3r, cartlex_, cats, d3e4, deadrxsezzz, denzi_, devblixt, dimulski, erosjohn, evmboi32, fnanni, grearlake, hulkvision, immeas, israeladelaja, jaydhales, jesjupyter, jnforja, juancito, klau5, korok, ktg, ladboy233, matejdb, merlinboii, novamanbg, nuthan2x, oualidpro, peanuts, petro_1912, pkqs90, pynschon, radev_sw, rouhsamad, sashik_eth, shaka, sobieski, soliditywala, stackachu, tallo, thank_you, ubl4nk, vnavascues, web3pwn, xchen1130, zhaojohnson
0.1044 USDC - $0.10
Players can get unfair advantage by owning more than 10 fighters in the game, hence stake, sell, trade more and benefit from the distribution of features.
The problem was caused, because the developers forgot to override safeTransferFrom method from the ERC721.
They have a function called _ableToTransfer, which checks whether a player is able to transfer its fighter. It reverts when the balance for the recipient is 10 or more, however this function is not used when safeTransferFrom with data is used.
In the ERC721 code implementation there are 2 safeTransferFrom:
safeTransferFrom(address from, address to, uint256 tokenId) and safeTransferFrom(address from, address to, uint256 tokenId, bytes _data)
The developers have confirmed that they override the first one, but forgot to override the second one, hence users can use it to avoid the _ableToTransfer method
This error was confirmed as high by the project developers, see my gist: https://gist.github.com/Ivan-Dosev/f62488e745ba72cfb2ad2c6e041fa569
try it in RankedBattleTest
function testSafeTransferFromWithVulnerability() public { address player1 = vm.addr(11); address player2 = vm.addr(12); for (uint8 i = 0; i < 10; i++) { _mintFromMergingPool(player1); } assertEq(_fighterFarmContract.balanceOf(player1), 10); assertEq(_fighterFarmContract.balanceOf(player2), 0); for (uint8 i = 0; i < 10; i++) { _mintFromMergingPool(player2); } assertEq(_fighterFarmContract.balanceOf(player2), 10); vm.startPrank(player1); vm.expectRevert(); _fighterFarmContract.safeTransferFrom(player1, player2, 0); // this reverts because it is an override function _fighterFarmContract.safeTransferFrom(player1, player2, 0, ""); // however this was missed by the developers therefore it doesn't check the _ableToTransfer function hence allow players to own more than 10 players in game assertEq(_fighterFarmContract.balanceOf(player2), 11); console.log("Player1 balance after transfer", _fighterFarmContract.balanceOf(player1)); _fighterFarmContract.safeTransferFrom(player1, player2, 1, ""); assertEq(_fighterFarmContract.balanceOf(player2), 12); console.log("Player2 balance after transfer", _fighterFarmContract.balanceOf(player2)); }
Foundry test
Simply, override safeTransferFrom(address from, address to, uint256 tokenId, bytes _data) like the other.
ERC721
#0 - c4-pre-sort
2024-02-23T04:39:31Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-23T04:39:41Z
raymondfam marked the issue as duplicate of #54
#2 - c4-pre-sort
2024-02-23T04:47:22Z
raymondfam marked the issue as duplicate of #739
#3 - c4-pre-sort
2024-02-23T04:54:02Z
raymondfam marked the issue as sufficient quality report
#4 - c4-judge
2024-03-11T02:40:54Z
HickupHH3 marked the issue as satisfactory
π Selected for report: Aamir
Also found by: 0rpse, 0x11singh99, 0x13, 0xAlix2, 0xAsen, 0xBinChook, 0xCiphky, 0xE1, 0xKowalski, 0xLogos, 0xWallSecurity, 0xaghas, 0xbranded, 0xlemon, 0xlyov, 0xpoor4ever, 0xprinc, 0xvj, ADM, Aymen0909, BARW, Bauchibred, Breeje, CodeWasp, DMoore, DeFiHackLabs, Draiakoo, Fulum, GhK3Ndf, Greed, Jorgect, Josh4324, Kalogerone, KmanOfficial, Krace, Limbooo, McToady, MidgarAudits, MrPotatoMagic, PedroZurdo, Pelz, Ryonen, SovaSlava, SpicyMeatball, Tendency, Timenov, ZanyBonzy, _eperezok, al88nsk, alexxander, alexzoid, aslanbek, blutorque, btk, cartlex_, cats, csanuragjain, deadrxsezzz, denzi_, devblixt, dimulski, djxploit, erosjohn, evmboi32, fnanni, grearlake, haxatron, hulkvision, immeas, israeladelaja, jaydhales, jesjupyter, jnforja, josephdara, juancito, kiqo, klau5, korok, krikolkk, ktg, kutugu, ladboy233, lil_eth, m4ttm, matejdb, merlinboii, n0kto, ni8mare, novamanbg, nuthan2x, oualidpro, pa6kuda, peter, petro_1912, pkqs90, pynschon, sandy, sashik_eth, shaflow2, shaka, sobieski, soliditywala, solmaxis69, stackachu, tallo, thank_you, tpiliposian, ubl4nk, visualbits, vnavascues, web3pwn, xchen1130, zhaojohnson
0.0037 USDC - $0.00
Some of the game items should not be transferrable that is why the developers have implemented a check to make sure that if the item's transferrable property is marked as false it shouldn't be able to be transferred. However the ERC1155 token code implements two functions for transferring items:
safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data) and safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data)
The SmartContract developers override safeTransferFrom, but forgot to override the safeBatchTransferFrom, which leads to vulnerability, where players can use it to transfer game items, which shouldn't be allowed to.
According to the developers, this is a High severity, please see: https://gist.github.com/Ivan-Dosev/4a3b788272352efac0ea5d06ba498356
There will be soulbound tokens and batteries which shouldn't be transferred however this vulnerability make that possible activity.
copy this into GameItemsTest
function testTransferingFalseItems() public { address player1 = vm.addr(15); address player2 = vm.addr(16); _fundUserWith4kNeuronByTreasury(player1); vm.prank(player1); _gameItemsContract.mint(0, 2); // player buys 2 batteries _gameItemsContract.adjustTransferability(0, false); (,, bool transferable,,,) = _gameItemsContract.allGameItemAttributes(0); assertEq(transferable, false); vm.prank(player1); vm.expectRevert(); _gameItemsContract.safeTransferFrom(player1, player2, 0, 1, ""); // this fails because the item is not transferable assertEq(_gameItemsContract.balanceOf(player1, 0), 2); uint256[] memory ids = new uint256[](2); uint256[] memory amounts = new uint256[](2); ids[0] = 0; amounts[0] = 1; vm.prank(player1); _gameItemsContract.safeBatchTransferFrom(player1, player2, ids, amounts, ""); // this succeeds because the requirement statements is missing assertEq(_gameItemsContract.balanceOf(player2, 0), 1); }
Foundry test
Override and add requirement
Token-Transfer
#0 - c4-pre-sort
2024-02-22T03:50:34Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T03:50:43Z
raymondfam marked the issue as duplicate of #18
#2 - c4-pre-sort
2024-02-26T00:27:55Z
raymondfam marked the issue as duplicate of #575
#3 - c4-judge
2024-03-05T04:53:17Z
HickupHH3 marked the issue as satisfactory
π Selected for report: nuthan2x
Also found by: 0xE1, 0xblackskull, 0xgrbr, 0xvj, Greed, McToady, MidgarAudits, PetarTolev, Sabit, SovaSlava, SpicyMeatball, Timenov, Tychai0s, _eperezok, alexxander, btk, c0pp3rscr3w3r, favelanky, jesjupyter, josephdara, juancito, klau5, kutugu, lil_eth, merlinboii, pynschon, sandy, shaflow2, zaevlad
14.5737 USDC - $14.57
Looking in Neuron contract we can see that 3 roles as Spender, Minter and Staker were distributed, however those roles can't be revoked. Consider a use case where those roles are malicious accounts and their access need to be restricted and new roles assigned. That is not possible, hence they will always posses their roles. The only chance to be revoked is to be revoked by themselves, but if they are malicious that is not going to happen.
I consider this as Medium, because at the moment those roles are assigned to smart contracts that are deployed by the developers of the protocol. However in future updates that can be ignored, hence causing some major issues in the control of the protocol.
Just look at the implementation of AccessControl.sol
Manual check
Consider implementing a revocation of roles.
Access Control
#0 - c4-pre-sort
2024-02-22T05:06:08Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T05:06:14Z
raymondfam marked the issue as duplicate of #20
#2 - c4-judge
2024-03-05T07:33:42Z
HickupHH3 marked the issue as satisfactory
#3 - HickupHH3
2024-03-05T07:38:16Z
mentions that roles can't be revoked, but doesn't explain why (lacking details)
#4 - c4-judge
2024-03-05T07:38:29Z
HickupHH3 marked the issue as partial-50
#5 - Ivan-Dosev
2024-03-19T20:51:44Z
Thank you for the feedback @HickupHH3 . I didn't explain why the roles can't be revoked, because I have considered this as part of the issue. I also do not understand why the developers have decided to avoid creating a function which revokes roles. In the current implementation only the user itself can decide to remove his role, but as explained if he is malicious this is not going to happen. That is why I considered it as vulnerability, but can't say why exactly it is left like this. Most probably the developers haven't consider this case and have really forgotten to implement a revoking function from admin.