Rhythmic Azure Manatee
Medium
The claimReward function in the TokenRewards contract allows any external account to invoke it on behalf of any wallet
The claimReward function in the TokenRewards contract allows any external account to invoke it on behalf of any wallet
The claimReward function in the TokenRewards contract allows any external account to invoke it on behalf of any wallet. This lack of access control enables unauthorized entities to trigger reward claims for arbitrary wallet addresses. While the rewards are correctly sent to the intended recipient, this functionality can be misused in ways that harm the protocol and its users.
-
Denial-of-Service (DoS) Attack:
- An attacker can spam calls to claimReward for multiple addresses, potentially overloading the network or consuming excessive gas for contract operations.
-
Increased User Costs:
- Users may incur unnecessary gas fees if attackers claim rewards on their behalf, particularly in high-frequency scenarios.
-
Unintended Side Effects:
- If claiming rewards triggers additional downstream logic, attackers could exploit this behaviour to manipulate protocol outcomes or interfere with integrations.
- Copy and paste the following function into the foundry test file called contracts/test/TokenRewards.t.sol
function testClaimRewardByAnyone() public {
rewardsWhitelister.setWhitelist(address(rewardsToken), true);
// Add shares for two users
vm.startPrank(address(trackingToken));
tokenRewards.setShares(user1, 60e18, false); // 60%
tokenRewards.setShares(user2, 40e18, false); // 40%
vm.stopPrank();
// Deposit rewards
uint256 depositAmount = 100e18;
tokenRewards.depositRewards(address(rewardsToken), depositAmount);
address anyOne = address(0xfeefdeef);
vm.startPrank(anyOne);
// Claim rewards for user 1 and user 2
tokenRewards.claimReward(user1);
tokenRewards.claimReward(user2);
vm.stopPrank();
}
- Then run forge test in CMD in the contracts folder.
# forge test --match-test testClaimRewardByAnyone -vvv
Ran 1 test for test/TokenRewards.t.sol:TokenRewardsTest
[PASS] testClaimRewardByAnyone() (gas: 494229)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.50ms (299.17µs CPU time)
Ran 1 test suite in 140.68ms (1.50ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
- The foundry test is passed and I have claimed rewards for a couple of users, but it has gone to their wallets.
Implement access control to restrict claimReward function calls to the wallet owner or an authorised entity.
Mitigated Implementation:
function claimReward(address _wallet) external override {
+ require(_wallet == _msgSender(), "UNAUTHORIZED: Only the wallet owner can claim rewards");
_processFeesIfApplicable();
_distributeReward(_wallet);
emit ClaimReward(_wallet);
}