Solidity Security By Example #05: Cross-Contract Reentrancy

Phuwanai Thummavet
Valix Consulting
Published in
6 min readSep 21, 2022

By Phuwanai Thummavet

Smart contract security is one of the biggest impediments toward the mass adoption of the blockchain. For this reason, we are proud to present this series of articles regarding Solidity smart contract security to educate and improve the knowledge in this domain to the public.

Cross-contract reentrancy typically happens when multiple contracts share the same state variable, and some contracts update that variable insecurely. This type of reentrancy might be considered a complicated issue since it is often challenging to discover.

In this article, you will learn how the cross-contract reentrancy attack happens and how to prevent it. Enjoy reading. 😊

You can find all related source code at 👉 https://github.com/serial-coder/solidity-security-by-example/tree/main/05_cross_contract_ reentrancy.

Disclaimer:

The smart contracts in this article are used to demonstrate vulnerability issues only. Some contracts are vulnerable, some are simplified for minimal, some contain malicious code. Hence, do not use the source code in this article in your production.

Nonetheless, feel free to contact Valix Consulting for your smart contract consulting and auditing services.🕵

Table of Contents

  • The Dependencies
  • The Vulnerability
  • The Attack
  • The Solution
  • Summary

The Dependencies

The code below contains dependencies required by the InsecureMoonVault and the FixedMoonVault contracts. The dependencies include ReentrancyGuard abstract contract (lines 3–12), IMoonToken interface (lines 14–23), and MoonToken contract (lines 25–121).

The ReentrancyGuard contains the noReentrant modifier that is used to prevent reentrancy attacks. The noReentrant (lines 6–11) is a simple lock that allows only a single entrance to the function applying it. If there is an attempt to do the reentrant, the transaction would be reverted by the require statement in line 7.

The IMoonToken interface defines function prototypes, enabling MoonVault contract (i.e., InsecureMoonVault in the code below) to interact with the MoonToken contract.

Lastly, the MoonToken contract is a simple ERC-20 token. Users can buy (via the deposit function in lines 13–16 in the below code) and sell (via the withdrawAll function in lines 18–27 in the below code) MOON tokens through the MoonVault contract. The MOON is a stablecoin pegged with Ether one-on-one. Whatever amount of Ether you deposit, what amount in MOON you will get.

Additionally, users can transfer their MOONs (via the transfer function in lines 50–58 in the above code) or approve the transfer allowance of their MOONs (via the approve function in lines 85–91 in the above code) to other users or smart contracts.

The Vulnerability

The following code describes the InsecureMoonVault contract. As mentioned earlier in the previous section, the contract allows users to:

  • Buy MOON tokens (via the insecureMoonVault.deposit() function)
  • Sell MOON tokens (via the insecureMoonVault.withdrawAll() function)
  • Transfer MOONs (via the moonToken.transfer() function)
  • Approve the transfer allowance of their MOONs (via the moonToken.approve() function)
  • Check the balances of their MOONs (via the insecureMoonVault.getUserBalance() or moonToken.balanceOf() function)

Undoubtedly, the InsecureMoonVault contract is vulnerable to a reentrancy attack. But, can you discover the issue? 👀

Since the InsecureMoonVault contract applies noReentrant modifier to the deposit and withdrawAll functions, both functions are safe from the reentrancy attack 🕵. Please refer to our previous article on the single-function reentrancy attack to understand how the insecure withdrawAll function can be exploited.

Unfortunately, the InsecureMoonVault contract in this article got another level of reentrancy in terms of complexity; we would call it: cross-contract reentrancy. 🤢

The cross-contract reentrancy begins in line 22 in the withdrawAll function. Figure 1 below illustrates how the cross-contract reentrancy attack occurs.

Figure 1. How the cross-contract reentrancy happens (click on the image to enlarge)

The root cause of cross-contract reentrancy attack is typically caused by having multiple contracts mutually sharing the same state variable, and some of them update that variable insecurely.

Again, both the deposit and withdrawAll functions apply the noReentrant modifier. Thus, an attacker cannot execute the reentrancy on these functions anymore.

Anyway, the withdrawAll function does not update the withdrawer’s balance (moonToken.burnAccount(msg.sender)) before sending Ethers back to the withdrawer (Step 4). Consequently, the attacker can perform the cross-contract reentrancy attack by manipulating the control flow in the Attack #1 contract’s receive function to transfer its balance (Step 5) to another contract, Attack #2 (i.e., contract instance #2 in Figure 1).

Subsequently, the attacker can trigger another transaction by invoking the attackNext function of the Attack #2 contract (Step 6) to gradually withdraw Ethers from the InsecureMoonVault contract and then transfer the Attack #2 contract’s balance to the Attack #1 contract.

To drain all Ethers locked in the InsecureMoonVault, the attacker executes the attackNext function of the Attack #1 and Attack #2 contracts alternately. Oh My! 😱

In fact, the attacker can integrate the attack step 6 into a single transaction call to automate the attack. Though, the step 6 was intentionally isolated for the understanding sake.

The Attack

The following code presents the Attack contract that can exploit the InsecureMoonVault contract.

To exploit the InsecureMoonVault, an attacker has to deploy two Attack contracts (i.e., attack1 and attack2) and then perform the following actions:

  1. Call: attack1.attackInit() and supplies 1 Ether
  2. Call: attack2.attackNext()
  3. Alternately Call: attack1.attackNext() and attack2.attackNext() to gradually steal all locked Ethers

To understand how the Attack contract works in more detail, please refer to the attack steps depicted in Figure 1 above.

As mentioned earlier, the actions #2 and #3 can be integrated into the action #1 for an atomic attack transaction call. Nonetheless, we intentionally isolated them for the sake of understanding.

Figure 2. The attack result (click on the image to enlarge)

The result of the attack is displayed in Figure 2. As you can see, the attacker stole all locked Ethers by triggering separate transactions to the two Attack contracts alternately. 🤑

The Solution

The FixedMoonVault contract below is the remediated version of the InsecureMoonVault. 👨‍🔧

The withdrawAll function was improved to follow the checks-effects-interactions pattern to resolve the associated issue. In other words, we moved the so-called effect part (moonToken.burnAccount(msg.sender)) to line 23 to execute it before the interaction part in line 26 (msg.sender.call{value: balance}(“”)). This coding pattern guarantees that the withdrawer’s balance would be updated before sending Ethers back to the withdrawer, impeding the cross-contract reentrancy attack.

Note, even though the moonToken.burnAccount(msg.sender) statement in line 23 is interacting with the MoonToken which is an external contract, we consider that the MoonToken is trustworthy.

Summary

In this article, you have learned the cross-contract reentrancy vulnerability in the Solidity smart contract, how an attacker exploits the attack, and the preventive solution to resolve the issue. We hope you gain your security knowledge from our article. See you again in our upcoming article.

Again, you can find all related source code at 👉 https://github.com/serial-coder/solidity-security-by-example/tree/main/05_cross_contract_ reentrancy.

Author Details

Phuwanai Thummavet (serial-coder), Lead Blockchain Security Auditor and Consultant | Blockchain Architect and Developer.

See the author’s profile.

About Valix Consulting

Valix Consulting is a blockchain and smart contract security firm offering a wide range of cybersecurity consulting services. Our specialists, combined with technical expertise with industry knowledge and support staff, strive to deliver consistently superior quality services.

For any business inquiries, please get in touch with us via Twitter, Facebook, or info@valix.io.

--

--

Phuwanai Thummavet
Valix Consulting

Blockchain | Coding | Hacking — I’m a full-time bug collector. Most of the time I bring some bugs to life. Visit my website: www.serial-coder.com