0xCC5fDA5e3cA925bd0bb428C8b2669496eE43067e
漏洞原因:
合约0xCC5fDA5e3cA925bd0bb428C8b2669496eE43067e存在一个performAction函数,函数可以用于任意两个代币的swap交换,代码如下:
function performAction(
address fromToken,
address toToken,
uint256 amount,
address receiverAddress,
bytes32 metadata,
bytes calldata swapExtraData
) external payable override returns (uint256) {
uint256 _initialBalanceTokenOut;
uint256 _finalBalanceTokenOut;
// Swap Native to Wrapped Token
if (fromToken == NATIVE_TOKEN_ADDRESS) {
_initialBalanceTokenOut = ERC20(toToken).balanceOf(socketGateway);
(bool success, ) = toToken.call{value: amount}(swapExtraData);
if (!success) {
revert SwapFailed();
}
_finalBalanceTokenOut = ERC20(toToken).balanceOf(socketGateway);
require(
(_finalBalanceTokenOut - _initialBalanceTokenOut) == amount,
"Invalid wrapper contract"
);
// Send weth to user
ERC20(toToken).transfer(receiverAddress, amount);
} else {
_initialBalanceTokenOut = address(socketGateway).balance;
// Swap Wrapped Token To Native Token
ERC20(fromToken).safeTransferFrom(
msg.sender,
socketGateway,
amount
);
(bool success, ) = fromToken.call(swapExtraData);
if (!success) {
revert SwapFailed();
}
_finalBalanceTokenOut = address(socketGateway).balance;
require(
(_finalBalanceTokenOut - _initialBalanceTokenOut) == amount,
"Invalid wrapper contract"
);
// send ETH to the user
payable(receiverAddress).transfer(amount);
}
emit SocketSwapTokens(
fromToken,
toToken,
amount,
amount,
Identifier,
receiverAddress,
metadata
);
return amount;
}
可以看到这一行通过call理论上可以调用toToken的函数,切toToken地址由用户传入,可控:
(bool success, ) = toToken.call{value: amount}(swapExtraData);
因此,当toToken为任意ERC20代币时,可以调用原生transferFrom方法,将用户approve给该合约的代币进行转移。只需要swapExtraData为abi-encode的transferFrom(address, address, uint256)并带入参数即可。
例如攻击者的这次调用:

其swapExtraData数据为:编码结果如下:
0x23b872dd00000000000000000000000038d2ca742b90ebd21dcceceb02ff6fd3d233b21e00000000000000000000000050df5a2217588772471b84adbbe4194a2ed39066000000000000000000000000000000000000000000000000000000407c809af0

同时,代码中的以下部分理论上对调用前后是否有代币转移作了限制:通过比较_finalBalanceTokenOut和_initialBalanceTokenOut值完成,但是很容易在amount=0时被绕过。
_initialBalanceTokenOut = ERC20(toToken).balanceOf(socketGateway);
(bool success, ) = toToken.call{value: amount}(swapExtraData);
if (!success) {
revert SwapFailed();
}
_finalBalanceTokenOut = ERC20(toToken).balanceOf(socketGateway);
require(
(_finalBalanceTokenOut - _initialBalanceTokenOut) == amount,
"Invalid wrapper contract"
);
