Summary
Any user can pass in an address and an amount of their choice which will then mint the specified amount of WStETH/StETHMock to the specified address.
https://github.com/Cyfrin/2024-01-Morpheus/blob/main/contracts/mock/tokens/StETHMock.sol#L19-L27
https://github.com/Cyfrin/2024-01-Morpheus/blob/main/contracts/mock/tokens/WStETHMock.sol#L15-L17
Vulnerability Details
The issue comes from the following blocks of code in two different contracts
WStETHMock.sol
function mint(address account_, uint256 amount_) external {
_mint(account_, amount_);
}
StETHMock.sol
function mint(address _account, uint256 _amount) external {
require(_amount <= 1000 * (10 ** decimals()), "StETHMock: amount is too big");
uint256 sharesAmount = getSharesByPooledEth(_amount);
_mintShares(_account, sharesAmount);
totalPooledEther += _amount;
}
Due to missing onlyOwner modifier, anyone can call the mint
function and mint any amount of WStETH/StETHMock to any address.
Impact
Users can mint themselves unlimited tokens to the passed in address parameter.
Proof of Concept:
Consider setting up a test. User can pass in their own or any address with any amount and they will be minted that amount of tokens.
function testAnyoneCanMint() public {
vm.prank(user);
stETHMock.mint(user, 1000e18);
vm.stopPrank();
assertEq(stETHMock.balanceOf(address(user)), 1000e18);
}
function testWStETHMockMint() public {
vm.prank(user);
wstETHMock.mint(user, 10e18);
vm.stopPrank();
console.log("Balance of user: ", wstETHMock.balanceOf(user));
}
Tools Used
Manual Review, Unit Tests
Recommendations
Consider adding an onlyOwner access modifier so only the owner can mint the tokens for the account provided.
WStETHMock.sol
-function mint(address account_, uint256 amount_) external {
+function mint(address account_, uint256 amount_) external onlyOwner {
_mint(account_, amount_);
}
StETHMock.sol
-function mint(address _account, uint256 _amount) external {
+function mint(address _account, uint256 _amount) external onlyOwner {
require(_amount <= 1000 * (10 ** decimals()), "StETHMock: amount is too big");
uint256 sharesAmount = getSharesByPooledEth(_amount);
_mintShares(_account, sharesAmount);
totalPooledEther += _amount;
}