# JAY Reentry攻击分析 **Published by:** [Fuzzingq](https://paragraph.com/@liusy/) **Published on:** 2023-11-25 **URL:** https://paragraph.com/@liusy/jay-reentry ## Content 2022年12月9日,ETH链上Jay合约遭受重入攻击,代币合约地址及代码: https://etherscan.io/address/0xf2919d1d80aff2940274014bef534f7791906ff2 问题的原因出在合约中的buyJayWithERC721()函数,该合约中调用了未检测的外部合约函数并且未检测balance状态的修改 先看一下jay代币的定价方式,池子中有一定数量的jay和eth。假设为totalsupplyJay和reserveEth。当用户需要用AmountEthIn的数量购买jay的时候,用户可以买到的jay数量为: $$AmountJayOut = AmountEthIn/reserveEth * totalsupplyJay$$ 同样的。当用户打算卖出AmountJayIn数量的Jay并获取ETH是,他可以换到的ETH数量为: $$AmountEthOut = AmountJayIn/totalsupplyJay*reserveEth$$ 接下来我们看下出问题的点,也就是函数buyJayWithERC721function buyJayWithERC721( address[] calldata _tokenAddress, uint256[] calldata ids ) internal { for (uint256 id = 0; id < ids.length; id++) { IERC721(_tokenAddress[id]).transferFrom( msg.sender, address(this), ids[id] ); } } IERC721(_tokenAddress[id]).transferFrom(msg.sender,address(this),ids[id])这里的_tokenAddress[id]是用户可控的参数,触发点位于:buyJay函数:// Sell NFTs (Buy Jay) function buyJay( address[] calldata erc721TokenAddress, uint256[] calldata erc721Ids, address[] calldata erc1155TokenAddress, uint256[] calldata erc1155Ids, uint256[] calldata erc1155Amounts ) public payable { require(start, "Not started!"); uint256 total = erc721TokenAddress.length; if (total != 0) buyJayWithERC721(erc721TokenAddress, erc721Ids); if (erc1155TokenAddress.length != 0) total = total.add( buyJayWithERC1155( erc1155TokenAddress, erc1155Ids, erc1155Amounts ) ); if (total >= 100) require( msg.value >= (total).mul(sellNftFeeEth).div(2), "You need to pay ETH more" ); else require( msg.value >= (total).mul(sellNftFeeEth), "You need to pay ETH more" ); _mint(msg.sender, ETHtoJAY(msg.value).mul(97).div(100)); (bool success, ) = dev.call{value: msg.value.div(34)}(""); require(success, "ETH Transfer failed."); nftsSold += total; emit Price(block.timestamp, JAYtoETH(1 * 10**18)); } 所以目前攻击者可以做的事是: 调用buyJay合约,自定义参数address[] calldata erc721TokenAddress为任意的一个ERC721合约。 接下来看回公式: $$AmountEthOut = AmountJayIn/totalsupplyJay*reserveEth$$ 如果用户想要获得更多的AmountEthOut,需要做的就是增加AmountJayIn减少totalsupplyJay增加reserveEth而增加reserveEth是实现起来最简单的方案:只需要在call的时候代入eth就可以了。配合重入漏洞我们就可以做到:Jay.buyJay(erc721contract){value:xxxxETH} --> buyJayWithERC721(erc721TokenAddress, erc721Ids) --> IERC721(_tokenAddress[id]).transferFrom() --> erc721TokenAddress.transferFrom() --> Jay.sell() 在调用sell的时候 由于Jay.buyJay(erc721contract){value:xxxxETH}传入了ETH,这时也就增加了adress(Jay).balance (reserveETH)。获取了更多的ETH。之后再卖掉xxxxETH兑换来的Jay即可。 以下是真实的攻击交易详情:用户通过闪电贷借出了72.5个ETH。先通过22ETH买入了13584899853779845952188个Jay。然后再次调用buyJay(){50.5ETH}的通过重入直接调用sell函数卖出13584899853779845952188 ,操纵reserveETH达到太高Jay价格的效果。 https://explorer.phalcon.xyz/tx/eth/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6?line=11 ## Publication Information - [Fuzzingq](https://paragraph.com/@liusy/): Publication homepage - [All Posts](https://paragraph.com/@liusy/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@liusy): Subscribe to updates - [Twitter](https://twitter.com/_fuzzingq): Follow on Twitter