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,
AttackerContract will unexpectedly call (or "re-enter") theVulnerableContract before theVulnerableContract 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
AttackerContract and exploit this vulnerability within the lab environment! - 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!Tip
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
AttackerContract 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.
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, theAttackerContract 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: TheAttackerContract callswithdrawAll()to start the main portion of the attack. - Marker
3: This condition passes due to Marker1. -
Marker
4msg.senderrefers to the address that calledwithdrawAll(). For the purposes of this example, we'll refer to this asattacker_address.- When
attacker_addressinitially callswithdrawAll(),beginExecutionBalanceis equal to the amount ofETHthatattacker_addressinitially deposited into the contract. call()is a low-level Solidity function that can sendETHto another address. When thiscall()occurs, the execution flow is delegated to theAttackerContract which allows theAttackerContract to callwithdrawAll()again. This secondwithdrawAll()occurs before the state update (Marker5) occurs. As theVulnerableContract can be "re-entered" before it updates its state,VulnerableContract is vulnerable to a Reentrancy attack. When theAttackerContract callswithdrawAll()for the second time, Marker3will evaluate tofalseasbeginExecutionBalancehasn't (yet) been set to0. During the secondcall(), theAttackerContract will receive moreETHthan they deposited into the contract. If this process continued, theAttackerContract could bankrupt theVulnerableContract!
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
AttackerContract could exploit theVulnerableContract. As previously discussed, you'll create theAttackerContract in the next lesson. - 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.Important
- To solidify your understanding, try to cross-reference theTip
VulnerableContract (in the diagram) with the Code.
Attack Diagram
Tip:
Click on the image to zoom in
Explanation
- Seeding Phase -
1: In this example, the attack starts whenAttackerContract creates a balance withinVulnerableContract. - Transfer Phase -
2: Note theAttackerContract balance (on the blockchain), is now 0ETH. - Transfer Phase -
3a3b: WhenAttackerContract attempts to withdraw 1ETH, theVulnerableContract checks to see ifAttackerContract has a positive balance. - Transfer Phase -
4a4b: The transfer moves forward becauseAttackerContract has 1ETHavailable (inVulnerableContract's state). - Transfer Phase -
5: When the developer createdVulnerableContract, 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 -5to 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 theAttackercontract, the Deduction Phase hasn't occurred. - Transfer Phase -
3a: After receiving the 1ETH, theAttackerContract can immediately attempt another withdrawal. In other words, theAttackerContract is "re-entering" theVulnerableContract. As the Deduction Phase hasn't occurred, Transfer Phase -3bwill evaluate toTrueand theVulnerableContract will transfer unintended eth to theAttackerContract. Unfortunately, this transfer can start the "attack loop" again (see red arrows). The "attack loop" will continue until all eth has been transferred to theAttackercontract. - : Notice how theKey Takeaway
VulnerableContract has state-changing operations (Deduction Phase) after giving execution control to theAttackerContract ( Transfer Phase -4a). This is a common sign of a Reentrancy vulnerability.
Next Steps
- In the
Reentrancy Fundamentals: Attack Lab, you'll create the
AttackerContract 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: