sherlock-DODO Cross-Chain DEX

参数没有校验,用户可以伪造

ZEVM 合约中的 onCall() 函数接收来自Gatewaytokenamount,但没有验证解码后的 MixSwapParams 是否与这些值一致。

具体来说:

  • params.fromToken 完全由用户传入消息决定

  • params.fromTokenAmount 也是用户可控的

因此攻击者可以:

通过桥接发送一小笔任意代币(如 1 DAI

在消息中伪造一个大额的 swap 请求,比如用合约中持有的大额WZETA来兑换 USDC

实际 swap 时使用的是合约自己持有的 WZETA

攻击者获得兑换后的 USDC,而合约的 WZETA 被盗

这会导致攻击者可以完全清空合约中持有的高价值代币。

参数没有校验

如果发生转移的是原生代币,实际上是没有校验数量是不是准确的

function withdrawToNativeChain(
address zrc20,
uint256 amount,
bytes calldata message
) external payable {
if(zrc20 != _ETH_ADDRESS_) {
require(IZRC20(zrc20).transferFrom(msg.sender, address(this), amount), "INSUFFICIENT ALLOWANCE: TRANSFER FROM FAILED");
}
// 🚨这里缺失了关键校验🚨
// require(msg.value >= amount, "INSUFFICIENT NATIVE TOKEN");
}

攻击者可以假装提取原生币(ETH),从而绕过代币的 transferFrom 校验,但实际指向真实的 ZRC20 合约(如 USDC.ZRC20)来偷偷提走这些代币!

地址不要设置为零

比如代币转回的时候,接受地址不能设置为零,在看到地址为零的地方,需要查看它接受的代币是什么,如果被转到零地址,那么就会出现代币进入黑底洞

攻击者获得退款信息后,可以提前构造一个相同的退款交易覆盖用户的退款

攻击者发现,某合法用户(称为“受害者”)的跨链交易失败了,退款信息 refundInfo[VICTIM_EXTERNAL_ID] 已经存在,里面有受害者的钱,比如 1000 个 zUSDC。

攻击者自己发起一笔跨链交易,故意设计让它失败(比如调用一个总是会 revert 的合约),以便触发 onRevert

这笔攻击交易构造了一个恶意的 RevertOptions,里面的 revertMessage 包含了受害者的 externalId 和攻击者自己的钱包地址。

当这笔攻击交易触发 onRevert 时:

  • 合约拿到 revertMessage,用它去写 refundInfo[externalId]。

  • 由于没有检查,直接用攻击者的地址覆盖了受害者对应的退款信息。

这样,原本属于受害者的钱被“绑架”到了攻击者的钱包地址。

受害者尝试退款时,发现退款的钱已经不在自己的地址了,资金被攻击者劫持。

验证逻辑错误

function claimRefund(bytes32 externalId) external {
RefundInfo storage refundInfo = refundInfos[externalId];

address receiver = msg.sender; // 默认是调用者本人
if (refundInfo.walletAddress.length == 20) {
receiver = address(uint160(bytes20(refundInfo.walletAddress)));
}
require(bots[msg.sender] || msg.sender == receiver, "INVALID_CALLER");
// 执行转账逻辑
}

只要if条件不满足,就会直接进入require语句,那么就会直接变为ture,任何人都可以调用

USDT不允许从非零额度授权到非零额度

USDTapprove 函数设计有别于标准 ERC20

  • 如果当前授权额度(allowance)大于 0,且调用方试图设置一个非零的新授权额度,approve 会直接失败(revert)。

  • 这是 USDT 特殊的安全设计,避免某些风险,但与大多数 ERC20 标准实现不同。

区分原生代币和ERC20代币

如果一个合约中出现这实现了原生代币和erc20代币,那么就要考虑到不要混淆实现逻辑,或者忘记实现原生代币的

无法兼容 USDT 等非标准 ERC20 代币

协议本应支持 ZetaChain 所支持的资产代币,但在实际运行中,它无法兼容如 USDT 这类非标准 ERC20 代币,即使它们已被加入支持列表中。

// 第 238-241 行
require(
IERC20(fromToken).transferFrom(msg.sender, address(this), amount),
"INSUFFICIENT AMOUNT: ERC20 TRANSFER FROM FAILED"
);

// 第 316-319 行
require(
IERC20(fromToken).transferFrom(msg.sender, address(this), amount),
"INSUFFICIENT AMOUNT: ERC20 TRANSFER FROM FAILED"
);

在 depositAndCall 函数中,协议调用 IERC20.transferFrom 试图将用户的代币转入合约中。但部分非标准 ERC20 代币(比如 USDT)在实现 transfer 或 transferFrom 方法时,没有返回布尔值(bool),违反了 ERC20 的标准。

由于 require(…) 表达式期望该调用返回 true,而 USDT 的调用会直接返回空值(无返回),因此 require 判断为 false,从而导致整个交易失败。

池子创造没有进行检查条件,

这个漏洞我发现了,但是由于影响写错了,不符合它们给的规定,下次一定要写规整点,这个漏洞错过太可惜了