{"content":{"title":"账户抽象安全指南 - 漏洞都可能发生在哪？","body":"![img](https://img.learnblockchain.cn/attachments/migrate/1700919732947)\r\n\r\n## 摘要\r\n\r\n[在上一篇文章中](https://medium.com/chainlight/patch-thursday-all-about-account-abstraction-part-1-11fadf3cb8fe)，我们讨论了账户抽象的组件和操作结构以及其相关的安全考虑。在本文中，我们将分析和分类在使用账户抽象实现的项目的安全审计中发现的漏洞。基于这些发现，我们将介绍基于账户抽象的项目应该优先考虑的注意事项。\r\n\r\n## AA 项目的安全考虑\r\n\r\nChainLight 已经分析了针对构建在或计划实现 ERC4337 的项目的安全审计报告，并根据这些报告编制了基于这些报告的账户抽象项目的基本注意事项清单。在本文中，我们参考了 OpenZeppelin 对 ERC4337 参考实现的审计报告，以及其他项目的审计报告，如 Biconomy、Zerodev Kernel 和 Ambire 等，这些项目经历了详细的安全审计。\r\n\r\n### 1. Gas 费用计算逻辑\r\n\r\n基于 ERC4337 的项目可以提供的关键功能之一是通过捆绑器（Bundler）支付用户的Gas费用，并让 Paymaster 在交易成功时偿还捆绑器消耗的费用。因此，使恶意捆绑器操纵Gas费用奖励或无辜的捆绑器无法收到适当的支付的漏洞将导致参与实体的财务损失。\r\n\r\n**Gas费用补偿的不正确计算**\r\n\r\n由于Gas费用操纵漏洞可能导致资金盗窃，处理Gas费用补偿逻辑时应谨慎对待。在 Biconomy 中，账户为代表用户提交交易的中继器（类似于 ERC4337 的捆绑器）提供补偿，该补偿设计为随着 calldata 的大小而增加。然而，一种漏洞允许攻击者或恶意的中继器通过向 calldata 添加零字节来任意增加补偿。\r\n\r\n在此漏洞中，当中继器提交交易时，账户每`msg.data.length`（完整 calldata）支付 8 个Gas。然而，EVM 对于 calldata 中的零字节只消耗 4 个Gas，而对于非零字节则消耗 16 个Gas。因此，每当中继器向 calldata 添加零字节时，它就会获得额外的 4 个Gas（8 个Gas作为补偿-实际消耗成本的 4 个Gas）。这使得攻击者通过向 calldata 添加大量零字节来获得额外的补偿，耗尽账户的资产。这是在 Infinitism 的 ERC4337 标准实现发布之前实现的。在参考实现中，捆绑器的补偿由 UserOperation 的`maxFeePerGas`、`maxPriorityFeePerGas`和`preVerificationGas`参数确定，因此现在这种操纵是不可行的。然而，在设置这些参数过高时要谨慎，因为这可能导致用户账户向捆绑器支付不必要的高Gas补偿。\r\n\r\n**捆绑器的Gas估算错误**\r\n\r\n如果捆绑器在模拟期间执行的Gas估算低于链上执行所需的实际Gas成本，那么当使用估算的Gas量执行时，交易将因 Gas 不足而失败。在 Infinitism 的先前 ERC4337 实现中存在一个漏洞，即在模拟阶段的Gas估算低于链上执行的实际Gas成本，这是由于模拟阶段的冷存储访问导致的。在此漏洞中，捆绑器使用值 1 作为地址聚合器的参数调用`_validateAccountPrepayment()`。在此过程中，捆绑器从发送方账户的存储中检索发送方账户的聚合器地址。由于在模拟阶段会先访问（冷访问）发送方账户的存储，因此在调用`validateUserOp()`验证发送方账户的 UserOperation 时，会产生较少的Gas成本（[EIP-2929](https://eips.ethereum.org/EIPS/eip-2929)）。因此，模拟环境和链上执行环境之间的Gas使用量不同，交易可能因链上执行中的Gas不足错误而失败。为了防止这种情况发生，捆绑器必须确保模拟环境和链上执行之间的Gas测量没有差异，特别是在直接设计 EntryPoint 时。\r\n\r\n### 2. 签名生成和使用\r\n\r\n正确生成和验证签名确实至关重要。如果签名验证未正确执行，攻击者可能会冒充账户并执行任意交易，这可能导致严重问题，如用户资金被盗。\r\n\r\n**生成的签名验证不足**\r\n\r\n在 Biconomy 的`SmartAccount`合约中存在一个漏洞，攻击者可以通过绕过签名验证创建任意交易。`SmartAccount`合约通过`execTransaction()`函数支持基于 EIP1271 的签名交易，但缺乏验证过程来检查`checkSignatures()`函数中的输入签名是否属于合约所有者。\r\n\r\n```solidity\r\n// https://github.com/code-423n4/2023-01-biconomy/blob/8a7b05ac58a65727e7e1fb17b91e418bc372be2b/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L302-L353\r\nfunction checkSignatures (…) {\r\n  …\r\n  _signer = address(uint160(uint256(r)));\r\n  require(ISignatureValidator(_signer).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, \"BSA024\");\r\n  …\r\n}\r\n```\r\n\r\n由于签署者未验证签署者是否为账户所有者，攻击者可以为签署者指定攻击合约地址，并使`isValidSignature()`始终返回`EIP1271_MAGIC_VALUE`。因此，攻击者可以创建任意交易，并由账户执行。这可能导致攻击，如从账户中窃取资金、通过`selfdestruct()`销毁代理合约以及通过对实现合约的更新夺取钱包的控制权。攻击者可以部署新的实现以窃取账户中的所有资金，甚至从协议中取回资金。由于 ERC4337 基于账户的签名验证错误可能导致资金被盗，因此需要特别注意确保签名验证没有错误。\r\n\r\nInfinitism 的实现由于签名验证机制存在缺陷而出现问题。`BLSSignatureAggregator`合约提供了一种机制，使捆绑器在组合用户操作之前验证每个 UserOperation 的签名。然后，捆绑器在链下生成成功的 UserOperation 的组合签名，EntryPoint 可以在链上环境中一次性验证所有签名。然而，存在一个漏洞，使得单个签名的验证通过，但组合签名的验证失败。\r\n\r\n以下代码中的`validateUserOpSignature()`调用`getUserOpPublicKey()`以获取 UserOperation 的`publicKey`。`GetUserOpPublicKey()`根据`initCode`的存在性检索`publicKey`。如果两个`publicKey`不相同，即使单个 UserOp 的验证成功，组合 UserOp 的验证也可能失败。在这种情况下，捆绑器无法收回提交的交易，因为交易会因验证失败而回滚。此外，由于假定聚合器已经回滚了捆绑器的成功交易，这可能损害聚合器的声誉，并导致拒绝使用特定聚合器的其他 UserOp。\r\n\r\n```solidity\r\n// https://github.com/eth-infinitism/account-abstraction/blob/6dea6d8752f64914dd95d932f673ba0f9ff8e144/contracts/bls/BLSSignatureAggregator.sol#L123-L131\r\nfunction validateUserOpSignature(UserOperation calldata userOp)\r\n… (bytes memory sigForUserOp) {\r\n  uint256[2] memory signature = abi.decode(userOp.signature, (uint256[2]));\r\n  uint256[4] memory pubkey = getUserOpPublicKey(userOp);\r\n  uint256[2] memory message = userOpToMessage(userOp);\r\n  require(BLSOpen.verifySingle(signature, pubkey, message), \"BLS: wrong sig\");\r\n  return \"\";\r\n}\r\n\r\n// https://github.com/eth-infinitism/account-abstraction/blob/6dea6d8752f64914dd95d932f673ba0f9ff8e144/contracts/bls/BLSSignatureAggregator.sol#L20-L27\r\nfunction getUserOpPublicKey(UserOperation memory userOp) … (uint256[4] memory publicKey) {\r\n  bytes memory initCode = userOp.initCode;\r\n  if (initCode.length > 0) {\r\n    publicKey = getTrailingPublicKey(initCode);\r\n  } else {\r\n    return IBLSAccount(userOp.sender).getBlsPublicKey();\r\n  }\r\n}\r\n```\r\n\r\n**使用未签名变量**\r\n\r\nBiconomy 的`SmartAccount`合约具有一个`handlePayment()`函数，使账户向提交交易的中继器支付Gas费用。此函数使用与Gas相关的变量，如`baseGas`、`gasPrice`和`gasToken`，这些变量与账户支付的Gas量密切相关。然而，存在一个漏洞，允许中继器任意设置其中一个参数`tokenGasPriceFactor`。账户必须向捆绑器退款的最终支付由`tokenGasPriceFactor`确定，如`handlePayment()`代码所示。攻击者可以操纵`tokenGasPriceFactor`的值，向账户收取超过实际Gas成本的费用，可能导致资金被盗。\r\n\r\n```solidity\r\n// https://github.com/code-423n4/2023-01-biconomy/blob/8a7b05ac58a65727e7e1fb17b91e418bc372be2b/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L247-L269\r\nfunction handlePayment(\r\n  uint256 gasUsed,\r\n  uint256 baseGas,\r\n  uint256 gasPrice,\r\n  uint256 tokenGasPriceFactor,\r\n  address gasToken,\r\n  address payable refundReceiver\r\n) private nonReentrant returns (uint256 payment) {\r\n  // uint256 startGas = gasleft();\r\n  // solhint-disable-next-line avoid-tx-origin\r\n  address payable receiver = refundReceiver == address(0) ? payable(tx.origin) : refundReceiver;\r\n  if (gasToken == address(0)) {\r\n    // For ETH we will only adjust the gas price to not be higher than the actual used gas price\r\n    payment = (gasUsed + baseGas) * (gasPrice < tx.gasprice ? gasPrice : tx.gasprice);\r\n    (bool success,) = receiver.call{value: payment}(\"\");\r\n    require(success, \"BSA011\");\r\n  } else {\r\n    payment = (gasUsed + baseGas) * (gasPrice) / (tokenGasPriceFactor);\r\n    require(transferToken(gasToken, receiver, payment), \"BSA012\");\r\n  }\r\n  // uint256 requiredGas = startGas - gasleft();\r\n  // console.log(\"hp %s\", requiredGas);\r\n}\r\n```\r\n\r\n### 3. 签名的重用\r\n\r\n允许在短时间内重复发送相同的 UserOperation 的签名可能导致Paymaster的资金枯竭，并显著降低参与组件的可用性。因此，基于 ERC4337 的项目必须仔细实现对 UserOperations 和交易签名重用的验证。\r\n\r\n**通过重用 UserOperation 签名设置任意验证器**\r\n\r\n在 Zerodev Kernel 的`KillSwitchValidator`合约中，`validateUserOp()`函数验证 UserOperation 的有效性，并在签名长度为 71 时指定验证器。以下代码显示了存储 UserOperation 验证器信息的`validatorStorage`的数据：\r\n\r\n```solidity\r\n// https://github.com/zerodevapp/kernel/blob/199ae7d838f21b37f069267c86e8eeb6f0175a69/src/validator/KillSwitchValidator.sol#L43-L80\r\nfunction validateUserOp(UserOperation calldata _userOp, bytes32 _userOpHash, uint256) external payable override returns (uint256) {\r\n  KillSwitchValidatorStorage storage validatorStorage = killSwitchValidatorStorage[_userOp.sender];\r\n  uint48 pausedUntil = validatorStorage.pausedUntil;\r\n  uint256 validationResult = 0;\r\n  if (address(validatorStorage.validator) != address(0)) {\r\n    // check for validator at first\r\n    try validatorStorage.validator.validateUserOp(_userOp, _userOpHash, pausedUntil) returns (uint256 res) { // ECDSAValidator\r\n    validationResult = res;\r\n    } catch {\r\n      validationResult = SIG_VALIDATION_FAILED;\r\n    }\r\n    ValidationData memory validationData = _parseValidationData(validationResult);\r\n    if (validationData.aggregator != address(1)) {\r\n      // if signature verification has not been failed, return with the result\r\n      uint256 delayedData = _packValidationData(false, 0, pausedUntil);\r\n      return _packValidationData(_intersectTimeRange(validationResult, delayedData));\r\n    }\r\n  }\r\n  if (_userOp.signature.length == 71) {\r\n    // save data to this storage\r\n    validatorStorage.pausedUntil = uint48(bytes6(_userOp.signature[0:6]));\r\n    validatorStorage.validator = KernelStorage(msg.sender).getDefaultValidator();\r\n    validatorStorage.disableMode = KernelStorage(msg.sender).getDisabledMode();\r\n    bytes32 hash = ECDSA.toEthSignedMessageHash(keccak256(bytes.concat(_userOp.signature[0:6], _userOpHash)));\r\n    address recovered = ECDSA.recover(hash, _userOp.signature[6:]);\r\n    if (validatorStorage.guardian != recovered) {\r\n      return SIG_VALIDATION_FAILED;\r\n    }\r\n    return _packValidationData(false, 0, pausedUntil);\r\n  } else {\r\n    return SIG_VALIDATION_FAILED;\r\n  }\r\n}\r\n```\r\n\r\n漏洞出现在`validatorStorage`的数据可以被`msg.sender`更新。换句话说，即使`userOp.sender`和`msg.sender`不同，`msg.sender`（攻击者）也可以将`killSwitchValidatorStorage[_userOp.sender].validator`指定为其选择的任何地址。\r\n\r\n假设攻击者使用先前使用过的 UserOperation 调用`validateUserOp()`。当验证器未指定时，第一个条件`if (address(validatorStorage.validator) != address(0))`将原样通过。即使验证器已经存在，它也不会触发已使用的 UserOperation 的回滚，但会将`SIG_VALIDATION_FAILED`分配给`validationResult`。这里的重要一点是，在第二个`if`条件中，攻击者可以在检查 UserOperation 的签名长度后，将攻击者指定的地址设置为`validatorStorage.validator`。由于重用了先前的 UserOperation，攻击者可以通过攻击者设置的新验证器任意通过任何 UserOperation 的验证。因此，这可能导致窃取钱包的所有权或资金。\r\n\r\n**Paymaster签名的重用**\r\n\r\n在 Biconomy 的`Upgrader`合约中发现了一个漏洞，攻击者可以使用`delegatecall`将账户的实现升级为恶意实现。攻击者可以将账户升级为执行与原始`SmartAccount`合约相同功能的合约，而无需进行 nonce 验证。通过这种方式，攻击者可以使用先前在账户上使用的相同Paymaster签名重复发送相同的交易，耗尽Paymaster的资金。作为对策，Biconomy 在 EntryPoint 中添加了对重放攻击的检查，并通过添加`NonceManager`来处理先前由账户自身处理的 nonce 验证的[修改了合约](https://github.com/eth-infinitism/account-abstraction/blob/d2b2762b46dae299ee9155cff740b538e8e8e1f3/contracts/core/NonceManager.sol#L33) 。\r\n\r\n使用账户抽象的项目应确保在信任某些 EntryPoint 时实现适当的 nonce 验证。此外，建议最小化Paymaster对账户的信任依赖。\r\n\r\n在 Infinitism 的 ERC4337 标准实现中，存在一种允许Paymaster签名重用的漏洞。`VerifyingPaymaster`合约用于验证Paymaster的 UserOperation，计算了 UserOperation 的哈希，并对其进行了签名验证。然而，签名数据未包括特定Paymaster的地址或链 ID，这使得可以在不同链或具有相同`verifyingSigner`的不同Paymaster上重用此签名。\r\n\r\n```\r\n// https://github.com/eth-infinitism/account-abstraction/blob/6dea6d8752f64914dd95d932f673ba0f9ff8e144/contracts/samples/VerifyingPaymaster.sol#L36-L50\r\nfunction getHash(UserOperation calldata userOp) public pure returns (bytes32) {\r\n  return keccak256(abi.encode(\r\n    userOp.getSender(),\r\n    userOp.nonce,\r\n    keccak256(userOp.initCode),\r\n    keccak256(userOp.callData),\r\n    userOp.callGasLimit,\r\n    userOp.verificationGasLimit,\r\n    userOp.preVerificationGas,\r\n    userOp.maxFeePerGas,\r\n    userOp.maxPriorityFeePerGas\r\n  ));\r\n}\r\n\r\n// https://github.com/eth-infinitism/account-abstraction/blob/6dea6d8752f64914dd95d932f673ba0f9ff8e144/contracts/samples/VerifyingPaymaster.sol#L56-L75\r\nfunction validatePaymasterUserOp(…) … {\r\n  bytes32 hash = getHash(userOp);\r\n  …\r\n  if (verifyingSigner != hash.toEthSignedMessageHash().recover(paymasterAndData[20 :]) {\r\n    return (\"\",1);\r\n  }\r\n}\r\n```\r\n\r\nInfinitism 通过修改`getHash()`函数来解决了这个问题，以包括`block.chainid`、Paymaster合约的地址和签名的过期时间。\r\n\r\n**重用已签名交易**\r\n\r\nBiconomy 的`SmartAccount`合约中的`execTransaction()`函数负责在发送时计算已签名交易的哈希，包括`nonces[batchId]`。但它不验证`batchId`本身，允许设置任意`batchId`。利用这一点，攻击者可以为共享相同 nonce 值（`nonces[batchId]`）的不同`batchId`多次重复使用相同的交易。\r\n\r\n```\r\n// https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L192-L245\r\nfunction execTransaction(\r\n  Transaction memory _tx,\r\n  uint256 batchId,\r\n  bytes memory signatures\r\n) … {\r\n  …\r\n  bytes32 txHash;\r\n  {\r\n    bytes memory txHashData =\r\n    encodeTransactionData(\r\n      _tx,\r\n      refundInfo,\r\n      nonces[batchId]\r\n    );\r\n    nonces[batchId]++;\r\n    txHash = keccak256(txHashData);\r\n    checkSignatures(txHash, txHashData, signatures);\r\n  }\r\n  …\r\n}\r\n```\r\n\r\n因此，对于特定 nonce 值，可能存在²²⁵⁶个可能的`batchId`值，允许相同的交易被实际上执行无限次。在这种情况下，重要的是防止在交易哈希计算过程中生成具有相同`batchId`的重复交易。\r\n\r\n### 4. 抢跑交易\r\n\r\n在 ERC4337 标准中，`handleOps()`函数处理捆绑在一起的数组中的多个 UserOperations。在此过程中，如果一个 UserOperation 失败，整个交易将被回滚。假设一个捆绑器提交了一个交易，而攻击者利用抢跑交易先执行 UserOperation 数组中的最后一个 UserOperation。受攻击的捆绑器验证了直到最后一个 UserOperation，为它们消耗了Gas，然后在最后一个 UserOperation 失败，导致整组 UserOperations 被回滚。因此，受害者花费了大部分的Gas费用，但由于交易失败而无法收到支付。\r\n\r\n为了防止这种情况发生，捆绑器应采取措施防范抢跑交易。因此，捆绑器应该是可以将其 UserOperation 捆绑作为区块中的第一笔交易的区块矿工，或者利用 MEV-boost 等基础设施。此外，捆绑器应该在每个区块中检查其 UserOperations 的有效性。只有序列化程序可以创建区块的 Layer 2 区块链应支持捆绑器的 RPC 端点，如在[先前](https://blog.theori.io/이더리움-계정-추상화의-모든-것-part-1-eb114bd1f6f7)文章中提到的。 \r\n在 Ambire 中，发现了一个问题，即 Relayer 可以通过抢包运行强制撤销通过特定函数执行的交易。`AmbireAccount`合约包含一个名为`tryCatch()`的函数，该函数通过低级调用调用 UserOperation。如下所示，`tryCatch()`函数的实现方式是，即使调用失败，也不会撤销。因此，即使 UserOperation 未能执行，Relayer 也可能因交易提交而获得奖励。\r\n\r\n```\r\n// https://github.com/AmbireTech/wallet/blob/1ef5b7208e906be4287673746f15418984e78bc0/contracts/AmbireAccount.sol#L81-L87\r\nfunction tryCatch(address to, uint256 value, bytes calldata data) external payable {\r\n  require(msg.sender == address(this), 'ONLY_IDENTITY_CAN_CALL');\r\n  (bool success, bytes memory returnData) = to.call{ value: value, gas: gasleft() }(data);\r\n  if (!success) emit LogErr(to, value, data, returnData);\r\n}\r\n```\r\n\r\n问题在于 Relayer 可以故意使 UserOperation 失败，并通过操纵交易的 gas 限制来获得奖励。以太坊通过 [EIP150](https://eips.ethereum.org/EIPS/eip-150) 硬分叉引入了对 gas 费用政策的更改，引入了“1/64 规则”。这意味着 gas 的 1/64 被保留给调用上下文本身，剩余的 gas（`gasLeft — gasLeft * 1/64`）传递给被调用的函数。换句话说，即使由于内部操作而被调用的函数（子级）耗尽 gas，调用者（父级）仍有一些 gas 可以完成其执行，因此不会撤销交易。\r\n\r\n恶意的 Relayer 或可以抢跑运行交易的实体可以利用这种行为来破坏账户的运行。当用户账户打算通过`tryCatch()`函数提交交易时，调用将按以下顺序进行：\r\n\r\n1. 调用用户账户的`execute()`函数。\r\n2. 调用`tryCatch()`函数。\r\n3. 调用目标合约中的函数。\r\n\r\n攻击者可以在提交调用目标合约函数的交易时精确设置 gas 限制，使得由于 gas 耗尽而使调用失败，同时允许`tryCatch()`后的操作成功。换句话说，交易提交了，但账户的预期行为将不会被执行。\r\n\r\n为解决此问题，Ambire 已修改合约，在`tryCatch()`函数中在低级调用之前和之后检查剩余 gas，并在调用后的剩余 gas 不足时撤销交易。与 Ambire 的实现相反，Infinitism 的 ERC4337 参考实现[在 UserOperation 调用失败时撤销](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/core/EntryPoint.sol#L100)交易，因为出现了 gas 耗尽错误。\r\n\r\n### 5. ERC4337 兼容性\r\n\r\n在 Biconomy 中，存在几个部分不符合 ERC4337 标准。存在一些情况，其中逻辑在 ERC4337 中指定的顺序中[未正确实现](https://github.com/code-423n4/2023-01-biconomy-findings/issues/498) （`createSenderIfNeeded()`）， [实现不正确](https://github.com/code-423n4/2023-01-biconomy-findings/issues/498) （`validateUserOp()`），或者[根本未实现](https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/BaseSmartAccount.sol#L60-L68) （聚合器验证）。\r\n\r\n此外，有一种情况实现了可以阻止标准预期行为的修饰符函数。例如，Biconomy 的`SmartAccount`合约中的`execute()`和`executeBatch()`函数具有`onlyOwner`修饰符，限制了与 EntryPoint 的交互。\r\n\r\n在这种情况下，EntryPoint 合约无法调用这些函数，导致用户账户无法与 EntryPoint 合约进行交互。基于 ERC4337 的账户抽象应该允许根据用户账户指定的 UserOperations 执行交易。这些功能应该被移除，因为它们与原始设计意图相矛盾，并限制了交易行为的范围。\r\n\r\nZerodev Kernel 也由于不符合 ERC4337 标准而存在漏洞。根据标准，Zerodev Kernel 的`ECDSAKernelFactory`合约应在 EntryPoint 处质押一定数量的 ETH 以访问存储。如果在 EntryPoint 处未质押资金，则 Bundlers 将不执行使用 Factory 的 UserOperations 的交易。在 Zerodev Kernel 的情况下，发现了 Bundlers 未在应访问的 EntryPoint 处质押的问题。\r\n\r\n不遵守标准可能导致未预期的错误或在未来交互中与其他协议不兼容。由于不同实体可以操作账户抽象的组件，如 Bundler、Paymaster、EntryPoint 和 Wallet Factory，项目在实现过程中必须仔细验证对 ERC4337 标准的兼容性。\r\n\r\n此外，在 Zerodev Kernel 中存在问题，可能导致与外部库和标准的交互产生意外行为。在 Zerodev Kernel 的`SessionKeyValidator`合约中，`validateUserOp()`函数负责使用 Merkle Proof 机制验证 UserOperations，该机制实现为使用外部验证库进行验证。该函数返回`MerkleProofLib.verify()`的结果，该库实现为对于有效的 UserOperation 返回 1，对于无效的 UserOperation 返回 0。换句话说，对于有效的 UserOperation 返回 1，对于无效的 UserOperation 返回 0。\r\n\r\n然而，在 EntryPoint 中，值 1 被解释为`SIG_VALIDATION_FAILED`，表示签名验证失败。因此，使用 Merkle Proof 验证 UserOperation 失败被认为是 EntryPoint 处用户资金被盗的签名验证成功。在项目实现过程中使用不同的验证库验证 UserOperation 时，必须充分了解潜在情况，而不是直接使用结果值。\r\n\r\n**变体逻辑中的漏洞**\r\n\r\nERC4337 支持一种称为“先验地址”的功能，允许用户提前知道账户的地址并允许向该地址转移资金。Biconomy 也支持 [counterfactual address feature](https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L68)。但是，在 Biconomy 中存在问题，允许调用者在[创建先验地址](https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L33C14-L33C40) （与先验钱包相同地址）时指定任意 EntryPoint。在此过程中，EntryPoint 的地址未包括在账户的创建中，仅使用`owner`和`index`作为 salt。因此，攻击者可以在用户之前发现先验钱包地址，并在用户之前设置恶意 EntryPoint。如果用户在创建之前预先为账户提供资金，攻击者可以将攻击合约设置为 EntryPoint 并清空其中的资金。\r\n\r\n在 ERC4337 标准中，用户无需单独指定 EntryPoint。相反，账户实现（`SimpleAccount.accountImplementation`）仅利用预先注册的 EntryPoint 地址。因此，即使攻击者部署了一个账户，也不会使用攻击者指定的 EntryPoint；而是使用预先注册的 EntryPoint 地址。\r\n\r\n### 6. 实现保护\r\n\r\n在 Biconomy 中，存在一个问题，允许攻击者占据`SmartAccount`工厂中指定的实现的所有权。如果实现未在账户部署时初始化，攻击者可以通过`selfdestruct()`销毁实现，并冻结指向特定实现合约的所有账户的所有操作。类似的情况也存在于 Ambire 的`AmbireAccount`合约中，它也缺乏对其实现的保护，这允许授权实体通过`constructor()`或服务本身销毁实现。这两种情况导致了指向实现的账户的资金冻结。\r\n\r\n在 Infinitism 的 ERC4337 参考实现中存在一个[漏洞](https://blog.openzeppelin.com/eip-4337-ethereum-account-abstraction-incremental-audit#self-destruct-eip4337manager:~:text=However%2C) ，任何人都可以配置管理合约以添加新模块。其中实现可能会因调用`EIP4337Manager`合约内部的`delegatecall`而被销毁。该合约继承自`GnosisSafe`合约，问题出现在任何人都可以像[下面的代码](https://github.com/eth-infinitism/account-abstraction/blob/6dea6d8752f64914dd95d932f673ba0f9ff8e144/contracts/gnosis/EIP4337Manager.sol#L66)中所示添加任意模块。攻击者可以添加一个调用执行`selfdestruct()`的模块，从而销毁`EIP4337Manager`合约。\r\n\r\n```solidity\r\n// https://github.com/eth-infinitism/account-abstraction/blob/6dea6d8752f64914dd95d932f673ba0f9ff8e144/contracts/gnosis/EIP4337Manager.sol#L66-L72\r\n\r\nfunction setup4337Modules(\r\n  EIP4337Manager manager //the manager (this contract)\r\n) external {\r\n  GnosisSafe safe = GnosisSafe(payable(this));\r\n  safe.enableModule(manager.entryPoint());\r\n  safe.enableModule(manager.eip4337Fallback());\r\n}\r\n```\r\n\r\n### 7. 非预期交易失败\r\n\r\nBiconomy 提供了多种方式从用户账户执行交易，包括通过 EntryPoint 调用`execFromEntryPoint()`或`execute()`函数以及执行`execTransaction()`函数。Biconomy 的`SmartAccount`合约为 EntryPoints 维护了一个固定的`batchId` 0。当 EntryPoint 执行账户的`validateUserOp()`时，`nonces[0]`会增加 1。\r\n\r\n`batchId` 0 应该只由 EntryPoints 调用。但是，在`execTransaction()`函数中没有限制禁止其他 Relayers 使用`batchId` 0。如果使用设置为 0 的`batchId`调用`execTransaction()`，则`batchId` 0 的 nonce 将增加，并且 EntryPoint 的后续交易将由更新后的 nonce 撤销。这种非预期的交易失败可能导致 Relayers 的财务损失，并减少他们的活动动力。\r\n\r\n如[先前文章](https://medium.com/chainlight/patch-thursday-all-about-account-abstraction-part-1-11fadf3cb8fe#:~:text=Revert Reason Bombing Vulnerability%3A)中提到的，Infinitism 的实现也存在可能导致交易失败的漏洞。在 Infinitism 的标准实现中，当 UserOperations 执行阶段的交易因某种原因撤销时，EntryPoint 被设计为以`memory bytes`形式接收撤销原因。然而，这个过程会消耗大量 gas 来复制极长的撤销原因到 EntryPoint，这可能导致强制交易失败。这种类型的攻击可能发生在 UserOperation 的执行阶段，使得在模拟阶段难以预测交易失败。同样，在 EntryPoint 中的意外交易失败可能导致 Bundlers 的经济损失。因此，在设计独立的 EntryPoint 合约时，必须意识到这个问题并加以考虑。\r\n\r\n## 结论\r\n\r\n在本文中，我们讨论了基于 ERC4337 的账户抽象项目必须认识到的安全考虑。目前，基于 ERC4337 的实现处于早期阶段，除了本文提到的项目之外，许多有前途的项目，如 [Gnosis Safe](https://github.com/safe-global/safe-core-protocol-specs/blob/main/whitepaper.pdf)，正在积极开展与 ERC4337 相关的项目。由于许多这些项目尚未完成安全审计，因此仍有可能由于这些项目独立开发的功能而出现新的漏洞。\r\n\r\n鉴于迄今为止开发的项目的初始实现中出现的各种问题，对于即将到来的项目来说，在开发过程中仔细考虑本文中提到的要点至关重要。\r\n\r\n## 参考\r\n\r\n- https://code4rena.com/reports/2023-01-biconomy\r\n- https://medium.com/code4rena/smart-account-security-69b544c0da86\r\n- https://github.com/4337Mafia/awesome-account-abstraction\r\n- https://code4rena.com/reports/2023-05-ambire\r\n- https://solodit.xyz/issues/m-02-attacker-can-force-the-failure-of-transactions-that-use-trycatch-code4rena-ambire-ambire-git\r\n- https://github.com/zerodevapp/kernel/tree/main/audits\r\n- https://blog.openzeppelin.com/eip-4337-ethereum-account-abstraction-incremental-audit\r\n\r\n## 关于 ChainLight\r\n\r\n>  ChainLight 每周发布的报告，介绍和分析我们的安全研究人员发现和报告的漏洞。我们的使命是协助安全研究人员和开发人员共同学习、成长，并为使 Web3 成为更安全的地方做出贡献，我们每周发布一次报告，免费提供。\r\n>\r\n> *要获取由屡获殊荣的专家进行的最新研究和报告：*\r\n>\r\n>\r\n> *👉 关注@*[*ChainLight_io*](https://twitter.com/ChainLight_io)\r\n>\r\n\r\n## 关于 AAStar社区\r\n\r\n AAStar 将研究和讨论当前以 ERC4337（账户抽象）为中心的智能合约钱包的技术和产品以及相关的生态建设。\r\n\r\nAAStar 分享账户抽象相关技术与相关进展，让跟多的人了解AA。\r\n\r\n---\r\n\r\n>  原文链接： https://medium.com/chainlight/patch-thursday-account-abstraction-security-guide-c348cc5e36ee"},"author":{"user":"https://learnblockchain.cn/people/17736","address":null},"history":"bafkreicrmtrxayvzc5ohlornkebdpxihc2b4jxjis2xrrmhvr4u4tw3a4q","timestamp":1700924247,"version":1}