Reentrancy Attacks in Solidity Smart Contracts

1 October, 2024
article image
Education

Contents

It’s been several years since hackers stole a lot of money from the DAO in 2016 by using a trick called a reentrancy attack. This kind of attack can still happen today and cause serious financial problems. Luckily, there are ways to stop these attacks and keep your project safe.

How does it work?

A reentrancy attack on a smart contract happens when one contract gives control to another contract. Then, the second contract can call the first contract again before the first call is finished. This usually happens through a special function like fallback or onERC721Received. Hackers can use this trick to change the contract and steal money.

Mechanism of attack:

  • External call: The contract calls a function in another contract, like to send some tokens.
  • Recursive call: The other contract can call the same function in the first contract again before the first call is finished.
  • State change: Because the first contract hasn’t finished updating yet, the hacker can use this to do something like take out money again.

Why does this happen?

The problem is caused by the wrong order of steps:

  1. Checks: The contract checks its status (like if it has enough money).
  2. Interactions: The contract calls another contract.
  3. Changes: The contract updates its status (like taking away some money).

If the checks happen before the contract calls another contract, but the changes happen after, a hacker can trick the contract into giving away money again.

Example With the Code

Below is a simple contract BasicBank that allows users to deposit and withdraw Ether, manage user balances, and collect fees.

Test your skills. Can you find a vulnerability in this contract?

//SPDX-License-Identifier: MIT
pragma solidity 0.7.0;

contract BasicBank {

    mapping (address => uint) private userFunds;
    address private commissionCollector;
    uint private collectedComission = 0;

    constructor() {
        commissionCollector = msg.sender;
    }

    modifier onlyCommissionCollector {
        require(msg.sender == commissionCollector);
        _;
    }

    function deposit() public payable {
        require(msg.value >= 1 ether);
        userFunds[msg.sender] += msg.value;
    }

    function withdraw(uint _amount) external payable {
        require(getBalance(msg.sender) >= _amount);
        msg.sender.call{value: _amount}("");
        userFunds[msg.sender] -= _amount;
        userFunds[commissionCollector] += _amount/100;
    }

    function getBalance(address _user) public view returns(uint) {
        return userFunds[_user];
    }

    function getCommissionCollector() public view returns(address) {
        return commissionCollector;
    }

    function transfer(address _userToSend, uint _amount) external{
        userFunds[_userToSend] += _amount;
        userFunds[msg.sender] -= _amount;
    }

    function setCommissionCollector(address _newCommissionCollector) external onlyCommissionCollector{
        commissionCollector = _newCommissionCollector;
    }

    function collectCommission() external {
        userFunds[msg.sender] += collectedComission;
        collectedComission = 0;
    }
}

Source: https://github.com/wasny0ps/Reentrancy#-example-of-vulnereable-contract

Did you find a problem? Let’s look at an attack:

  1. The attacker calls withdraw(_amount).
  2. The contract checks the attacker’s balance (sufficient) and proceeds.
  3. The contract sends funds to the attacker’s address using .call{value}. Execution pauses while the transfer happens.
  4. The attacker, knowing the execution is paused, calls withdraw(_amount) again.
  5. Since the balance hasn’t been updated, it appears sufficient again.
  6. The contract attempts to send another _amount to the attacker’s address.
  7. The first transfer is completed, the user receives funds.
  8. Finally, the contract deducts _amount from the attacker’s balance (but it’s already spent in the first transfer).
  9. The attacker can repeat steps 4-8, draining the contract’s funds while only updating their balance once.

As a result, the withdraw() function is vulnerable to reentrancy because the user’s balance is updated after transferring funds. This allows attackers to exploit the pause during the transfer to manipulate the contract and withdraw funds multiple times before the balance reflects the actual withdrawal.

Types of Reentrancy Attack

Single function Reentrancy

This is a common problem where a hacker can trick a smart contract into doing the same thing over and over again. This happens when the contract changes its own information but then does something else before the change is finished. The hacker can take advantage of this to steal money or other things.

Cross-function Reentrancy

This kind of problem happens when a series of function calls leads to unexpected results because the information in the contract isn’t correct. A hacker can cause this problem by calling a function that calls another function, and that function calls the first function again, causing the contract to change in a bad way.

Cross-contract Reentrancy

A cross-contract reentrancy attack happens when multiple smart contracts talk to each other and share information. If one contract calls another contract without updating its own information first, a hacker can trick the contracts into doing things in a wrong order, which can cause problems.

Cross-Chain Reentrancy

Cross-chain reentrancy is less common, but it happens when smart contracts on different blockchains talk to each other. This can happen in systems that let you move things between different blockchains, like bridges or DEXs. It’s similar to the other kind of reentrancy, but it’s more complicated because it involves different blockchains.

Read-Only Reentrancy

This kind of attack is a special type of reentrancy problem. It happens when a smart contract calls another smart contract, but the second contract doesn’t change anything in itself. Instead, it reads information from the first contract and then calls the first contract again, which can cause unexpected problems. Even though the second contract doesn’t change anything, it can still mess up the order of things and cause security issues.

Victims of Attack

Let’s take a look at some of the victims who have been affected by this attack.

  1. Penpie
  1. Pythia
  1. Eralend
  1. Omni
  1. BurgerSwap

Protection Methods

Use the Checks-Effects-Interactions Pattern

Make sure that we change the state before we do anything with other contracts or send Ether. Let’s look at the example we talked about before and change the steps to follow the Checks-Effects-Interactions pattern

  • Checks: Check the state of the contract that’s calling this one. For example, make sure the calling contract has enough money to take out.
  • Effects: Update the overall state, like reducing the calling contract’s balance in the record.
  • Interactions: If the checks pass, do something with another contract, like sending tokens.

Reentrancy Guard

This is a special piece of code made by OpenZeppelin. It helps to stop the same function from being called twice simultaneously.

When you use this special code in your code, you can use a special word called nonReentrant to protect your functions. If you use this word for a function, it can’t be called again until it’s finished running.

Source: https://coinsbench.com/reentrancy-attacks-and-how-to-deal-with-them-16da3a2549

How OXORIO Can Help?

Understanding and mitigating reentrancy vulnerabilities is crucial for any blockchain project. These attacks exploit flaws in smart contract code to manipulate contract states and drain assets, often going undetected until it’s too late. The complexity of smart contracts and the nuanced nature of these vulnerabilities make them challenging to identify without specialized expertise.

OXORIO specializes in safeguarding your projects against such sophisticated threats. Here’s how we can help:

  • Comprehensive Smart Contract Audits
  • Implementation of Secure Coding Patterns
  • Use of Trusted Libraries
  • Education and Training:
  • Ongoing Security Support

By partnering with OXORIO, you gain access to a dedicated team of blockchain security experts committed to protecting your project from the financial and reputational damages caused by reentrancy attacks and other vulnerabilities. We understand the intricacies of smart contract security and are equipped to provide the solutions you need.

Don’t let vulnerabilities compromise your project’s success. Secure your smart contracts and build confidence among your users and investors by collaborating with OXORIO. Together, we’ll strengthen your blockchain initiatives and contribute to a safer, more reliable Web3 ecosystem.

Telegram
Education

Contents

Telegram

Have a question?

Have a question?

Stay Connected with OXORIO

We're here to help and guide you through any inquiries you might have about blockchain security and audits.