Skip to main content

Introduction

  • Reentrancy is a smart contract vulnerability that allows the attacker the ability to invoke the vulnerable contract in an unexpected state. As you'll see in Process Diagram, Attacker Contract will unexpectedly call (or "re-enter") the Vulnerable Contract before the Vulnerable Contract has updated its state.
  • In this lesson, you'll learn the theory behind Reentrancy attacks. After you've learned the theory, you'll create your own Attacker Contract and exploit this vulnerability within the lab environment!
  • Tip
    Do you already have a grasp on how Reentrancy works? Feel free to move to the Reentrancy Fundamentals: Attack Lab and put your knowledge to the test!

Prerequisites

  • Basic Solidity knowledge

Code

  • The code in this section is vulnerable to a Reentrancy attack. In the Reentrancy Fundamentals: Attack Lab, you'll create an Attacker Contract that exploits this bug.
Tip:

Don't worry if the code (below) doesn't make complete sense. You'll dive into the details within the Process Diagram.

Vulnerable.sol
13 collapsed lines
// SPDX-License-Identifier: MIT
// WARNING: This is buggy code. Do NOT use in the real world.
pragma solidity 0.8.24;
import "./shared/VulnerableBase.sol";
contract Vulnerable is VulnerableBase {
mapping(address => uint256) public balances;
constructor() payable VulnerableBase() {}
// Allow a customer the ability to deposit funds into their account
function deposit() public payable {
uint256 initialBalance = balances[msg.sender];
balances[msg.sender] += msg.value;
emit AttackerDepositBalance(
initialBalance / 1 ether,
balances[msg.sender] / 1 ether
);
emit VulnerableContractBalance(address(this).balance / 1 ether);
}
// Allow a customer the ability to withdraw all ETH from their account.
function withdrawAll() public {
uint256 beginExecutionBalance = balances[msg.sender];
// COMMENT GROUP A: START
if (beginExecutionBalance <= 0) {
revert("Customer has insufficient balance");
}
(bool success, ) = msg.sender.call{value: beginExecutionBalance}("");
require(success, "Failed to send ETH");
balances[msg.sender] = 0;
// COMMENT GROUP A: END
}
}

Explanation

  • Marker 1 : Before exploiting the Reentrancy vulnerability, the Attacker Contract must complete some prerequisite work. In this case, deposit() is called to give the attacker a positive balance. (The reasoning for this step will be evident soon.)
  • Marker 2: The Attacker Contract calls withdrawAll() to start the main portion of the attack.
  • Marker 3: This condition passes due to Marker 1.
  • Marker 4
    • msg.sender refers to the address that called withdrawAll(). For the purposes of this example, we'll refer to this as attacker_address.
    • When attacker_address initially calls withdrawAll(), beginExecutionBalance is equal to the amount of ETH that attacker_address initially deposited into the contract.
    • call() is a low-level Solidity function that can send ETH to another address. When this call() occurs, the execution flow is delegated to the Attacker Contract which allows the Attacker Contract to call withdrawAll() again. This second withdrawAll() occurs before the state update (Marker 5) occurs. As the Vulnerable Contract can be "re-entered" before it updates its state, Vulnerable Contract is vulnerable to a Reentrancy attack. When the Attacker Contract calls withdrawAll() for the second time, Marker 3 will evaluate to false as beginExecutionBalance hasn't (yet) been set to 0. During the second call(), the Attacker Contract will receive more ETH than they deposited into the contract. If this process continued, the Attacker Contract could bankrupt the Vulnerable Contract!

Summary

  • Reentrancy is like a bank withdrawal scenario where someone requests money, but before the teller updates their balance, they jump back in line to withdraw again.
  • The code demonstrates Single-Function Reentrancy, where a single function is re-entered during the attack. As you'll see in Reentrancy Fundamentals: Defend Theory, there can be other types of Reentrancy (e.g., Cross-Function Reentrancy, Cross-Contract Reentrancy, etc.)

Process Diagram

  • In this section, you'll visualize how the Attacker Contract could exploit the Vulnerable Contract. As previously discussed, you'll create the Attacker Contract in the next lesson.
  • Important
    The diagram is meant to give a high-level overview of the attack. In other words, certain minor steps are omitted. Learning how to complete these minor steps independently is a pivotal skill. As such, you'll practice this skill within the Reentrancy Fundamentals: Attack Lab.
  • Tip
    To solidify your understanding, try to cross-reference the Vulnerable Contract (in the diagram) with the Code.
Attack Diagram
Tip:

Click on the image to zoom in


Attacker Contract
Attacker Contract
Balance
Balance
1 eth
1 eth
Logic
Logic
Call deposit() to transfer 1 eth to Vulnerable Contract
Call deposit() to tr...
Vulnerable Contract
Vulnerable Contract
Logic
Logic
withdrawAll()
withdrawAll()
Does Attacker Contract have a positive balance?
Does Attacker Contrac...
Transfer 1 eth to Attacker Contract
Transfer 1 eth to Att...
True
True
3b
3b
4a
4a
State
State
Balances
Account
Account
Balance
Balance
Attacker Contract
Attacker Contract
1 eth
1 eth




Seeding Phase
Seeding Phase
Transfer Phase
Transfer Phase
Deduction Phase
Deduction Phase
Attacker Contract
Attacker Contract
1 ethLib
1 ethLib
Balance
Balance
0 eth
0 eth
+ 1 eth
+ 1 eth
2
2
Logic
Logic
Call withdrawAll()
to withdraw 1 eth from Vulnerable Contract
Call withdrawAll()...

Receive 1 eth from Vulnerable Contract
Receive 1 eth from V...
4b
4b
5
5
3a
3a
Vulnerable Contract
Vulnerable Contract
Logic
Logic
withdrawAll()
withdrawAll()
Set
Attacker Contract balance
to 0
Set...
State
State
Balances
Account
Account
Balance
Balance
Attacker Contract
Attacker Contract
0 eth
0 eth
Vulnerable Contract
Vulnerable Contract
Logic
Logic
deposit()
Update Attacker Contract
Balance to 1 eth
Update Attacker Contract...
1
1
State
State
Balances
Account
Account
Balance
Balance
Attacker Contract
Attacker Contract
1 eth
1 eth
Red Arrows correspond to the "Attack Loop"
Red Arrows correspon...

Explanation

  • Seeding Phase - 1: In this example, the attack starts when Attacker Contract creates a balance within Vulnerable Contract.
  • Transfer Phase - 2: Note the Attacker Contract balance (on the blockchain), is now 0 ETH.
  • Transfer Phase - 3a 3b: When Attacker Contract attempts to withdraw 1 ETH, the Vulnerable Contract checks to see if Attacker Contract has a positive balance.
  • Transfer Phase - 4a 4b: The transfer moves forward because Attacker Contract has 1 ETH available (in Vulnerable Contract's state).
  • Transfer Phase - 5: When the developer created Vulnerable Contract, they assumed the contract would interact with Externally Owned Accounts (i.e., accounts that don't execute code). In other words, they didn't expect Transfer Phase - 5 to occur. In the developer's faulty mental model, the execution flow would immediately transition to the Deduction Phase. However, the third party is actually a smart contract and when one contract calls another, it's like temporarily handing over the steering wheel - the called contract gets to decide what happens next. When the "steering wheel" is handed to the Attacker contract, the Deduction Phase hasn't occurred.
  • Transfer Phase - 3a: After receiving the 1 ETH, the Attacker Contract can immediately attempt another withdrawal. In other words, the Attacker Contract is "re-entering" the Vulnerable Contract. As the Deduction Phase hasn't occurred, Transfer Phase - 3b will evaluate to True and the Vulnerable Contract will transfer unintended eth to the Attacker Contract. Unfortunately, this transfer can start the "attack loop" again (see red arrows). The "attack loop" will continue until all eth has been transferred to the Attacker contract.
  • Key Takeaway
    : Notice how the Vulnerable Contract has state-changing operations (Deduction Phase) after giving execution control to the Attacker Contract ( Transfer Phase - 4a ). This is a common sign of a Reentrancy vulnerability.

Next Steps

  • In the Reentrancy Fundamentals: Attack Lab, you'll create the Attacker Contract and get some "hands on" experience with this vulnerability!

Real World Examples

Inspiration

We wanted to thank the following content creators. Without people like you, BlockBash tutorials couldn't exist :)

Disclosures

Warning:

  • Content should be used for educational purposes only. You should not leverage this content for nefarious purposes. Blockbash's authors are not liable for misuse of this content.
  • Everyone makes mistakes, including the authors of Blockbash. All content and recommendations should be verified via another source. Blockbash's authors are not liable for any mistakes. If you've found an error, please create a Github Issue.