ERC2771 Multicall任意地址欺骗攻击分析
字数 1655 2025-08-10 08:28:24
ERC2771 Multicall任意地址欺骗攻击分析教学文档
1. 漏洞概述
ERC2771与Multicall标准的不安全集成导致了一个严重的任意地址欺骗漏洞,攻击者可以利用该漏洞伪造交易发送者地址,从而未经授权地操作合约功能。该漏洞影响了多个ERC20代币合约,导致大量资金被盗。
2. 漏洞背景
2.1 相关标准介绍
ERC2771标准:用于实现原生元交易(Meta Transaction)的协议,允许用户使用签名而非ETH支付gas费用。关键特性是重写了_msgSender()函数,可以从calldata中提取实际交易发起者。
Multicall标准:允许在单个交易中批量执行多个函数调用,提高效率并减少gas成本。
2.2 漏洞产生原因
当合约同时实现ERC2771和Multicall标准时,攻击者可以构造特殊的calldata,通过Multicall的delegatecall机制绕过ERC2771的签名验证,伪造_msgSender()返回值。
3. 攻击案例分析
3.1 受影响代币
- ETH主网:HXA、WFCA、TIME、NAME等
- Polygon链:Swop等
3.2 典型攻击流程
以TIME代币为例:
- 攻击者部署MEV bot合约(攻击合约)
- 攻击合约用5ETH兑换WETH
- 用WETH兑换345,539,9346个TIME代币
- 构造恶意calldata调用代币合约的multicall函数
- multicall delegatecall执行代币的burn函数,燃烧池子地址中的62,227,259,510个TIME
- 代币价格因供应量减少而上涨
- 攻击者卖出TIME获利约94ETH
4. 技术原理分析
4.1 关键代码分析
Forward.sol中的execute函数:
function execute(ForwardRequest calldata req, bytes calldata signature) public payable returns (bool, bytes memory) {
require(verify(req, signature), "MinimalForwarder: signature does not match request");
_nonces[req.from] = req.nonce + 1;
(bool success, bytes memory result) = req.to.call{ gas: req.gas, value: req.value }(
abi.encodePacked(req.data, req.from)
);
攻击者构造的恶意calldata示例:
0xac9650d8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003842966c680000000000000000000000000000000000000000c9112ec16d958e8da8180000760dc1e043d99394a10605b2fa08f123d60faf840000000000000000
MulticallUpgradeable.sol:
function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
results[i] = _functionDelegateCall(address(this), data[i]);
}
return results;
}
function _functionDelegateCall(address target, bytes memory data) private returns (bytes memory) {
require(AddressUpgradeable.isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return AddressUpgradeable.verifyCallResult(success, returndata, "Address: low-level delegate call failed");
}
ERC2771的_msgSender()实现:
function _msgSender() internal view returns (address payable signer) {
signer = msg.sender;
if (msg.data.length>=20 && isTrustedForwarder(signer)) {
assembly {
signer := shr(96,calldataload(sub(calldatasize(),20)))
}
}
}
4.2 漏洞利用原理
- 攻击者构造特殊的calldata,使Multicall执行delegatecall时,EVM根据偏移量截取特定部分作为函数参数
- 截取的部分包含burn函数签名和伪造的调用者地址(通常是流动性池地址)
- 由于delegatecall保留了原始调用的上下文,ERC2771的_msgSender()会从calldata末尾提取伪造的地址
- 合约误认为调用来自合法地址(如流动性池),执行了本应受限的操作(如燃烧代币)
5. 漏洞复现方法
5.1 复现环境
- 使用测试网或fork主网环境
- 部署攻击合约(参考已公开的攻击合约)
- 准备目标代币合约(需同时实现ERC2771和Multicall)
5.2 复现步骤
- 部署攻击合约(MEV bot)
- 准备初始资金(如兑换WETH)
- 购买目标代币
- 构造恶意calldata调用multicall函数
- 观察代币燃烧效果
- 出售代币获利
6. 防御措施
6.1 合约开发者
- 避免同时使用ERC2771和Multicall:两种标准的安全模型存在根本冲突
- 使用OpenZeppelin的修复方案:他们发布了专门解决此问题的补丁
- 实现严格的权限控制:即使_msgSender()被伪造,关键操作仍需多重验证
- 使用msg.sender而非_msgSender():对于敏感操作,直接使用原始发送者
6.2 项目方应急方案
- 暂停受影响合约
- 升级合约实现
- 迁移资金到安全合约
- 通知交易所暂停交易受影响代币
7. 相关资源
8. 总结
ERC2771与Multicall的组合漏洞展示了标准集成时可能产生的意外安全问题。此事件强调了:
- 标准组合需谨慎评估安全影响
- 元交易实现需要特别关注身份验证
- 合约升级需全面测试兼容性
- 安全监控和应急响应机制的重要性