臭名昭著的漏洞文摘 #2

本文是The Notorious Bug Digest 2,总结了近期Web3的多个安全漏洞和事件。讨论了利用手续费代币的交易滑点漏洞进行三明治攻击、zkLend协议由于向下舍入错误被攻击事件,并分析了攻击者如何利用未经验证的合约以及智能合约中不安全的访问控制、不足的输入验证或未初始化的状态漏洞获利。此外,还分享了Stellar和Uniswap Hooks库在代码审计中发现的问题。

合作者:Ionut-Viorel Gingu和Victor Xie

介绍

欢迎来到The Notorious Bug Digest #2——一份精选的关于近期Web3漏洞和安全事件的见解汇编。当我们的安全研究员们没有深入进行审计时,他们会花时间了解最新的安全领域,分析审计报告,并剖析链上事件。我们相信这些知识对更广泛的安全社区来说是宝贵的,为研究人员提供了一个磨练技能的资源,并帮助新手了解Web3安全的世界。加入我们,一起探索这批漏洞!事件分析

用转移费来防御三明治攻击?这次不行

2月7日,地址为0x2d70d62的合约受到攻击 [1] [2],因为它的depositBNB函数在用WBNB购买ADAcash代币时没有采用滑点保护。

img-1

然而,这次攻击并非如此简单。ADAcash是一种转移时收费的代币(fee-on-transfer token),每次转移都要向代币合约收取15%的费用。因此,如果攻击者只是用闪电贷对depositBNB函数进行三明治攻击,那么合并后的费用(购买时15%,出售时15%)很容易吞噬掉所有利润。

img-2 (1)

那么,ADAcash收取的这些转移费会发生什么呢?该合约会交换它们,但不幸的是,没有任何滑点保护。根据代码,当from地址不是AMM(例如出售ADAcash时),并且收集了足够的费用时,大部分收集到的费用会被交换为ADA,而大约13%会被转换为WBNB,以提高ADAcash/WBNB池的流动性。

img-3

这就是聪明的地方:攻击者可以通过对ADAcash代币合约本身发起第二次三明治攻击来追回这些费用。以下是具体的步骤:

  • 获取WBNB的闪电贷。
  • WBNB购买ADAcash,推高其价格(15%的ADAcash变成费用)。
  • WBNB购买ADA,推高ADA的价格。
  • 在合约0x2d70d62上触发depositBNB函数。这是第一次三明治交换,进一步推高了ADAcash的价格。
  • 出售ADAcash,从第一次三明治交换中获利。请注意,出售ADAcash也会触发15%的费用。为了最大限度地减少费用,攻击者将出售操作分成多次交换,因为每次出售都会触发使用所有累积费用的交换。这些出售构成了第二次三明治交换,提高了ADA的价格。
  • 出售ADA,锁定第二次交换的利润。
  • 偿还WBNB闪电贷并确保利润。

这个故事的寓意是什么?确保你始终考虑并为滑点保护设置合理的值。否则,交换可能会以复杂、意想不到的方式被三明治攻击。

来自 zkLend 黑客攻击的教训

2月12日,由于withdraw函数中的向下舍入错误,zkLend借贷协议遭到黑客攻击。SlowMist发布了 一个彻底的分析 。以下是它的运作方式:

  • 空白市场设置:攻击者存入了1 wei的资产,并以1:1的比例获得了1个lending_accumulator份额。
  • 放大 lending_accumulator:使用闪电贷,他们偿还额外的费用以增加 lending_accumulator
  • 受害者存入了资产:其他用户存入了资产。
  • 重复利用:攻击者不断存入资产并提取大于已存入的金额。由于大的 lending_accumulator 以及从舍入燃烧数量而产生的相应的精度损失,每次提款操作燃烧与存款操作相同数量的份额。存入和提取的资产之间的这种差异导致攻击者获利。

这种利用模式并非一次性的,zkLend中使用的方法与其他借贷协议的利用类似:

  • Radiant Capital:袭击了一个空的USDC市场。
  • Silo Finance:提高了空市场的利用率。
  • Raft Finance:通过清算引爆了一个舍入错误。
  • Wise Finance:一个关于空市场的狡猾的利用。它有利于用户,但是攻击者使用了一个秘密的捐赠来膨胀份额,然后将其翻转以在市场上制造不良债务。

审计师的经验教训

只有在可以放大的情况下,舍入误差才会耗尽协议。如果没有通货膨胀,你每次提款只能提取1 wei——远低于gas费用。

为什么是借贷协议?空市场或金库是膨胀舍入误差的主要目标。这些攻击就像类固醇上的金库膨胀漏洞。在大多数设置中,空的金库只是坑了第一个存款人。但是在借贷协议中,空的市场允许你从其他市场借款或猛拉抵押品,从而耗尽整个系统。

发现这个错误的一个关键问题是“攻击者可以在空市场中膨胀哪些变量?”这些不仅仅是舍入错误——它们是舍入 + 膨胀攻击。以上两个漏洞甚至没有使用错误的舍入方向:它们都依赖于膨胀空市场中的会计变量。

对于zkLend,审计员可以将lending_accumulator标记为可膨胀的。将其与有利于用户的舍入配对,你就得到了耗尽的秘诀。检查舍入方向将显得至关重要。

这个问题也挖掘出了非舍入错误。考虑一下Silo Finance错误——它使用了捐赠来膨胀空金库中的变量,不需要舍入技巧。在Raft Finance漏洞中,已经报告了一个有利于用户的舍入错误,并被报告为一个低严重性问题,并且在修复审查中没有得到解决。进一步分析错误如何膨胀可能已经发现了关键并避免了黑客攻击。

未验证合约中的漏洞

本月,由于薄弱的访问控制、不充分的输入验证或未初始化的状态,多个未验证的智能合约遭到入侵。以下是事件的分解。

合约0xeffca1:通过未检查的回退调用损失了20 ETH [1] [2]

下图高亮显示了一个关键问题:访问控制不足。受害者,一个MEV机器人,未能验证其uniswapV2函数的调用者。但是,对反编译代码和msg.data的更深入分析揭示了更多细节:

  • 不存在 uniswapV2 函数——该调用反而命中了 Bot 的回退逻辑。
  • delegatecall 目标 (0xcc85e)、参数(例如,对不存在的 0x24ec8approve 调用)以及后续的 WETH 转移都由 msg.data 控制。
  • 值得注意的是,delegatecall 目标在漏洞利用前几个小时被 列入白名单 ——这是一项特权操作。

这引出了一个问题:这次漏洞利用是自动化的吗?一个熟练的攻击者本可以绕过0x24ec8,直接批准WETH,并通过swapIn回退来耗尽它。鉴于反编译代码的复杂性,黑客可能已经改变了历史msg.data,注入了他们的地址、资产详细信息和有利可图的返回值。这仍然是推测性的,但并非不可能。

img 4合约0x378c6:通过假池交换窃取了151个BNB [1] [2]

此漏洞利用非常简单。如图所示,0xb9d384fa函数缺乏访问控制,从而可以在未验证的Uniswap V3池上进行代币交换。攻击者创建了一个具有最小WBNB和大量虚假代币的池,然后触发0xb9d384fa以将WBNB交换为毫无价值的代币。他们随后提取了流动性,从而以WBNB获利。一个关键的细节:滑点保护参数sqrtPriceLimitX96(不是攻击者控制的)设置为MIN_SQRT_RATIO,从而确保了交换以低WBNB价格成功。

img-5合约0xd4f1a:由于未初始化的状态,耗尽了23个BNB [1] [2]

这种情况同样简单明了。受害者是一个可初始化的合约,在其部署期间没有调用_disableInitializersinitialize,使其保持未初始化状态。在积累了两天的费用后,攻击者调用initialize来声明所有权,并调用withdrawFees来转移资金。有趣的是,漏洞利用后的费用仍在流入,攻击者将其重定向到Tornado.cash

审计问题

Stellar Contracts Library - 属性宏会忽略后续属性

这个bug是在我们的Rust审计中发现的。它强调了过程宏如何在Rust中工作和被定义。过程宏是一个 编译时代码生成工具:当编译器看到 function doAction 已经被 macro 注释时,它会查找 macro 的定义,并据此添加、删除或替换 doAction 函数中的代码。

这可以作为开发像Solidity的修饰符这样的功能的强大工具。我们可以创建一个when_not_paused过程宏,当写在一个函数上面时,它会复制函数代码并在开头添加额外的逻辑。如果paused变量设置为true,则额外的逻辑将恢复。

类似地,客户端打算创建一个when_not_paused过程宏。这个宏接收作为参数的函数代码(连同所有注释)解析为 tokens,之后可以通过以下方式访问:

  • fn_vis (函数可见性: 例如 pub)
  • fn_sig (函数签名: 例如 fun doAction(number: u32) -> u32)
  • fn_block (函数体: 例如 return number;)

在编译时,编译器复制函数可见性、函数签名、函数体,并在函数的开头添加一个openzeppelin_pausable::when_paused(#env_arg);调用,如下所示:

img-6 (1)

少了什么。但是什么呢?其余的注释! 如果我们的函数也用其他过程宏(比如 only_owner)注释,那么 when_not_paused 宏会改变代码,使得 only_owner 在这个过程中丢失。 解决此问题的方法是 也复制其他注释,结果如下:

img-7 (1)


Uniswap Hooks - 非显式的多池支持允许覆盖 Hook 状态

在审计 Uniswap Hooks 库期间,我们发现了BaseCustomAccounting hook中的一个关键漏洞。此Hook旨在完全控制其关联池中的流动性,确保所有流动性修改都通过它。因此,它必须永久链接到单个池。否则,流动性可能无法访问。

但是,由于池-hook意识中的不对称,除非hook本身明确拒绝,否则任何有效的hook地址都可以附加到任何池。BaseCustomAccounting hook 缺乏针对重新初始化的保护措施,允许攻击者将其从受害者的池重定向到恶意创建的 Uniswap 池。 这实际上锁定了受害者池的流动性,阻止了对资金的访问。

修复 确保一旦定向到一个池,继承 BaseCustomAccounting 的 hook 将无法重新初始化并指向另一个池。


重要的是要强调,这些内容的意图不是批评或指责受影响的项目,而是提供客观的概述,作为社区学习和更好地保护项目免受未来侵害的教育材料。

  • 原文链接: openzeppelin.com/news/th...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

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