You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on Jan 12, 2025. It is now read-only.
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelMediumA Medium severity issue.RewardA payout will be made for this issue
A users lock time can be extended indefinitely by an attacker
Summary
Anyone can add to any position, and by doing so, extend the lock time indefinitely, making the original owner unable to withdraw except in an emergency due to a misuse of _isAuthorized.
function _requireOnlyOperatorOrOwnerOf(uint256tokenId) internalview {
// isApprovedOrOwner: caller has no rights on token require(ERC721Upgradeable._isAuthorized(msg.sender, msg.sender, tokenId), "FORBIDDEN");
}
We can see that the msg.sender is passed as both the spender and owner to this function, and if we look at _isAuthorized,we see the following:
/** * @dev Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in * particular (ignoring whether it is owned by `owner`). * * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this * assumption. */function _isAuthorized(addressowner, addressspender, uint256tokenId) internalviewvirtualreturns (bool) {
return spender !=address(0) && (owner == spender ||isApprovedForAll(owner, spender) ||_getApproved(tokenId) == spender);
}
As is clear from the warning, the function does not check whether the owner passed to it is the real owner, and if the owner is equal to the spender, the check succeeds immediately. Since _requireOnlyOperatorOrOwnerOf passed msg.sender as both owner and spender any address will be able to pass this check and deposit to any token ID, even if the ID has not been minted yet.
The lock duration is updated upon each successful addToPosition call by the following formula:
This makes it possible for an attacker to keep depositing to a certain position and extending the locktime.
Impact
Because of the above issue, anyone can increase the user to an indefinite amount of time ranging from hours to years and by doing so exposing the user to price fluctuations he did not plan for.
The amout of time the position will be extended by depends on
the amount the attacker added
the initial locktime the user sat
the remaining time
The following graph shows that if a user has $X$ locked tokens and $I$ intial lock time, by adding $0.19672X$ to his position, you will extend it by $0.16438356164$ of $I$ (without accounting for small precesion loss from the solidity fixed point numbers model).
In the graph below, we assume the remaining time to be zero, but as the remaining time increases, the duration added by locking decreases, but this decrease will be trivial if the remaining time is short, like a day or less and this will be demonstrated in the poc.
But the attacker can always wait when it's near for the lock to expire and add more lock time without exposing himself to the effect of the remaining time.
If we assume the user deposited 1000 tokens and locked them for a year and 364 days passed, then the attacker can, by depositing 19.672% of the user's locked amount, lock the user's funds for 60 additional days (minus a small amount due to precision loss and the effect of remaining time)
If the attacker after nearly 60 days wants to lock the position for another 60 days, he will need to pay 19.672% of $( X + 0.19672X )$ and so on.
The following is poc that demostrates the previous attack senario
Add the following to MlumStaking.t.sol
function testAttackPoc() public {
vm.warp(0);
address attacker =makeAddr("attacker");
_rewardToken.mint(address(_pool), 100_000_000);
_stakingToken.mint(ALICE, 1000 ether);
_stakingToken.mint(attacker, 196.72 ether);
vm.startPrank(ALICE);
_stakingToken.approve(address(_pool), 1000 ether);
_pool.createPosition(1000 ether, 365 days);
vm.stopPrank();
skip(364 days);
uint Alice_LockTime_Before =1days;// 364 of the 365 days passed the remaining lock time is 1 day
vm.startPrank(attacker);
_stakingToken.approve(address(_pool), 196.72 ether);
_pool.addToPosition(1, 196.72 ether);
vm.stopPrank();
uint Alice_LockTime_After = _pool.getStakingPosition(1).lockDuration;
assertEq(Alice_LockTime_After, Alice_LockTime_Before +60days-14232); // 14232 seconds/4 hours less than 60 days because of remainig time caused deviation
}
Instead of using _isAuthorized you can do the same thing it does safely by just checking whether the caller is the owner of the NFT or whether he is approved of the NFT.
sherlock-admin4
changed the title
Quaint Alabaster Alligator - A users lock time can be extended indefinitely by an attacker
neogranicen - A users lock time can be extended indefinitely by an attacker
Jul 29, 2024
Sign up for freeto subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelMediumA Medium severity issue.RewardA payout will be made for this issue
neogranicen
Medium
A users lock time can be extended indefinitely by an attacker
Summary
Anyone can add to any position, and by doing so, extend the lock time indefinitely, making the original owner unable to withdraw except in an emergency due to a misuse of
_isAuthorized
.Vulnerability Detail
Its supposed that
addToPosition
can only be called by lsNFT's owner or operators and to do this check_requireOnlyOperatorOrOwnerOf
is used which uses_isAuthorized
as is shown belowWe can see that the msg.sender is passed as both the spender and owner to this function, and if we look at
_isAuthorized
,we see the following:As is clear from the warning, the function does not check whether the owner passed to it is the real owner, and if the owner is equal to the spender, the check succeeds immediately. Since
_requireOnlyOperatorOrOwnerOf
passed msg.sender as both owner and spender any address will be able to pass this check and deposit to any token ID, even if the ID has not been minted yet.The lock duration is updated upon each successful
addToPosition
call by the following formula:This makes it possible for an attacker to keep depositing to a certain position and extending the locktime.
Impact
Because of the above issue, anyone can increase the user to an indefinite amount of time ranging from hours to years and by doing so exposing the user to price fluctuations he did not plan for.
The amout of time the position will be extended by depends on
The following graph shows that if a user has$X$ locked tokens and $I$ intial lock time, by adding $0.19672X$ to his position, you will extend it by $0.16438356164$ of $I$ (without accounting for small precesion loss from the solidity fixed point numbers model).
In the graph below, we assume the remaining time to be zero, but as the remaining time increases, the duration added by locking decreases, but this decrease will be trivial if the remaining time is short, like a day or less and this will be demonstrated in the poc.
But the attacker can always wait when it's near for the lock to expire and add more lock time without exposing himself to the effect of the remaining time.
If we assume the user deposited 1000 tokens and locked them for a year and 364 days passed, then the attacker can, by depositing 19.672% of the user's locked amount, lock the user's funds for 60 additional days (minus a small amount due to precision loss and the effect of remaining time)
If the attacker after nearly 60 days wants to lock the position for another 60 days, he will need to pay 19.672% of$( X + 0.19672X )$ and so on.
The following is poc that demostrates the previous attack senario
Add the following to
MlumStaking.t.sol
Code Snippet
https://github.com/sherlock-audit/2024-06-magicsea/blob/42e799446595c542eff9519353d3becc50cdba63/magicsea-staking/src/MlumStaking.sol#L142
Tool used
Desmos
Manual Review
Foundry
Recommendation
Instead of using
_isAuthorized
you can do the same thing it does safely by just checking whether the caller is the owner of the NFT or whether he is approved of the NFT.Duplicate of #378
The text was updated successfully, but these errors were encountered: