前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Meter Bridge && Qubit Bridge

Meter Bridge && Qubit Bridge

作者头像
Tiny熊
发布2022-04-08 14:02:54
6130
发布2022-04-08 14:02:54
举报
文章被收录于专栏:深入浅出区块链技术

本文作者:bixia1994[1]

💡 Meter bridge

交易 hash[2]

参考链接:

chainbridge-solidity-v1.0.0-eth/deployed_0421/merged at master · meterio/chainbridge-solidity-v1.0.0-eth[3]

Breaking down the Meter hack[4]

错误原因:

产生错误的根本原因是:meter 中针对 deposit 和 depositETH,emit 了相同的事件。但是在 depositETH 中,将 ETH 包装成 WETH 后马上转给了 handler,导致与 deposit 方法里对于 ERC20 的处理方式不一致,从而使得 handler 里面针对 depositETH 进行特殊处理。

即:跨链桥的逻辑应该是

用户 → 桥 deposit → Handler: transferFrom(burn/lock) → emit Deposit

用户 → 桥 depositETH: transfer WETH→ Handler (do nothing) → emit Deposit

用户 → 桥 deposit → Handler: do nothing

根本逻辑错误在于:handler 里 if (tokenAddress != _wtokenAddress) 导致

代码语言:javascript
复制
Handler:
function deposit(
        bytes32 resourceID,
        uint8   destinationChainID,
        uint64  depositNonce,
        address depositer,
        bytes   calldata data
    ) external override onlyBridge {
        bytes   memory recipientAddress;
        uint256        amount;
        uint256        lenRecipientAddress;

        assembly {

            amount := calldataload(0xC4)

            recipientAddress := mload(0x40)
            lenRecipientAddress := calldataload(0xE4)
            mstore(0x40, add(0x20, add(recipientAddress, lenRecipientAddress)))

            calldatacopy(
                recipientAddress, // copy to destinationRecipientAddress
                0xE4, // copy from calldata @ 0x104
                sub(calldatasize(), 0xE) // copy size (calldatasize - 0x104)
            )
        }

        address tokenAddress = _resourceIDToTokenContractAddress[resourceID];
        require(_contractWhitelist[tokenAddress], "provided tokenAddress is not whitelisted");

        // ether case, the weth already in handler, do nothing
        if (tokenAddress != _wtokenAddress) {
            if (_burnList[tokenAddress]) {
                burnERC20(tokenAddress, depositer, amount);
            } else {
                lockERC20(tokenAddress, depositer, address(this), amount);
            }
        }

        _depositRecords[destinationChainID][depositNonce] = DepositRecord(
            tokenAddress,
            uint8(lenRecipientAddress),
            destinationChainID,
            resourceID,
            recipientAddress,
            depositer,
            amount
        );
    }

当用户的传入的调用参数如下时:

代码语言:javascript
复制
Function: deposit(uint8 destinationChainID, bytes32 resourceID, bytes data)

MethodID: 0x05e2ca17
 //destinationChainID
 //resourceID
 //offset
 //len
 //amount
 //addr len 0x14=20
 //receipient addr

首先在 Handler 中(0xde4fC7C3C5E7bE3F16506FcC790a8D93f8Ca0b40),根据 wtokenAddress 查找到对应的 resouceID:

然后构造上述的一个交易数据即可。

💡 Qubit

参考链接:https://twitter.com/peckshield/status/1486841239450255362[5]

tx[6]

tx2[7]

错误原因:

用户 →Bridge: deposit (resourceID → ETH) → Handler: deposit (tokenAddress = 0)

当 handler 中,deposit tokenAddr=0, 其调用 safeTransferFrom 时,其会直接调用 STOP,返回 true,而不是 revert 或者 false。

即:

代码语言:javascript
复制
function safeTransfer(
        address token,
        address to,
        uint value
    ) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "!safeTransfer");
    }
当token不是一个合约地址时,比如一个EOA地址,其调用的call仍然会成功,返回success!
代码语言:javascript
复制
Bridge:
function deposit(uint8 destinationDomainID, bytes32 resourceID, bytes calldata data) external payable notPaused {
        require(msg.value == fee, "QBridge: invalid fee");

        address handler = resourceIDToHandlerAddress[resourceID];
        require(handler != address(0), "QBridge: invalid resourceID");

        uint64 depositNonce = ++_depositCounts[destinationDomainID];

        IQBridgeHandler(handler).deposit(resourceID, msg.sender, data);
        emit Deposit(destinationDomainID, resourceID, depositNonce, msg.sender, data);
    }

    function depositETH(uint8 destinationDomainID, bytes32 resourceID, bytes calldata data) external payable notPaused {
        uint option;
        uint amount;
        (option, amount) = abi.decode(data, (uint, uint));

        require(msg.value == amount.add(fee), "QBridge: invalid fee");

        address handler = resourceIDToHandlerAddress[resourceID];
        require(handler != address(0), "QBridge: invalid resourceID");

        uint64 depositNonce = ++_depositCounts[destinationDomainID];

        IQBridgeHandler(handler).depositETH{value:amount}(resourceID, msg.sender, data);
        emit Deposit(destinationDomainID, resourceID, depositNonce, msg.sender, data);
    }
Handler:
function deposit(bytes32 resourceID, address depositer, bytes calldata data) external override onlyBridge {
        uint option;
        uint amount;
        (option, amount) = abi.decode(data, (uint, uint));

        address tokenAddress = resourceIDToTokenContractAddress[resourceID];
        require(contractWhitelist[tokenAddress], "provided tokenAddress is not whitelisted");

        if (burnList[tokenAddress]) {
            require(amount >= withdrawalFees[resourceID], "less than withdrawal fee");
            QBridgeToken(tokenAddress).burnFrom(depositer, amount);
        } else {
            require(amount >= minAmounts[resourceID][option], "less than minimum amount");
            tokenAddress.safeTransferFrom(depositer, address(this), amount);
        }
    }

    function depositETH(bytes32 resourceID, address depositer, bytes calldata data) external payable override onlyBridge {
        uint option;
        uint amount;
        (option, amount) = abi.decode(data, (uint, uint));
        require(amount == msg.value);

        address tokenAddress = resourceIDToTokenContractAddress[resourceID];
        require(contractWhitelist[tokenAddress], "provided tokenAddress is not whitelisted");

        require(amount >= minAmounts[resourceID][option], "less than minimum amount");
    }

https://etherscan.io/tx/0x3dfa33b5c6150bf3d64f49cb97eba351f99e4dff7119ef458e40f51160bf77ec/advanced[8]

攻击者的调用参数为:

代码语言:javascript
复制
Function: deposit(uint8 destinationDomainID, bytes32 resourceID, bytes data)

MethodID: 0x05e2ca17
 //destination
 //resource
 //offset
 //len
 //option
 //amount
 //receipient

quibit 被盗的根本原因其实在于:

他没有使用 Openzeppelin 的 safeERC20 合约,而是自己实现了一个版本的 safeERC20. 但是在它自己实现的 safeERC20 合约里面的 safeTransferFrom 方法里有 bug,没有检查 token 必须是合约地址,而不是 EOA。

在 Openzeppelin 则做了相应的检查。

代码语言:javascript
复制
function safeTransferFrom(
        address token,
        address from,
        address to,
        uint value
    ) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "!safeTransferFrom");
    }
代码语言:javascript
复制
function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.call{ value: value }(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

参考资料

[1]bixia1994: https://learnblockchain.cn/people/3295

[2]交易hash: https://etherscan.io/tx/0x2d3987963b77159cfe4f820532d729b0364c7f05511f23547765c75b110b629c

[3]chainbridge-solidity-v1.0.0-eth/deployed_0421/merged at master · meterio/chainbridge-solidity-v1.0.0-eth: https://github.com/meterio/chainbridge-solidity-v1.0.0-eth/tree/master/deployed_0421/merged

[4]Breaking down the Meter hack: https://medium.com/chainsafe-systems/breaking-down-the-meter-io-hack-a46a389e7ae4

[5]https://twitter.com/peckshield/status/1486841239450255362: https://twitter.com/peckshield/status/1486841239450255362

[6]tx: https://etherscan.io/address/0x80d1486ef600cc56d4df9ed33baf53c60d5a629b#code

[7]tx2: https://etherscan.io/address/0x99309d2e7265528dc7c3067004cc4a90d37b7cc3#code

[8]https://etherscan.io/tx/0x3dfa33b5c6150bf3d64f49cb97eba351f99e4dff7119ef458e40f51160bf77ec/advanced: https://etherscan.io/tx/0x3dfa33b5c6150bf3d64f49cb97eba351f99e4dff7119ef458e40f51160bf77ec/advanced

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-02-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档