During the development of a smart contract, programmers may need to know the exact time to be able to execute some actions. Therefore, smart contract developers use what we call ...
In June 17, 2016 the most famous DAO platform developed by solidity language was victim to a big hack that make it lose more than 3.6 million Ether. The attacker has exploited a vulnerability called reentrancy in the smart contract that managed the platform. So what is a reentrancy attack in solidity?
The reentrancy vulnerability happens when a smart contract tends to interact with another address before reacting to its variables. Therefore, the attacker continuously calls the same function and drains money from it without allowing the smart contract to react to its variables.
In this blog post, you are going to see what a reentrancy attack is and why technically it exists in smart contracts. In addition, you will get a number of real vulnerable solidity smart contracts to get a better technical idea about this attack. Moreover, you will see real technical details on how to fix this vulnerability in your source code.
At the end of this blog post, I am also going to discuss the possibilities of finding this vulnerability in other Blockchain networks like Solana and other technologies. So if you are interested just keep reading and leave a comment below.
Reentrancy is one of the most popular smart contract vulnerabilities and it did get even more popular after the big hack that divided the whole Ethereum network (DAO attack).
The reentrancy vulnerability happens when a function is communicating with another address before changing it stats. If the address is a simple wallet, nothing happens. However, if the address is actually a smart contract, then it can initiate another call to the same or another address. This behavior could create a loop that manipulates the vulnerable contract without allowing the vulnerable smart contract to change it state.
Usually, while attacking a smart contract using a reentrancy vulnerability, the attacker smart contract tries to call the same contract function each time that one calls it. This could be seen as the recursion concept in programming languages.
In most scenarios, especially in the DAO attack, this exploitation is made possible due to a special function in solidity language called the fallback function. This function is declared without the function keyword, should be external, payable, and does not return a value. In addition, this function can have a modifier to control its working. For more details about it, you can check this link.
When a smart contract or in general an address tries to send money to a smart contract without specifying a function, this fallback function is triggered to receive that money and perform any business logic the developer wants.
This vulnerability could be found in four types in smart contracts in the wild. In the following section, you will see what those types are and what the difference between each one of them is. If you need a technical example of such vulnerability, please jump directly to the section 3.
Depending on the way the reentrancy happens in the contract source code, this vulnerability can be divided into 4 subcategories:
The fallback function reentrancy vulnerability is the most popular one and it is the one that makes use of the fallback function existing in the solidity language. Here, the vulnerable smart contract tries to send money to another address, which triggers the fallback function into it which itself calls again the vulnerable smart contract to withdraw the money.
To better understand this vulnerability, I really invite you to take a look at the following smart contract example:
The fallbackReVuln smart contract has two functions. The first one allows users to send money to the contract using the addMoney() function. The second function withdrawMoney() function allows the user to get their money back.
Now, let’s analyze the withdrawMoney() function line by line to see where the problem is. The first line in this function, which is line 12, checks if the amount that the user tries to withdraw is lower than what he actually has deposited to the contract. This is a normal check to avoid being able to withdraw a larger amount of money than what he actually put.
Then, if the amount is equal to or less than what he had deposited, line 13 sends the specified amount of money to the caller’s address. After that, the smart contract updates the user’s balance by removing that amount from his account.
Until now, everything seems logical and the smart contract performs the checks it needs to. However, the smart contract is vulnerable to a reentrancy attack as the attacker can force the vulnerable smart contract to not update the balances variable till the attacker drains all the funds in the smart contract.
To exploit this vulnerability, the attacker creates another contract with a fallback function that recalls the fallbackReVuln withdraw() function, and makes it deposit a small amount of money. Then use its exploit contract to call the fallbackReVuln smart contract function withdraw().
Each time the fallbackReVuln will reach line 13 it will send the money to the exploit contract and keep waiting till the operation ends. At the same moment, the fallback function of the exploit contract will be triggered and will call the withdraw() function again. You should keep in mind that the fallbackReVuln smart contract balances variable didn’t get modified yet, which means the first check line 12 will pass no matter what was the first amount. Then line 13 will again be executed.
This scenario will repeat until no funds are kept in the smart contract. This is exactly what happened in the DAO smart contract.
Create-based reentrancy attack is another type of reentrancy attack and happens when the smart contract constructor function calls other malicious contracts. The constructor function in solidity is a special and optional function that gets declared without a name only with the keyword “constructor” and should have its specific visibility declared.
This type of reentrancy attack is very rare in the industry as in most cases the constructor function is made with care and all the calls within it are calculated and verified for gas optimization problems. To better understand this vulnerability you should take a look at the following example:
Let’s suppose you have victimContract and maliciousContract. The victim contract has a function called start() which sends money back to the maliciousContract. However, to send back the money this contract creates a new contract called Helper and inject the send money line into its constructor function. Once the helper contract is created the constructor is executed and the money is sent to the maliciousContract.
At this moment the maliciousContract call the victimContract function start() again which creates the helper function again, and starts looping into this until all the funds get leaked or until a big number of smart contracts are created (depending on the objective of the attack).
The cross-function reentrancy attack happens when two functions share states and at least one of them communicates with an external address (send money or call a contract function). This attack is a little bit similar to the previously explained one (Create-Based Reentrancy Attack). The only difference is that in this situation the vulnerable function calls another one instead of creating a smart contract.
Here is an illustration of this scenario:
In this example the malicious contract calls function1 in the vulnerable smart contract, then that one initiates a call to function2 to perform some business logic. The function1 might be well made and not vulnerable to a reentrancy attack. However, its call to function 2 could make it vulnerable and after calling the malicious contract by sending ethers, the exploit is triggered.
The delegated call reentrancy attack happens when a function in a contract performs a delegated call to another smart contract’s function that itself communicates with the attacker’s address before the first one can make a change to its state. This attack is a little bit similar to the Create-Based Reentrancy Attack, the only difference between them is that in this situation the second contract is called through a delegated call.
The reentrancy vulnerability is not always exploitable. Some honeypot smart contract that I have previously discussed in this blog post, create a reentrancy vulnerability in the smart contract but, when you try to exploit it, the vulnerable smart contract start to revert your transactions to lock your ethers into it. I have detailed this situation in that blog post, and I highly encourage you to take a look at it.
Moreover, some versions of EVM are being developed by cybersecurity experts that can detect if a transaction is trying to exploit a reentrancy attack and stop it before it drains the money.
In addition, the reason to exploit it or not is what changes from one situation to another. If the vulnerability impact is to only change some useless information in the smart contract, then most attackers would not try to do it.
The reentrancy vulnerability impact depends mainly on the business logic of the smart contract. In most cases, the impact of the reentrancy vulnerability is the loss of the smart contract money. However, in some cases, it can be, for example, the ability to create multiple instances of paid objects, or repeatedly perform a code that is supposed to be done only once for each call.
Most of the existing tools both free and paid ones can be used to detect this kind of vulnerability. However, some of them are capable of discovering only one type of those vulnerabilities, and others can detect all of them. Here is a list of some of the best tools that can be used to find those vulnerabilities:
To get a more detailed idea about what kind of vulnerabilities can each tool discover in a smart contract, I highly encourage you to take a look at our previous blog post. Some of the tools specified in this list are still prototypes, which means you may not be able to find them on Github.
|Mythril||v0.19.9||Partial (conservative)||Partial (conservative)||No||Partial (conservative)|
|Securify||2018-08-01||Partial (conservative)||Partial (conservative)||No||No|
|Slither||0.6.4||Yes (conservative)||Yes (conservative)||Yes (conservative)||No|
To prevent the reentrancy vulnerability, the check-effect-interact pattern should be applied. The idea of this pattern is simple before you interact with the outside (both internal functions and other smart contracts), checks and changes should be applied to the desired variables.
By doing this, even if the attacker tries to inter the same function before its execution has ended, he will not be able to exploit the reentrancy mechanism. To better understand this concept let’s try to fix the vulnerability in the simplified version of DAO smart contract previously discussed.
Here is the vulnerable smart contract:
As explained in the previous section, the reentrancy vulnerability happens in the function withdrawMoney(). As you can see the checks are performed but there is an interaction before making an effect on the state of the contract. Therefore, the secure pattern (check-effect-interact) is not applied.
To fix this issue you need to change the order of lines 13 and 14. This means you should decrease the amount from the balances before sending the money. Therefore, here is how the source code will become:
This vulnerability also exists in other Blockchains, like VeChain and others. However, to my best knowledge, for a smart contract (or program) built on the Solana Blockchain, most reentrancy attacks are by default prevented. The reason is simple, the Solana Blockchain has a call depth of maximum 4 calls. For more detail about the call depth of Solana take a look at this page of the documentation.
Written by: Z. Oualid
I am a Cyber Security Expert, I have worked with many companies around the globe to secure their applications and their networks. I am certified OSCP and OSCE which are the most recognized and hard technical certifications in the industry of cybersecurity. I am also a Certifed Ethical hacker (CEH). I hope you enjoy my articles :).