-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A Malicious Prize Winner Can Steal the ClaimReward From the Claimer Using the BeforeClaimPrize Hook #263
Comments
Claimable::claimPrize has the visibility of onlyClaimer denying the winner's hook reentrancy. |
raymondfam marked the issue as insufficient quality report |
raymondfam marked the issue as duplicate of #18 |
hansfriese changed the severity to QA (Quality Assurance) |
hansfriese marked the issue as grade-c |
Hi @hansfriese, I think there was a misunderstanding here and I'd really value a second look from you. In my submission, I've demonstrated how a malicious winner could exploit the beforeClaimPrize hook to get the claim reward for himself, basically stealing it from the rightful claimer. @raymondfam in his analysis mentioned the It has also be shown by another researcher (@Al-Qa-qa) here that the gas limit implemented as a defensive mechanism wouldn't prevent such exploit. And He also provided a runnable PoC for that : #345 Regarding the impact, I still think it should be categorized a Medium as there is a clear theft of value from a malicious user to a honest user + endangering/getting around the incentive mechanism as this could easily be implemented by many users, making the claiming mechanism less financially interesting. Finally, if this finding gets re-evaluated to satisfactory, I would like to point that the submissions I will list afterward either do not show the impact, or show something different and shouldn't be grouped under #345 :
And thanks a lot for your time! |
This previously downgraded issue has been upgraded by hansfriese |
hansfriese removed the grade |
hansfriese marked the issue as not a duplicate |
Thank you for your detailed comment. |
hansfriese marked the issue as satisfactory |
hansfriese marked the issue as duplicate of #345 |
hansfriese marked the issue as partial-75 |
Hi @hansfriese,thank you very much for taking the time to re-evaluate my submission. Also, as you confirmed, I clearly shown the vulnerability and exploit path, and the Regards |
It won't affect the reward calculation. |
Lines of code
https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/abstract/Claimable.sol#L76-L119
Vulnerability details
Impact
Claimer will lose part of their profit.
If too many winners set such a hook, this can disencentivize claimers to do their job.
Vulnerability details
Pool Together prize distribution is based on incentivization of 3rd parties to activate its functionality, rather than depending on a governance or centralized actor. For example in this case, everytime a draw to award prizes is closed, any 3rd party can call the
claimable::claimPrize
function to claim the winner's prize on his behalf. By doing so, the prize will be distributed to the winner, minus a small fee rewarded to the claimer.As said above, to claim prizes 3rd party must call the
Claimable::claimPrize
:https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/abstract/Claimable.sol#L76-L119
And as we can see here, before the prize is claimed (L99), a hook is called (L86).
We can observe that this hook receives the exact same parameters as the
claimPrize
call:_winner
,_tier
,_prizeIndex
,reward
andrewardRecipient
.The vulnerability lies in the fact that nothing prevent a malicious winner to configure a hook that will claim the prize before the original claimer.
By developping a hook that reuses these parameters to call
claimable::claimPrize
, but replacing the_rewardRecipient
address with one of its own, the winner can then receive its legitimate prize + the claimer reward.And as a bonus, we can also note that the winner will not even have to pay the gas fee for the call, as it uses the claimer call to execute this action.
Proof of Concept
By claiming right before the original claimer thanks to the hook, the winner sets the
_claimedPrizes[msg.sender][_winner][lastAwardedDrawId_][_tier][_prizeIndex])
entry totrue
(L516), after that the award will be credited to him (L525)Then, when the claimer will enter
PrizePool::claimPrize
himself after the hook has been executed, the entry will already be set totrue
, making if statement at L512 pass and the call return early.https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4380f1ab65e39109ac6a36f8935137a2ef685860/src/PrizePool.sol#L512-L516
Tools Used
Manual review
Recommended Mitigation Steps
Hopefully, the solution is pretty easy to set-up: just add a
nonReentrant
modifier (can use the ReentrancyGuard lib from OZ) to theClaimable::claimPrize
This way, the hook will not be allowed to reenter to function.
Assessed type
Reentrancy
The text was updated successfully, but these errors were encountered: