Socket 业务逻辑漏洞

01/12/2024 socket gateway合约遭受攻击,漏洞合约地址:

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)并带入参数即可。

例如攻击者的这次调用:

https://phalcon.blocksec.com/explorer/tx/eth/0xc6c3331fa8c2d30e1ef208424c08c039a89e510df2fb6ae31e5aa40722e28fd6?line=22&debugLine=22

post image

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

0x23b872dd00000000000000000000000038d2ca742b90ebd21dcceceb02ff6fd3d233b21e00000000000000000000000050df5a2217588772471b84adbbe4194a2ed39066000000000000000000000000000000000000000000000000000000407c809af0

post image

同时,代码中的以下部分理论上对调用前后是否有代币转移作了限制:通过比较_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"
            );