Biconomy - Smart Contract Wallet contest - Koolex's results

One-Stop solution to enable an effortless experience in your dApp to onboard new users and abstract away transaction complexities.

General Information

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

Biconomy

Findings Distribution

Researcher Performance

Rank: 20/105

Findings: 6

Award: $621.96

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Findings Information

🌟 Selected for report: V_B

Also found by: 0xdeadbeef0x, HE1M, Koolex, Matin, adriro, chaduke, gogo, hihen, jonatascm, kankodu, ro, smit_rajput, spacelord47, taek

Awards

125.5131 USDC - $125.51

Labels

bug
3 (High Risk)
satisfactory
sponsor confirmed
duplicate-496

External Links

Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L18-L551

Vulnerability details

Impact

Functions from the logic contract (SmartAccount) shouldn't be called directly. a bad actor can take the ownership by calling init directly then destroy it making all proxies (Smart Contract Wallets) pointing to a non-existing contract.

One could argue that Biconomy's admin can call init after deploying it. However, there is still a risk of front-runing it.

Proof of Concept

  1. SmartAccount is left uninitialized. there is no code that's initializing it nor disabling initializing it.

  2. The attacker initializes it to gets the ownership.

  3. Since SmartAccount can delegate calls, the attacker can delegate a call to a malcious contract calling selfdestruct which results in destroying SmartAccount.

Tools Used

Manual analysis

As per openzeppelin's doc:

To prevent the implementation contract from being used, you should invoke the _disableInitializers function in the constructor to automatically lock it when it is deployed

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
    _disableInitializers();
}

https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializing_the_implementation_contract

#0 - c4-judge

2023-01-17T14:03:26Z

gzeon-c4 marked the issue as duplicate of #496

#1 - c4-sponsor

2023-01-25T23:37:40Z

livingrockrises marked the issue as sponsor confirmed

#2 - c4-judge

2023-02-10T12:42:47Z

gzeon-c4 marked the issue as satisfactory

Awards

22.7235 USDC - $22.72

Labels

bug
3 (High Risk)
satisfactory
sponsor confirmed
duplicate-175

External Links

Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/interfaces/ISignatureValidator.sol#L4-L20 https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L342

Vulnerability details

Impact

  1. The EIP1271_MAGIC_VALUE is incorrect. it has 0x20c13b0b which is different than what EIP-2171 specified 0x1626ba7e. It results in wrong contract signature always when validating.
  2. isValidSignature should have hashData (bytes32) parameter and not data (bytes). Therefore, the hashData should be passed instead of data (in checkSignatures function) . This also results in wrong contract signature always since the signer contract expects dataHash and not the data its self.

Proof of Concept

Check:

require(ISignatureValidator(_signer).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, "BSA024");
  • Line 19 in ISignatureValidator
    function isValidSignature(bytes memory _data, bytes memory _signature) public view virtual returns (bytes4);

Tools Used

Manual analysis

  1. Change the implementation of ISignatureValidator to adhere to EIP-1271 specs.
  2. In SmartAccount's checkSignatures function, pass the dataHash instead of data.

#0 - c4-judge

2023-01-17T06:56:28Z

gzeon-c4 marked the issue as duplicate of #175

#1 - livingrockrises

2023-01-26T00:15:42Z

elaborate on this please - Change the implementation of ISignatureValidator to adhere to EIP-1271 specs

#2 - c4-sponsor

2023-02-09T11:46:09Z

livingrockrises marked the issue as sponsor confirmed

#3 - c4-judge

2023-02-10T12:28:26Z

gzeon-c4 marked the issue as satisfactory

Findings Information

🌟 Selected for report: Tointer

Also found by: 0xdeadbeef0x, HE1M, Haipls, Koolex, PwnedNoMore, Tricko, V_B, csanuragjain, orion, peakbolt, ro, romand, taek

Labels

bug
3 (High Risk)
satisfactory
sponsor confirmed
duplicate-36

Awards

149.4204 USDC - $149.42

External Links

Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L192-L219

Vulnerability details

Impact

Any transaction that was already executed through execTransaction function can be executed again (many times not only once).

Proof of Concept

Explanation

checkSignatures function takes into account nonces to prevent a signature from being valid again. The nonces increases by one everytime a transaction gets executed which protects from replaying. However, Biconomy uses a 2D nonces to support batches, and the caller has to pass the batchId when calling execTransaction. A malicious actor can use a batchId that was never used (within the uint256 range which is big) and replay all transactions in the same order in a batchId that was used by a user.

Coded PoC

  1. Go to line 491 in the file './test/smart-wallet/onboardings-test.ts' and duplicate the last transaction with a different batchId (e.g. 77) as folllows:
const txs: MetaTransaction[] = [
      buildContractCall(
        walletFactory,
        "deployCounterFactualWallet",
        [owner, entryPoint.address, handler.address, 1],
        0
      ),
      buildContractCall(
        userSCW,
        "execTransaction",
        [transaction, 1, refundInfo, signature],
        0
      ),
      buildContractCall(
        userSCW,
        "execTransaction",
        [transaction, 77, refundInfo, signature],
        0
      ),
    ];
  1. At the end of the test case, change the expectation from 10 ether to 20 ether as follows:
expect(await token.balanceOf(charlie)).to.equal(
      ethers.utils.parseEther("20")
 );
  1. Run the test file and it will pass.
npx hardhat test ./test/smart-wallet/onboardings-test.ts

Tools Used

Manual analysis

Include the batchId as a part of the signed hashed data.

#0 - c4-judge

2023-01-17T07:09:46Z

gzeon-c4 marked the issue as duplicate of #485

#1 - c4-sponsor

2023-01-26T00:02:51Z

livingrockrises marked the issue as sponsor confirmed

#2 - c4-judge

2023-02-10T12:34:43Z

gzeon-c4 marked the issue as satisfactory

Findings Information

🌟 Selected for report: Franfran

Also found by: Koolex, MalfurionWhitehat, gogo, immeas, zaskoh

Labels

bug
2 (Med Risk)
disagree with severity
downgraded by judge
satisfactory
sponsor confirmed
duplicate-498

Awards

242.9785 USDC - $242.98

External Links

Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L506-L513 https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/BaseSmartAccount.sol#L63

Vulnerability details

Impact

According to the Biconomy's docs (check contest overview),

Biconomy Smart Account is a smart contract wallet that builds on core concepts of Gnosis / Argent safes and implements an interface to support calls from account abstraction Entry Point contract

As per Biconomy, it doesn't support signature aggregation at the moment, and according to Account Abstraction specs EIP-4337

If the account does not support signature aggregation, it MUST validate the signature is a valid signature of the userOpHash, and SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error should revert

However, the SmartAccount reverts rather than returns SIG_VALIDATION_FAILED. This migh lead to incompatibility since any EntryPoint that would be used in future will expect a returned value in case of invalid signature instead of a revert.

Proof of Concept

validateUserOp function (in BaseSmartAccount) calls _validateSignature to validate the userOp (UserOperation). However, _validateSignature reverts if the signature is invalid rather than returning SIG_VALIDATION_FAILED.

Check SmartAccount contract for

    function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash, address)
    internal override virtual returns (uint256 deadline) {
        bytes32 hash = userOpHash.toEthSignedMessageHash();
        //ignore signature mismatch of from==ZERO_ADDRESS (for eth_callUserOp validation purposes)
        // solhint-disable-next-line avoid-tx-origin
        require(owner == hash.recover(userOp.signature) || tx.origin == address(0), "account: wrong signature");
        return 0;
    }

Tools Used

Manual analysis

If the signature is invalid return SIG_VALIDATION_FAILED. for any other error, revert.

Note: adjust the EntryPoint contract according to the suggested change above.

#0 - c4-judge

2023-01-17T14:04:01Z

gzeon-c4 marked the issue as primary issue

#1 - gzeoneth

2023-01-17T14:04:20Z

not high risk

#2 - livingrockrises

2023-01-26T07:16:27Z

you're right. it has to be rebased with the latest code (0.4.0).

#3 - c4-sponsor

2023-01-26T07:16:38Z

livingrockrises marked the issue as sponsor confirmed

#4 - c4-sponsor

2023-01-26T07:16:43Z

livingrockrises marked the issue as disagree with severity

#5 - c4-judge

2023-02-10T11:57:06Z

gzeon-c4 changed the severity to 2 (Med Risk)

#6 - c4-judge

2023-02-10T12:17:03Z

gzeon-c4 marked the issue as selected for report

#7 - vladbochok

2023-02-11T09:53:30Z

Hey @gzeon-c4 @livingrockrises!

The #498 describes that validateUserOp() SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch.

This is currently not the case, as the case when the account does not support signature aggregation is not supported right now in the code. The validateUserOp() reverts everytime if the recovered signature does not match.

At the same time, the nature of these reports is very similar, so it does not give impact examples, but claim to be incompatible with the standard. All in all, I think this report and its duplicates should be evaluated as duplicates of #498, and not a separate issue.

#8 - livingrockrises

2023-02-11T10:11:36Z

yes it merely talks about contracts should be upgraded as entry point upgrades. when we scoped for C4 ERC4337 was at v0.3.0 now they are at 0.4.0 plus new commits that will become 0.5.0 with these interface updates. we have been already rebasing our wallet source with latest versions. just that it couldn't be sent for c4 audit scope end of year

#9 - vladbochok

2023-02-14T08:05:45Z

@gzeoneth Could you please take a look at my message above?

#10 - gzeon-c4

2023-02-14T08:08:44Z

Hey @gzeon-c4 @livingrockrises!

The #498 describes that validateUserOp() SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch.

This is currently not the case, as the case when the account does not support signature aggregation is not supported right now in the code. The validateUserOp() reverts everytime if the recovered signature does not match.

At the same time, the nature of these reports is very similar, so it does not give impact examples, but claim to be incompatible with the standard. All in all, I think this report and its duplicates should be evaluated as duplicates of #498, and not a separate issue.

make sense

#11 - c4-judge

2023-02-14T08:09:41Z

gzeon-c4 marked the issue as duplicate of #498

#12 - c4-judge

2023-02-14T08:09:51Z

gzeon-c4 marked the issue as not selected for report

#13 - c4-judge

2023-02-14T08:10:09Z

gzeon-c4 marked the issue as satisfactory

Findings Information

Labels

bug
2 (Med Risk)
satisfactory
sponsor confirmed
duplicate-261

Awards

44.8261 USDC - $44.83

External Links

Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L120-L125

Vulnerability details

Impact

If the implementation contract (SmartAccount) was upgraded to a contract which doesn't have upgrading mechanism (e.g. updateImplementation), then the proxy (i.e. Smart Contract Wallet) is locked forever with the new implementation.

Proof of Concept

Check updateImplementation in SmartAccount.

Tools Used

Manual analysis

Use a security mechanism similiar to what's used in UUPS (openzeppelin).

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/ERC1967/ERC1967Upgrade.sol#L84-L88

#0 - c4-judge

2023-01-17T07:55:20Z

gzeon-c4 marked the issue as duplicate of #352

#1 - c4-sponsor

2023-02-08T08:19:53Z

livingrockrises marked the issue as sponsor confirmed

#2 - c4-judge

2023-02-10T12:36:41Z

gzeon-c4 marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax Β© 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter