摘要
2023 年 12 月 5 日,Web3 开发平台 Thirdweb 报告了其预建智能合约中的安全漏洞。该漏洞影响了使用这些合约部署的所有ERC20、ERC721和ERC1155代币。在接下来的几天里,使用存在漏洞的合约部署的代币逐渐被攻击利用。
漏洞分析
ERC-2771
本 EIP 定义了通过受信任的 "转发者 "合约接受 "元交易 "的 "收件人 "合约的合约级协议。协议未作任何更改。接收方合约通过附加额外的 calldata 发送有效的 msg.sender (称为 _msgSender())和 msg.data (称为 _msgData())。
在实践中,OpenZeppelin 的实现 ERC2771Context 被广泛使用。具体来说,来自可信转发器的最后 20 字节 calldata 被视为 _msgSender()。对于普通库用户来说,似乎只需将 msg.sender 的所有用法替换为 _msgSender()。
语言=javascript 函数 _msgSender() 内部视图 virtual override 返回 (address) { uint256 calldataLength = msg.data.length; uint256 contextSuffixLength = _contextSuffixLength(); if (isTrustedForwarder(msg.sender) && calldataLength >= contextSuffixLength) { return address(bytes20(msg.data[calldataLength - contextSuffixLength:])); } else { return super._msgSender(); } }
function _msgData() 内部视图 virtual override returns (bytes calldata) { uint256 calldataLength = msg.data.length; uint256 contextSuffixLength = _contextSuffixLength(); if (isTrustedForwarder(msg.sender) && calldataLength >= contextSuffixLength) { 返回 msg.data[:calldataLength-contextSuffixLength]; } else { return super._msgData(); } }
### 多方调用
用户可利用多呼叫功能将多个呼叫整合为一个呼叫。多个呼叫的 calldatas 都是从单个呼叫的 calldata 中提取的。 在实践中,OpenZeppelin 的 `MulticallUpgradeable` 被广泛使用。(该错误现已修复)
函数 multicall(bytes[] calldata data) 外部虚拟返回 (bytes[] memory results) { results = new bytes; for (uint256 i = 0; i < data.length; i++) { results[i] = _functionDelegateCall(address(this), data[i]); } return results; }
### ERC-2771 + Multicall
问题产生的原因是这两个组件(ERC-2771 和 Multicall)之间打包和拆包 calldata 的方式不一致。根据 ERC-2771,可信转发器应将消息数据和发件人信息打包在一起。然后,合约使用 `_msgData()`和 `_msgSender()`分别解包消息数据和发件人信息。
但是,多调用函数与 ERC-2771 打包数据的方式不兼容。如果实现得当,"Multicall "应该使用"_msgSender() "来解压缩发件人信息,并将其附加到每个消息的调用数据中,以便在后续调用中正确解压缩。但在实际执行中却漏掉了这一步。
同时,遵循 ERC-2771 的合约会尝试使用 `_msgData()`和 `_msgSender()`来解包消息数据和发件人信息。但是,如果不将发件人信息附加到 calldata 中,发件人信息就会作为 `_msgData()`的最后 20 个字节解包,而攻击者可以控制这些字节。这样,攻击者就可以利用任意的发件人信息构建经过操纵的 calldata,执行恶意逻辑,从而违反规范设定的预期。
## 攻击分析
让我们以[攻击事务的一个实例](https://app.blocksec.com/explorer/tx/eth/0xecdd111a60debfadc6533de30fb7f55dc5ceed01dfadd30e4a7ebdb416d2f6b6)为例。
* 第 1 步:用 5 $WETH 交换 3,455,399,346 $TIME。
* 第 2 步:用精心制作的数据调用可信转发器,经多路调用解析后,以 Uniswap Pool 作为 `_msgSender()`,调用 burn 函数。池中的 $TIME 余额会减少。
* 第 3 步:同步池,提升 $TIME 的价格。
* 第 4 步:用 3,455,399,346 美元 TIME 交换 94 美元 WETH。
第 2 步是攻击的关键。攻击者调用 Forwarder.execute 转发数据:

然而,它被解析为长度为 1 的字节[],然后被用作调用合约的数据。

## 突出显示和课程
在 DeFi 领域,关注第三方库的安全性至关重要。不幸的是,这些库有时会以意想不到的隐蔽方式相互影响,从而对合同的安全性构成威胁。这就凸显了全面审计和监控的重要性,以减少此类交互可能产生的任何潜在漏洞。
## 阅读本系列的其他文章:
- 前言:2023 年十大 "可怕 "安全事件](https://blocksec.com/blog/top-ten-awesome-security-incidents-in-2023)
- #1: 利用 Flashbots Relay 中的漏洞收获 MEV 机器人](https://blocksec.com/blog/harvesting-mev-bots-by-exploiting-vulnerabilities-in-flashbots-relay)
- [#2: Euler Finance 事件:2023 年最大的黑客攻击](https://blocksec.com/blog/euler-finance-incident-the-largest-hack-of-2023)
- [#3: KyberSwap 事件:巧妙利用四舍五入错误的超精细计算](https://blocksec.com/blog/kyberswap-incident-masterful-exploitation-of-rounding-errors-with-exceedingly-subtle-calculations)
- [#4: Curve 事件:编译器错误从无辜源代码中产生错误字节码](https://blocksec.com/blog/curve-incident-compiler-error-produces-faulty-bytecode-from-innocent-source-code)
- [#5: 鸭嘴兽金融:幸运地躲过三次攻击](https://blocksec.com/blog/5-platypus-finance-surviving-three-attacks-with-a-stroke-of-luck)
- [#6: 百家金融事件:催化易受攻击的分叉协议中的精准相关漏洞利用浪潮](https://blocksec.com/blog/6-hundred-finance-incident-catalyzing-the-wave-of-precision-related-exploits-in-vulnerable-forked-protocols)
- [#7: ParaSpace 事件:与时间赛跑,挫败迄今为止业界最严重的攻击](https://blocksec.com/blog/paraspace-incident-a-race-against-time-to-thwart-the-industrys-most-critical-attack-yet)
- [#8: SushiSwap 事件:一次笨拙的救援尝试引发了一系列模仿攻击](https://blocksec.com/blog/8-sushi-swap-incident-a-clumsy-rescue-attempt-leads-to-a-series-of-copycat-attacks)
- [#9: MEV Bot 0xd61492:从捕食者到猎物的巧妙利用](https://blocksec.com/blog/9-mev-bot-0xd61492-from-predator-to-prey-in-an-ingenious-exploit)



