Web3 安全审计师回顾2024 年安全问题

Web3 安全审计师回顾2024 年安全问题

欢迎来到 Web3 安全审计师的 2024 年回顾,这是对去年显著安全事件和漏洞的简明技术分析合集。

该系列文章最初名为 以太坊智能合约审计师回顾,由 Patrick Drotleff 撰写,他是一位信息安全和隐私倡导者,独立安全研究员及 Secureum 社区的导师。在 TrustX 伊斯坦布尔会议上联系后,Patrick 将继续这一传统的重任托付给我们,同时将重心转向新的事业。

我们遵循了原有格式,为每个事件提供简洁、可操作的见解——突出漏洞并提供足够的背景信息,以便在安全审查或漏洞猎探过程中识别这些问题。为了增加问题的多样性,今年的汇编超越了利用攻击,还包括通过传统审计、竞赛和漏洞赏金平台发现的漏洞。此外,随着审计越来越多地涵盖链外基础设施,我们还包含了来自 Rust 和 Golang 系统(如区块链节点或 CEX 上线基础设施)的漏洞。

突出问题

EIP-1153: 瞬态存储

以太坊 Cancun-Deneb 升级引入了瞬态存储 ,这是一种类似于持久存储的短期数据空间,但其生命周期仅限于一个交易。开发人员可以声明一个变量为 transient 或利用 tstoretload 汇编指令与瞬态存储进行交互,从而享受其成本效益。每次操作大约需要 100 gas,这显著低于传统存储,后者通常需要约 20,000 gas 进行读写。不过,复杂性增加也带来了更大的风险,瞬态存储也不例外:

  • 由于访问瞬态存储的低 gas 成本, 低 gas 补贴的重入攻击 (就像通过 address.transfer 的 2300 gas 限制)已经成为可能。如果代码块未遵循检查-效果-交互模式,或未使用 ReentrancyGuard,安全研究人员应确保瞬态存储变量安全,不受重入修改的影响,因为此类变化可能导致合约行为的恶意或意外变化。

  • 目前,瞬态存储仅支持读写值类型。由于数据结构是引用类型,希望使用它们的开发人员必须实现妥协类型安全的变通办法。这个 Solidity Underhanded Contest 提交 演示了缺乏类型安全如何导致数据被意外解码为具有相似布局但不同含义的结构,这可能引入微妙且潜在可利用的错误。

  • 最后,虽然在交易结束时瞬态存储会自动清除,但开发人员应显式清除它,以防止在“肮脏”的瞬态状态下与合约发生可能的交互。 以下文章详细说明了在 multicalls 或合约间复杂交互期间,如何导致回滚或不正确的行为。

    • *

Uniswap V4 双入口代币

在 2024 年,Uniswap V4 经过几次深入审计,其中一项揭示了一个关键问题 ,涉及在原生代币也具有 ERC20 表示的链上部署该协议。

在 Uniswap V4 中,settle 函数通过增加指定代币的账户增量来管理债务。它使用 msg.value 来处理原生代币增量,并在 ERC-20 代币的上次 syncsettle 调用以来的余额变化。这种机制可以在原生代币(如 Celo 上的[CELO](https://celoscan.io/address/0x471ece3750da237f93b8e339c536989b8978a438))也具有 ERC-20 表示的链上被利用:攻击者可以在同步以加载当前余额后,首先调用 settle 并使用 msg.value > 0 来增加原生代币的增量,然后使用 ERC-20 代币的地址再调用 settle。由于 ERC-20 代币的余额在第一次调用后增加,攻击者可以在第二次调用中膨胀增量,而无需实际转移代币,从而允许他们稍后提取比他们存入的更多代币,最终耗尽池子的资金。

Rewind 4

修复措施确保 settle 调用必须解决之前同步的代币,否则交易将回滚。


Polygon PoS 投票接管和无限铸造漏洞

以下研究文章揭示了如何通过将一个恶意验证者与 Heimdall 验证器代码中发现的两个问题结合起来,可能导致 Polygon 桥的资金被抽取。

Polygon 权益证明网络由三层组成:

  1. Ethereum 合约:管理质押、检查点提交和验证者奖励。

  2. 共识层 (Heimdall):根据以太坊的质押状态确定区块生产者并推送检查点更新。

  3. 执行层 (Geth 分支):生成新区块。

当用户在以太坊上质押 MATIC 时,会触发 StakeUpdate(uint256 validatorId, uint256 nonce, uint256 newAmount) 事件。链外组件通知 Heimdall,从而增加验证者的投票权以进行共识。为了验证此类事件的合法性,Heimdall 查询以太坊以确保事件确实存在,字段值匹配且 nonce 与 Polygon 上存储的值对齐。

第一个问题出现是因为 Heimdall 未能验证事件类型。如果以太坊合约发出类似结构的事件,如 SignerChange(uint256 validatorId, address oldSigner, address newSigner),Heimdall 可能会错误地将其解释为 StakeUpdate。在这种情况下,Heimdall 将把 oldSigner 解析为 nonce,将 newSigner 解析为 newAmount。一个恶意验证者可能会发出一个包含匹配其 Polygon nonceoldSigner 地址和一个解析为巨额 newAmountnewSignerSignerChange 事件,人为地提高其投票权。

Rewind 1 & 2

第二个问题在于 Heimdall 如何验证 nonce。事件的 256 位 nonce 会被截断为 uint64,这极大地降低了生成与 Polygon 上的 nonce 匹配的 oldSigner 地址的计算难度。

Rewind 3

攻击者只需对齐 oldSigner 的最后 64 位即可。尽管相当有限,攻击者可以通过链上操作进一步减少所需的计算努力。

凭借压倒性的投票权,攻击者可以操控共识,接受虚假的存款事件,任意在 Polygon 上铸造资金。通过将资金再桥接回以太坊,他们可以抽取 Polygon 桥的资金。幸运的是,该漏洞已被负责任地披露并修复,没有发生任何事故和资金损失。


Compound DAO 治理攻击

在七月,Compound DAO 面临了一次攻击,旨在通过恶意治理提案。该提案试图将 499K COMP 代币—占总供应量的 5%,当时价值 2500 万美元—从国库转移到投资金库。除了转移之外,代币的投票权也将被委托给提案者。鉴于 Compound 的投票率通常为总代币供应量的 4-5%,攻击者可能通过通过该提案来控制治理过程。能够通过任何恶意提案,Compound 的总锁仓价值 (TVL) 和国库资金将面临风险。

攻击包括三个提案:

  • 提案 247 (5 月 6 日):直接在链上创建,没有相应的论坛帖子详细说明,这是 Compound 社区内的惯例。该提案意图将 92K COMP 代币从国库转移到“GoldenBoyz”拥有的多签地址,承诺这些代币将进一步投资于一个将其用于收益的金库。这引起了 OpenZeppelin 及我们的一位同事对此事的怀疑,并迅速通知了社区关于恶意活动的可能性。该提案随后在遭到大量反对票后被取消。

  • 提案 279 (7 月 15 日):这次附带了论坛帖子,意在通过 TrustSetup 智能合约直接将资金转入金库,绕过“GoldenBoyz”的多签。虽然这是一个改进,但新问题浮现 ,即代币的投票权在投资前已被委托给“GoldenBoyz”的多签。该提案被大量反对票打下,并未通过。

  • 提案 289 (7 月 24 日):与第二个提案相同,除了数量现在为 499K 代币而不是 92K。尽管社区迅速被通知 ,但“GoldenBoyz”集结了足够的票数通过该提案。怀疑前两个提案仅是为了测试社区的通常投票权,只是在此期间累积更多资金,以在第三个提案的投票中压倒反对派。此外,大多数“支持”票都是在投票期结束时投出的(见 Tally 中的“时间线”),限制了社区的应对能力。

鉴于“GoldenBoyz”的前期投票权和新获得的 499K COMP 代币,提案 289 的成功执行可能使攻击者控制 Compound 治理,使其他大型代币持有者在面对任何恶意提案时无能为力。

社区以提案 290 进行反制,旨在将时间锁管理权限转移至社区多签,并限制“GoldenBoyz”对 DAO 国库和协议执行恶意行为。感谢 Compound 委托人和 OpenZeppelin 及其他方的迅速行动,这一紧急提案消除了威胁,通过防止新获得的投票权被滥用。

最终,“GoldenBoyz”与 DAO 达成协议,在执行前取消了提案 290。虽然他们及其相关账户仍保留显著的投票权,但目前并不构成对 DAO 的直接威胁,不过需要保持持续警惕。

作为额外的安全措施,Compound 协议添加了一个临时提案管理员,拥有取消恶意提案的权力。


Kraken 交易所余额打印漏洞

中心化交易所通常在链外维护用户余额,基于观察到的链上存款和交易活动进行更新。用户被分配存款地址,一旦检测到转账到这些地址,他们的交易账户将被记入存款的代币。

在六月, 一项关键漏洞在 Kraken 中心化交易所被报告,允许恶意用户在未完成实际链上存款过程的情况下夸大其账户余额。虽然用户资金依旧安全,但该缺陷使 Kraken 的国库处于风险之中,使攻击者能够创建和提取虚假余额,作为真实链上资金,累计总额约为 300 万美元。

来自 DanielVF 的分析及其概念验证显示,如果构造得当,Kraken 系统将会将一次回退的代币转移视为有效存款。重要的是,我们无法找到详细说明其确切技术细节的官方事后报告。

一笔包含内部调用的交易转移了资金,随后回滚,错误地被视为有效存款,而没有考虑到内部调用的回滚。信用的兑换金额与回滚交易中代币转移的金额相符,因此攻击者可以利用闪电贷来放大他们的收益,从而使表面存款价值膨胀。

正在进行的调查旨在澄清这是否是研究人员的概念证明示范,还是一次恶意的资金盗窃尝试。


Radiant Capital 黑客事件

在十月,Radiant Capital 遭遇了一次安全漏洞,导致约 5000 万美元的损失。至少三个开发者的设备通过复杂的恶意软件注入被攻击,详细信息见 事件更新。此次漏洞使攻击者能够在交易数据到达硬件钱包进行签名处理之前,从 Safe UI 拦截真实的交易数据并进行篡改,最终导致汇集了不同的恶意链上操作的签名。

攻击涉及更改发送给硬件钱包的交易详情。在 Safe 前端显示的合法交易数据的同时,实际签名的交易是恶意的,使得攻击者能够在达到 3/11 的法定签署人同意后执行 transferOwnership 调用。Rewind 5

攻击者通过成为 Radiant 的 LendingPoolAddressesProvider 合约 的所有者,改变了 借贷池代理的实现,盗取了所有资金和之前给予的代币授权。Safe 预期的签名与硬件钱包给出的签名之间的差异导致了 Safe UI 和链上执行过程中的错误,但由于这种错误在提交元交易时很常见,因此并没有引起立即的怀疑,也没有受到详细分析。

要深入了解 Safe 如何与硬件钱包交互以及盲签名的风险,请参阅这个 分析

常规问题

缺失的输入验证

  • 在 Beanstalk 稳定币协议中,用户可以将 BEAN 代币作为流动性提供给 Well 合约(类似于流动池),以交换 LP 代币。当用户提取流动性时,他们会指定要提取的 Well 地址和要燃烧的 LP 代币数量。Well 合约负责燃烧 LP 代币并计算要返回给用户的 BEAN 代币数量。然而,Well 合约 没有经过验证,也不受任何白名单约束。攻击者可以创建一个合约,当被调用时,虚假报告任何燃烧的 LP 代币数量使用户有权获得 Beanstalk 合约中持有的全部 BEAN 代币余额。

  • BankrollNetworkStack 合约 作为一个保险库,用户通过 buyFor 函数 存入 WBNB,以交换 Bankroll 股票。部分存入的金额作为 WBNB 股息分配给每个股东。在累计足够的股息后,用户可以将他们的股票卖回到保险库并提取所有累积的股息。然而,buyFor 函数缺少对 _customerAddress 参数的检查,并不会验证其是否未设置为保险库自己的地址。这一疏忽允许攻击者反复调用该函数,将 _customerAddress 设置为保险库地址并将 buy_amount 设置为保险库中的 WBNB 总额。这将把 WBNB 从保险库转移到其自身,同时将部分金额分配为股息,其中一部分被攻击者所累积。在足够次数调用该函数后,攻击者出售其股票并提取了膨胀的股息,导致其他用户损失。

缺失的访问控制

  • TSURU 系统收到 ERC1155 代币时,会调用 TsuruWrapper 合约的 onERC1155Received 函数,为存款人铸造相应的 TSURU 代币。然而,onERC1155Received 函数缺乏适当的访问控制,允许攻击者直接调用它并 无限制地铸造代币 给自己。攻击者随后使用这些代币提取了 TSURU-ETH Uniswap 流动池的资金。

  • Galaxy Fox 代币空投通过在链上存储一个默克尔树根,允许参与者通过提交有效路径并将其地址作为叶子节点来领取代币。然而,setMerkleRoot 函数缺乏访问控制,允许攻击者 设置恶意构建的根,并根据该根领取足够的 GFOX 代币以抽干 WETH-GFOX Uniswap 流动池。

  • Alchemix 项目允许用户存入收益-bearing 资产,如 yvDAIwstETH,作为借入合成资产如 alDAIalETH 的抵押品,从而提供即时流动性。定期通过 harvest 函数收集收益,并有时需要通过在 Balancer 上进行交换来解除包装,创造了夹击攻击的机会。为了降低这些风险,harvest 函数包含了一个 minimumAmountOut 参数,并且只能由 Gelato Automate 合约调用 — 一个旨在以正确参数安排定期链上调用的系统,类似于传统的 cron 作业。

    然而,Alchemix 与通用的非定制版本的 Gelato 集成,该版本不会响应 仅由专用 msg.senders 的请求,允许任何人触发来自 Automate 合约的调用。这一缺乏定制的情况将允许攻击者 创建具有任意 minimumAmountOut 值的调用链,便于在解除收获收益时进行显著的夹击。

  • Maia DAO 系统中的 CoreBranchRouter 合约 缺乏必要的访问控制,因为一个虚拟函数未使用适当的调用者限制进行重写。此疏忽允许攻击者充当可信的 CoreBranchRouter 并通过一系列调用提取资金。正如事后分析中所强调的,开发人员和安全研究人员应仔细评审所有继承的合约,以防止类似的漏洞。

下溢和上溢

  • 在 Mantle 上,通过指定转账金额,可以在 L1 发起 MNTETH 之间的 L2 地址转账。在未验证余额的情况下,触发 TransactionDeposited 事件,并由 Mantle 节点接收,该节点会确保 from 地址有足够的资金进行转账。然而,当涉及时 ETH 转账时,Mantle 节点并未确保 from 地址有足够的资金,并直接更新了余额 。由于使用了 Golang 的 BigInt 类型和 BigToHash 函数 ,未能检测到下溢,并且负值会被转换为正值。这使得攻击者可以转账他们并没有的 ETH 到他们控制的另一个地址,同时增加两个余额,从而无限铸造。

  • 一名攻击者通过利用代币 transferFrom 函数中缺少的下溢检查, 盗取了多个用户的 LW 代币 。该函数未能验证转账金额是否在批准的金额内,假设减法在变为负数时会自动回滚。然而,该合约是使用 Solidity 0.7.6 编译的,缺乏自动下溢检查。这使得攻击者能够从用户那里转移未获得批准的代币,绕过了预定的限制。

    有关相同漏洞模式的另一个示例,参见这篇文章关于 Scroll 代币漏洞的分析。

闪电贷回调中的验证缺失

不完整的签名方案

  • ERC1271 中的 isValidSignature 函数提供了一种标准方法,使合约能够验证代表给定合约的签名是否有效。一个常见的用例是智能账户,它依赖于所有者的签名来授权操作。不幸的是,参考实现本质上未将 msg.sender 或目标合约地址与签名绑定。此遗漏可能导致签名重放攻击 ,即针对智能账户 A 的签名可能被重新用于账户 B,只要这两个账户具有相同的所有者和消息格式。

  • 为了获得 Taiko 拨款,部署了 TimelockTokenPool 合约,使得接收者在一段时间延迟后可以提取代币,并使用有效的签名。签名是根据 keccak256(abi.encodePacked("Withdraw unlocked Taiko token to: ", _to)) 摘要进行验证的, 缺少了几个关键字段

    • 没有 nonce,签名可能会在同一个 TimelockTokenPool 合约中被重放。

    • 没有验证合约的地址,签名可能在不同的 TimelockTokenPool 合约之间被重放。

    • 最后,缺少 chainId,签名可能会在其他链上重放。

  • 在 Phi 线下系统中,签名授权机构会为用户发出签名,以便他们在链上领取奖励。虽然签名是由所有必要安全字段构成的,但链上验证机制未检查 chainId 是否与当前链匹配。因此, 为一个链生成的签名可能会在另一个链上重用 ,以领取额外奖励。通过使用 EIP712 抽象合约 ,开发者可以确保所有安全字段都包含在签名中,并针对执行上下文进行验证(例如 versionaddress(this)block.chainid)。

  • 在 Phi 系统中,签名授权机构会使用前端验证的参数生成线下签名,供用户在链上提交并创建 NFT 艺术。然而, 并非所有参数都包含在签名中 ,允许攻击者抢先提交签名并更改某些字段,从而改变 NFT 的艺术家、最大供应量或版权接收者。

不安全的类型转换

  • Uniswap v4 中,validateMinOut 函数中的不安全类型转换从 int128 转为 uint128,可能允许跳过滑点检查。这是因为一个钩子可能返回负值 liquidityDelta,当它转为 uint128 时,会变成一个非常大的正数,超过用户指定的最小输出。 修复方法涉及使用 SafeCast.toUint128,当 liquidityDelta 为负时,将回滚交易。

  • 在 Filecoin 网络的 Lotus 和 Venus 客户端中发现了一个漏洞 ,可能使攻击者远程崩溃节点。根本原因是由于不安全的类型转换,从无符号整数到有符号整数,以及随后的越界访问。在 validateCompressedIndices 函数中,消息索引是无符号整数,被不安全地转换为用于验证的有符号整数。该验证检查索引是否小于 Bls 切片的长度,但由于对等方可以控制索引值,因此可以设置一个大于有符号整数最大值的索引。当转换为有符号整数时,这样一个大的值会回绕变成负数,从而绕过验证检查。这个错误导致在验证后产生越界访问,引发恐慌,从而导致节点崩溃。

  • AsEthereumData 函数中发现了一个漏洞,该函数用于解码以太坊交易数据。该漏洞的产生是因为该函数未验证 vrs 签名值(类型为 big.Int),而是直接用 uint256.MustFromBig 将它们转为 uint256。如果这些 big.Int 值被恶意构造得过于庞大,MustFromBig 函数可能会因溢出而导致 panic,从而使 Sei Node 关闭。

重复性攻击

  • 在 Scroll 上存入带发送者钩子的 ERC20 代币时,一个 放错位置的重入保护 可能允许攻击者获取膨胀的数量并可能从合约中排空资金。nonReentrant 修饰符放置在内部 _deposit 函数上,而该函数并未包含代币转移,而是放置在包含 safeTransferFrom 调用 的外部 depositERC20 上。

  • SumerMoney 项目是支持 ETHUSDC 等资产的 Compound 复刻版,其贷款偿还逻辑有所不同:在更新 totalBorrows 之前,它退还任何多余的金额,这可能会在基础资产与 cToken 之间的汇率膨胀时将执行权交给调用者。攻击者通过 借入 ETH,以 1 wei 的盈余偿还,并以膨胀的汇率兑换抵押品 来利用这一点。通过这样做,攻击者在兑换较少的 cTokens 时恢复了其最初的抵押品,并利用剩余的金额进一步借入更多基础代币而不进行还款,从市场中排空资金。

  • ETH/OETH Curve 池的 withdraw_admin_fees 函数将根据内部(self.balance; OETH.balanceOf(address(this)))和会计余额(self.balances[0]; self.balances[1])的差值确定的多余资金发送到一个费用接收者。因为该函数的行为不依赖于 msg.sender,所以缺乏访问控制。

    remove_liquidity_imbalance 函数允许用户以不成比例的金额提取流动性,先转移 ETH 并在转移 OETH 之前将执行权交给调用者。这将暂时使 OETH.balanceOf(address(this)) 保持在初始且不一致的状态。通过在正确的时间调用 withdraw_admin_fees,攻击者可以 将池的内部 OETH 余额减少到低于会计余额,从而暂时打破池的会计

  • Onyx 是一个从 Compound V2 分叉而来的借贷平台,实现了自己的清算流程。平台内的低流动性市场允许攻击者通过 持续铸造和赎回单一 LP 代币,逐渐降低其汇率。这一过程降低了抵押品的价值,直到它处于略微亏损的状态,从而允许攻击者启动有利可图的自清算。

    此外,Onyx 的清算逻辑中缺少输入验证,使得攻击者能够使用假代币进行偿还,从而提高了攻击的盈利性。

  • Penpie 项目为 Pendle Finance 用户提供收益提升服务,允许用户通过 batchHarvestMarketRewards 函数从特定 Pendle markets 收集奖励。该函数在 记录代币余额之前、赎回奖励并记录 代币余额之后,差额代表需要分配的奖励。

    然而,攻击者通过附加一个有效的 Pendle 市场和一个恶意的自定义代币来利用这一点,该代币在 IPendleMarket.redeemRewards() 内的代币转移过程中将执行权交出。攻击者利用这个执行窗口调用 depositMarket,在同时膨胀用于计算奖励的“之后”代币余额的同时存入代币以铸造 LP 代币。

价格操纵

取整错误

  • 在 The Graph 系统中,质押代币收取 1% 的策展费用。然而,由于费用计算功能进行了向下取整,策展人可以通过反复质押只有 99 个代币来避免支付费用。利用 multicall 在像 Arbitrum 这样的低成本网络上,攻击的威力可以得到增强。

  • 文章 Precision Loss Accumulation: The “Two Parser Bug” Lurking in the Shadows 探讨了如何小的、重复的精度损失可以累积,从而可能导致拒绝服务或破坏不变式。

    第一个例子描述了一个奖励系统,其中每个奖励向上取整,而费用则向下取整,这导致了累积的过度分配,可能会阻止最后一位用户领取他们的奖励。

    第二个例子讨论了一个包含公共和私人参与者的代币销售,在这种情况下,汇率计算中累积的精度损失允许参与者领取略高于销售上限的代币。

任意调用

  • 在 Mantle 中,桥接 ETHMNT 会在源链上锁定代币,并在目标链上的 CrossDomainMessenger 合约中铸造代币。然后,CrossDomainMessengertarget 上调用 relayMessage,转发资金并执行用户定义的任意逻辑。如果调用失败,消息会被存储以备重放,资金在此之前将保留在合约中。由于对 target 地址验证不足,攻击者 可以构造并发送恶意跨链消息,这将触发 approve 调用。由于交易是以 CrossDomainMessenger 作为 msg.sender 执行的,因此它将授予攻击者对锁定资金的批准,以应对失败的消息。

  • Li.Fi Dex Aggregator 引入了一个新的钻石面,允许用户存入代币并指定交换的路线。然而,对目标和路线中的函数选择器验证不足允许了任意调用。一名恶意攻击者利用该漏洞,编码目标为代币地址,函数选择器为 transferFrom最终从所有之前批准了脆弱 Li.Fi 合约的用户那里抽走了代币

  • Spectra Finance 允许用户从多条路线中选择执行代币交易,其中一条是通过 KyberSwap。然而,kyberRoutertargetData 参数没有得到适当验证。与 Li.Fi 漏洞类似,这一漏洞允许攻击者编码 asdCRV 代币地址和 transferFrom 选择器, 从之前授予 Spectra Finance 合约批准的地址中抽走代币

  • 类似地,Socket 的 Bungee Bridge 也被利用 了一个任意执行漏洞。由于对 swapExtraData 参数缺乏验证,攻击者可以编码一个 transferFrom 并从 WETH 地址的桥中调用它,从而抽走所有未被撤回的批准。

错误的业务逻辑

  • uniBTC 系统允许以 1:1 的汇率铸造 uniBTC 代币,抵押的为 BTCBTC 等值 代币。为限制对 非 BTC 等值 代币的铸造,该系统使用了一个 限制机制,当如果保险库的持有量(通过 totalSupply 函数返回)超过设定上限时,存款将回滚(对 非 BTC 等值 代币设定为 0)。在非 BTC 原生链上,NATIVE_BTC 地址 代表链的原生代币,可能不等于 BTC。对于这样的代币,totalSupply 函数错误地返回 0,因此几乎总是通过上限检查:(totalSupply == 0) <= (cap == 0)。这一疏忽 让攻击者能够以原生代币铸造 uniBTC,而该代币在多个链上并不是 BTC 等值代币。

  • Zyfy 项目的 PermissionlessPaymaster 合约允许通过启用用户设置一个经理地址为他们的交易提供 gas 费用赞助。在赞助交易后,剩余的 gas 被记入 previousManager 状态变量,该变量跟踪最后一笔交易的赞助商。该退款稍后可以由相应的经理提取。然而,selfRevokeSigner 函数的一个漏洞使得用户在更新 previousManager 变量的同时撤回他们的经理。

    攻击者 可以利用这一点,通过回溯赞助交易,调用 selfRevokeSigner,将 previousManager 更新为自己的地址。由于上一笔交易的退款是通过这个变量处理的,攻击者可以窃取本应归合法赞助商的 gas 信用。

锁定资金

  • 在 Renzo Finance 中,ETH 提现是通过 transfer 发送给索赔者的,而该操作只转发 2300 gas。如果索赔者是一个具有非平凡 receive 函数的智能合约,gas 限制将不足,导致交易回滚,从而有效地 将资金锁定在不可索赔的状态

  • Bridged USDC Standard 是 Circle 推荐的将 USDC 桥接到新区块链的方法,后续还有缩减流动性碎片化的选项,过渡到原生 USDC。DeFi Wonderland opUSDC 系统USDC 桥接到以 OP stack 为基础的链,允许将合约所有权转移给 Circle,以实现原生 USDC 的过渡。在 L1 上锁定 USDC 后,L2 相关合约具有权限,并将 USDC 铸造给接收者。如果在 L2 上铸造因为 gas 不足或桥接 USDC 被暂停而暂时失败,交易可以在 L2 上重放。然而,如果期间发生了原生 USDC 的过渡,则铸造权限将转移给 Circle,导致 任何重放交易将无限期回滚,从而锁定 L1 资金而无法恢复。

签名可变性

残留批准

  • Hedgey Finance 允许用户通过 createLockedCampaign 函数 创建解锁活动,该活动将资金从活动创建者(msg.sender)转移并 批准 ClaimLockup 合约 根据活动条款管理这些资金。当活动被取消时,剩余的资金会返回给创建者,但对 ClaimLockup 合约的批准并没有被撤销。一名攻击者通过 创建恶意的 ClaimLockup 合约 利用这一缺陷。该合约设置了一个活动,取消它以收回所有存入资金,然后在残留的批准上调用 transferFrom 再次接收资金。

  • Superposition AMM 的 OwnershipNFTs.sol 合约实现了自定义 ERC721 逻辑,该逻辑 在转移时未撤销代币批准。这一疏忽使得 NFT 拥有者可以将代币批准给自己,之后将其出售给受害者,然后 利用残留的批准回收 NFT 而无需征得同意。

错误的数组处理

  • 在 Alchemix Finance 系统中,同时创建多个检查点将导致 pointHistory 数组中出现重复时间戳的数据点。用于通过时间戳查找条目的二分搜索 未能正确处理重复项,在第一个匹配的时间戳处停止,并可能导致陈旧或不正确的结果。

  • 在 Royco 系统中,创建了提供激励和奖励随意链上操作的激励代币的提议。与上述相似,该系统未能考虑并清理奖励数组中的重复代币。 攻击者可以利用这一点 ,通过创建带有重复奖励代币的提议,自己完成操作,并索要乘以提议中重复代币数量的奖励,从而抽走系统资金。

弱随机数生成器

  • RedKeysGame 合约 允许用户进行代币投注,如果他们正确猜测 _betResult,会获得奖励。然而,结果是 从可预测的链上值派生的,例如区块的 timestampprevrandaogaslimitcoinbasenumber 字段。这使得攻击者可以构造一个合约,计算和提交 获胜数字,从而确保稳定的 winnings。

  • 同样,Boost AA Wallet 使用 block.prevrandaoblock.timestamp 来确定抽奖的赢家。一个控制区块生产的验证者可以通过有选择性地提议或保留区块来影响抽奖结果,直到 block.prevrandaoblock.timestamp 的值对他们有利。

自我转移

  • SuperSushiSamurai 代币的自定义 ERC20 逻辑未能处理发送者(from)和接收者(to)相同的转账。此外,它并没有增加接收者的余额,而是覆盖了它。这些缺陷使得攻击者可以 反复将代币转移给自己,每笔交易都将余额翻倍。关键漏洞在代码片段中高亮显示:

    uint256 toBalance = _postCheck(from, to, amountAfterTax) _balances[from] = fromBalanceBeforeTransfer - amount; _balances[to] = toBalance;

    有关具有相同漏洞模式的另一个示例,请参见 此分析 关于 GPU 代币漏洞。

  • Hackathon 代币对 涉及某些 AMM 配对的代币转移 有自定义逻辑,但未考虑到配对同时是 senderrecipient 的情况。这一疏忽通过 标准 AMM skim 函数 被利用,该函数通过退出盈余使代币余额与储备匹配。通过向配对发送多余的代币,并 调用 skim 使池作为接收者,错误的逻辑将多余的代币余额翻倍。随后,攻击者用自己的地址作为接收者调用 skim,收集多余的代币。

不安全的存储使用

  • Delta Prime 项目遭到黑客攻击,原因是其代理和实现合约之间的存储冲突,以及一个被遗忘的 init 函数。该项目使用了一种称为 DiamondBeaconProxy 的模式,每个用户的 Prime Account 智能合约作为链上克隆被部署,从而通过在账户之间重用相同的合约逻辑来降低 gas 成本。账户作为存储,执行则 delegatecalledDiamondBeacon 合约,然后根据功能选择器委托给不同的切面。该设置允许对 Diamond 切面进行更新,从而同时修改所有 Prime Accounts 的功能。

    每个 Prime Account 在部署时通过 initialize 函数进行初始化,该函数设定账户的拥有者。类似地,钻石通过 init 函数进行初始化,设定钻石的拥有者。然而,这两个合约使用了相同的存储槽来存储拥有者地址,但为它们的 initialized 标志使用了不同的存储槽。此外,开发团队在部署后忘记删除钻石的 init 函数。一名恶意攻击者通过对其他用户的 Prime Accounts 调用 init 函数来利用这一点,导致 delegatecall 到钻石的 init 函数,而不是 fallbackinitialized 标志检查通过了,因为它依赖于不同的存储槽,从而允许攻击者通过利用共享存储槽来修改 Prime Account 的拥有者。这使得攻击者能够控制多个 Prime Accounts 并使用管理权限提取资金。

  • 在 Restake Finance 中,提款请求存储用户希望销毁的股份数量,以换取基础代币。一旦处理,协议就会发送代币,删除请求并销毁股份。然而,销毁操作从已经删除的请求中检索了股份数量 ,导致值为 0。因此,股份从未被销毁,并且仍然卡在合约中,稀释了股份与基础资产的转换。

有缺陷的奖励系统

  • Pythia Finance 的质押合约允许用户质押 Pythia 代币以换取奖励。然而,奖励计算仅将用户余额乘以一个累积的每股份奖励变量 ,而没有考虑质押的持续时间。这个缺陷使得攻击者能够存入大量 Pythia 代币并反复索取过多的奖励

  • BGM 代币通过将 BGM 索赔分配给发送者接收者他们的推荐人来奖励转账,奖励来源于 BGM/USDT 的流动性池 。攻击者通过创建众多账户并进行人为转账来利用这个系统 ,以膨胀奖励。这些奖励随后被提取,耗尽了 BGM 的池,并显著提高了代币的价值。最后,攻击者借助膨胀的价格抽走了池中的 USDT

  • Elfi 协议允许用户质押代币,铸造 stakeToken (st) 作为存款的证明,同时也在内部记录质押的金额。奖励是基于 st.balanceOf(account) 而非内部记账计算的。这个失误,加上 st 的可转让性,导致了一个漏洞 :用户可以创建多个账户,质押代币,并在账户之间连续转移 st。这将使他们能够反复从两个账户中索取奖励,有效地将收入翻倍。

  • Ramses Exchange 根据奖励期和 veNFTtokenId 的组合来奖励用户,使用 tokenTotalSupplyByPeriod 值。然而,它在奖励被索取时未能减少 tokenTotalSupplyByPeriod,导致用户能够多次索取相同的奖励。此外,它未检查当前区块时间戳是否与指定的奖励期匹配,使得过去的奖励也能被索取。最后,为了绕过 tokenId 奖励索取的限制,攻击者利用系统的一个功能,允许铸造具有新 tokenIdsveNFT

交易时间攻击

  • EIP-2612 (Permit) 允许用户离线签署代币批准,由其他方在链上执行,无需持有原生代币用于 gas。该签名是一次性和不可变的,确保无论谁提交,都能得到一致的结果。然而,出现了一个拒绝服务和不完整执行的攻击向量 。在这种情况下,用户向智能合约提交一个交易 (T),该交易同时通过 permit() 批准资金并执行额外操作 (A)。攻击者可以观察已提交的签名,用他们自己的交易来抢跑交易 T 仅执行 permit() 并成功。由于签名重放保护,这会导致交易 T 回滚,从而导致资金正确批准,但操作 A 从未执行。

  • Uniswap V4 Peripheral Contracts 在一个设计为 multicall 的功能中使用 Permit2 来批准支出者。与上述情况类似,攻击者可以通过前置 multicall 并单独提交这些许可来利用这一点。这会导致合法的 multicall 交易失败,因为它试图消费一个已经被使用过的签名的许可。

  • SquidTokenSwap 合约允许任何用户触发 PancakeSwap 在合约中持有的 squidV1squidV2 代币之间的交易。由于这一点和设置的 5% 滑点容忍度使得攻击者能够反复触发和夹击这些交易 ,以滑点利润作为收益。

  • 在 Opal Omnipool 中索取奖励时,合约使用 Balancer.batchSwap 执行代币交换时未指定任何滑点保护。与上述类似,这个疏忽会允许攻击者通过夹击交换来窃取部分奖励 ,通过他的两次交换来夹击这些交换。

  • 更新 Radiant 市场的 Pyth 价格预言机需要向合约的 receive 函数发送 ETH,然后调用 updateUnderlyingPrices 更新所需市场。由于这些步骤是独立的,攻击者可以利用这一点反向执行 receive 函数 ,使用提供的 ETH 更新与原始交易发送者意图不同的市场。

忘记在零知识协议中盲化多项式

  • 在某些零知识 (ZK) 协议中,特别是使用 zk-SNARKs 的协议,证明者通过将秘密见证编码为多项式的一部分来证明对秘密见证的知识。为了验证正确性,证明者会在特定点上揭示多项式的评估值,这些点通常在协议的设置中确定。然而,如果没有保护,揭示足够的评估值可能会允许攻击者重构多项式,从而推测出秘密见证,破坏零知识特性。例如,度为 n 的多项式(即 n+1 个系数)可以从 n+1 个评估中完全恢复。

  • 为了对抗这一点,多项式盲化被采用。这涉及通过加入随机性来模糊多项式,比如添加随机系数以防止重构。例如,度为 n 的多项式可能在评估于 r 个点时被盲化为度为 n+r 的多项式。这确保了从评估中无法推断出关于多项式的信息,从而保持见证的秘密,并维护协议的零知识保证。

  • Linea 的 PLONK Go Prover 的实现未对多项式片段施加适当的盲化,这可能允许攻击者统计性地恢复见证。

协议错误配置

  • VOW 代币因在生产环境中使用了非原子测试交易而被利用。 详细信息 。更改操作参数、测试行为,然后撤销参数是在单独的交易中进行的。这使得攻击者能够在这些步骤之间插入他们自己的交易,从而利用合约,因为关键操作参数处于异常状态。

  • UWU 借贷协议将清算阈值设置为 90%,清算奖金设置为 10%,这过高且提供了很少的安全边际。这一设置使得协议即使在轻微的价格波动下也容易受到影响,可能导致坏账。 在攻击期间 ,攻击者通过仅 4% Manipulated oracle price,创建了一个贷款价值比(LTV)为 93% 的头寸。通过这样做,攻击者可以清算该头寸,获得利润计算为 93% * 110%(清算者) - 100%(被清算) = 2% 来自协议。

两个解析器错误

  • 在 Taiko 中,跨链消息在目标链上受以太币速率限制配额的约束,这限制了在特定时间范围内转移的价值。如果一条消息超过了该时间段的剩余配额,它会被标记为 RETRIABLE 并可以稍后重新执行。否则,该消息将进行验证检查,确保其未针对禁止的地址,然后再执行。

    在消息重试流程中发现了一种漏洞,该实现未在执行消息之前执行禁止地址检查。通过故意超过配额,恶意攻击者可以以 Bridgemsg.sender 执行不绑定任何目标的跨链消息,可能会耗尽其他用户的资产。

    此漏洞突显了一个常见问题,即相同数据不应被处理,但在执行路径下却被不同地处理。此类模式在其他系统中也曾被利用,如 这份关键漏洞分析 中所述。

错误的虚拟机 gas 收费

  • Fuel VM 的 CCP(代码复制)指令将代码从合约中复制到内存中,仅根据合约字节码的大小 而不是实际复制到内存中的字节数 收取 gas。此外,当复制的目标区域超过字节码大小时,该指令会将超过的区域填充为零。通过设置超过字节码长度的偏移量和复制长度,用户可以使函数通过零填充大内存区域而不产生适当的 gas 成本。这种操控允许以较低的成本执行代价高昂的内存清空操作,进而可能导致网络资源耗尽和拒绝服务攻击。

桥接状态不匹配

  • Fuel Layer 1(L1)执行器将 包括被 L2 撤销的转发消息。这使得攻击者能够反复进行虚假代币提取。例如,攻击者在 L2 上触发两个代币提取;第一个最终撤销,但仍向 L1 发送消息,而第二个成功。只要 L1 上有足够的代币,攻击者就会获得其最初存入的双倍代币,有效允许攻击者从桥接中窃取资金。

LayerZero 拒绝服务

  • LayerZero V1 是一个跨链协议,允许在区块链之间发送、验证和执行消息。当在源链上发送消息时,离线的 RelayerOracle 服务会收到通知,触发消息真实性的一致性。Oracle 在目标链上提交默克尔根,Relayer 提交证明以执行消息。

    研究文章系列的第一部分 描述了通过消息序列号重叠进行 DoS 攻击的向量,这可能会阻止任何跨链消息的交付。LayerZero 中的消息根据基于 srcChainsrcAddressdstChain 的唯一序列号进行排序,而不考虑 dstAddress。然而,由于应用程序可以选择自己的 RelayerOracle,它们可以伪造来自任何 srcChainsrcAddress 的消息,但无法发送到任何 dstAddress。由于序列号未考虑 dstAddress,攻击者可以发送带有相同序列号的伪造消息,阻止具有相同序列号的合法消息到达目标链。

  • LayerZero 的 OFTs(跨链可替代代币)和 ONFTs(跨链非可替代代币)通过在一个链上锁定代币并在另一个链上铸造,允许 ERC20 和 ERC721 代币的桥接。系列的第三部分 显示了一种攻击向量,当桥接 ERC721 代币时,攻击者可以通过在目标链上使用 onERC721Received 回调来引发 DoS,消耗所有 gas,阻止消息送达并冻结跨链转账。

LayerZero 集成拒绝服务

  • 系列的第二部分 涉及 Stargate,这是一种通过 LayerZero 进行跨链资产转移和交换的协议。在交换代币时,会发送一条消息和有效载荷,使资金接收方能够在收到后执行任意逻辑。如果消息传递失败,将通过 try-catch 优雅处理,错误和有效载荷将被存储,系统将认为消息已交付,确保该过程不会因序列号排序而阻塞后续消息。

    通过编码包含非智能合约的接收者地址的有效载荷,恶意攻击者可以强迫 try-catch 子句在不进入 catch 块的情况下撤销。这将保留即将到来的序列号,同时又无法递送消息,导致消息通道被冻结。

    阻止消息通道的第二种方法是在内存不足的情况下使其撤销。因为 catch 子句会将有效载荷存储在合约存储中,恶意攻击者可以发送一个如此大的有效载荷,以至于在存储时耗尽所有剩余 gas。

  • 类似于在桥接层面撤销的 Stargate 漏洞,以下线程 揭示了 Drips Network 中的拒绝服务可能性:如果消息在目标链的桥接层面无限制地撤销,通信通道将被阻止,并且理想情况下应该有手段从这种状态恢复。不幸的是,没有恢复机制;要在目标链上升级实现,必须成功的跨链消息,但由于上述原因这并不可行。

故障升级

  • 为了批准提款,Ronin 桥需要来自总权重大于特定阈值的验证者集的签名。然而,在从版本 2 升级到版本 4 的过程中,开发团队忽略了调用版本 3 的初始化函数。这个疏漏导致阈值未初始化,默认为 0,使任何人都可以在不提交有效签名的情况下发起提款。结果,桥遭受了 1200 万美元的漏洞攻击。

  • Pike Finance 的协议升级引入了存储错位,错误地将 initialized 变量移动到了不同的槽。这使得攻击者能够重新初始化代理 ,将其升级到恶意实现并最终盗取资金。

未设置的初始化标志

  • Wrapped XETA 合约未能在其 initialize 函数 中设置 initialized 标志,导致攻击者可以重新初始化合约。攻击者授予自己代币铸造权限,利用这些权限抽走了 USDT/WXETA 流动性池的资金。

  • 类似地,NFGSToken 合约在部署时未能 设置 uniswapV2Dele 标志,该标志本应锁定特权的 _uniswapV2Proxy 地址,防止进一步修改。这个疏漏允许攻击者将 _uniswapV2Proxy 指定为他们自己的地址,从而获得对受限操作的访问权限 ,例如铸造无限数量的 NFGS 代币。在初始化时将关键状态变量声明为 immutable,而不是依赖额外的锁定逻辑,将提供更强的保护,防止未经授权的更改。

提议者响应验证不足

其他

  • Munchables 智能合约安全地跟踪用户存款,限制每个用户的提款为其记录的余额。然而,代理管理员 之前在代理后设置了一个未经过验证的恶意实现,将自己分配了 1e24 ETH 的余额。这个膨胀的余额在存储中持续存在,允许管理员通过经过验证的安全实现后续抽走资金。

  • 研究论文 《揭秘和检测以太坊智能合约中的密码缺陷》 提供了对智能合约中密码学问题的分析。作者列出了九个不同类别的缺陷,可以作为安全研究人员的检查清单:

    • 单一和跨合约签名重放

    • 签名前运行和可变性

    • 签名验证不足

    • 默克尔证明重放和前运行

    • 动态长度参数的哈希碰撞

    • 来自哈希链属性的弱随机性

结论

与往年相似,2024 年在有价值的研究、创新的漏洞和创造性的攻击方面都很丰富。重要的是要强调,本文的意图不是批评或指责受影响的项目,而是提供客观的概述,以便作为社区学习的教育材料,从中吸取教训并更好地保护未来的项目。

我是 AI 翻译官,为大家转译优秀英文文章,如有翻译不通的地方,在这里修改,还请包涵~

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
OpenZeppelin
OpenZeppelin
江湖只有他的大名,没有他的介绍。