Revest Finance Schwachstellen: Mehr als Re-entrancy

Sicherung der Zukunft von DeFi: Lektionen aus den Schwachstellen von Revest Finance

Revest Finance Schwachstellen: Mehr als Re-entrancy

Am 27. März 2022 wurde das Staking-DeFi-Projekt Revest Finance auf Ethereum aufgrund des ERC-1155 Call-Back-Mechanismus angegriffen, was zum Diebstahl von Token im Wert von rund 2 Millionen US-Dollar (nämlich BLOCKS, ECO, LYXe und RENA) führte. Wir analysierten den Angriff zunächst und tweeteten unsere Analyse noch am selben Abend (UTC+8).

Tatsächlich hatten wir zum Zeitpunkt der Veröffentlichung des Tweets noch einige Zweifel an einer Funktion im Revest TokenVault-Vertrag. Wir untersuchten den Vertrag, um seine Funktionalität zu verstehen. Später stellten wir fest, dass es sich um eine weitere kritische Zero-Day-Schwachstelle handelt, die auf eine weitaus einfachere Weise ausgenutzt werden kann und die gleichen enormen Verluste verursachen kann (wie der bereits erfolgte Angriff).

Anschließend kontaktierten wir umgehend das Revest Finance-Team, das schnell reagierte und einen Workaround für die Schwachstelle vorschlug. Nachdem wir bestätigt hatten, dass die Schwachstelle nicht ausgelöst werden konnte, beschlossen wir, diesen Blogbeitrag zu veröffentlichen.

Der folgende Blogbeitrag besteht aus drei Teilen: dem Mechanismus von Revest Finance, dem ursprünglichen Re-entrancy-Angriff und der neuen Zero-Day-Schwachstelle.

Was ist Revest Finance FNFT?

Das Financial Non-Fungible Token (FNFT) von Revest Finance ermöglicht die treuhänderische Übertragung zukünftiger Rechte auf gesperrte Vermögenswerte. Der Einstiegsvertrag (Revest Vertrag) bietet drei verschiedene Schnittstellen zur Prägung von FNFTs durch Sperrung zugrunde liegender Vermögenswerte:

  • mintTimeLock: Der zugrunde liegende Vermögenswert wird nach Ablauf einer bestimmten Zeit freigeschaltet.
  • mintValueLock: Der zugrunde liegende Vermögenswert wird freigeschaltet, wenn sein Wert über einen vorgeschriebenen Wert steigt oder unter ihn fällt.
  • mintAddressLock: Der zugrunde liegende Vermögenswert wird von einem vorgeschriebenen Konto freigeschaltet.

Der Revest-Vertrag verbindet die drei anderen Verträge zur Sperrung und Freischaltung zugrunde liegender Vermögenswerte.

  • FNFTHandler: Erbt vom ERC-1155-Token. Er erstellt jedes Mal, wenn eine Sperrung erfolgt, einen neuen FNFT mit der inkrementellen fnftId. Die Sperrung schreibt die Gesamtmenge des neuen FNFT bei dessen Erstellung vor. Der FNFT kann auf keine andere Weise geprägt werden, kann aber zum Freischalten zugrunde liegender Vermögenswerte verbrannt werden.

  • LockManager: Erfasst die Freischaltungsbedingungen für jede Sperrung bei deren Erstellung und entscheidet, ob die Sperrung bei der Freischaltung entsperrt werden kann.

  • TokenVault: Empfängt und sendet die zugrunde liegenden Vermögenswerte und erfasst Metadaten für jeden FNFT, wie z. B. den Wert eines bestimmten FNFT.

Wir nehmen mintAddressLock als Beispiel, um den Prozess der Prägung von FNFTs zu veranschaulichen.

Abbildung 1
Abbildung 2

Die beiden obigen Abbildungen beschreiben, wie ein FNFT erstellt, geprägt und verbrannt wird. Konkret sperrt Benutzer A 100 WETH in Revest Finance, wodurch der entsprechende FNFT mit der fnftId 1 erstellt wird. Schließlich werden 100 1-FNFTs an bestimmte Empfänger mit bestimmten Anteilen geprägt.

Beachten Sie, dass nach der Freischaltung des zugrunde liegenden Vermögenswerts jeder 1-FNFT verbrannt werden kann, um ein (*1e18) WETH zu erhalten. Wie in Abbildung 2 gezeigt, zieht Benutzer B 25 (*1e18) WETH ab, indem er 25 1-FNFTs verbrennt.

Zusätzlich bietet der Revest-Vertrag eine weitere Schnittstelle namens depositAdditionalToFNFT, die zwei Schwachstellen aufweist, die im Folgenden diskutiert werden.

Wir verwenden zunächst die folgenden beiden Abbildungen, um die normale Verwendung dieser Funktion zu beschreiben.

Abbildung 3
Abbildung 4

Die Funktion depositAdditionalToFNFT sperrt weitere zugrunde liegende Vermögenswerte in eine bestehende Sperrung (angegeben durch fnftId). Sinnvollerweise (Abbildung 3) erfordert sie, dass die angegebene Menge der Gesamtmenge des angegebenen FNFT entspricht, und verteilt dann die zusätzlichen Vermögenswerte gleichmäßig auf jeden angegebenen FNFT.

Andernfalls (Abbildung 4) erstellt sie eine neue Sperrung mit der neuesten fnftId, verbrennt die angegebene Menge alter FNFTs und prägt die angegebene Menge neuer FNFTs und erfasst dann den depositAmount der neuen Sperrung als Summe aus dem depositAmount der alten Sperrung und dem angegebenen Betrag, wie im folgenden Code gezeigt.

// Jetzt übertragen wir an den Token Vault
if(fnft.asset != address(0)){
    IERC20(fnft.asset).safeTransferFrom(_msgSender(), vault, quantity * amount);
}

ITokenVault(vault).handleMultipleDeposits(fnftId, newFNFTId, fnft.depositAmount + amount);

emit FNFTAddionalDeposited(_msgSender(), newFNFTId, quantity, amount);

Da depositAmount, der im TokenVault-Vertrag erfasst wird, den Betrag des zugrunde liegenden Vermögenswerts angibt, den ein bestimmter FNFT abheben kann, überträgt diese Operation den Wert der angegebenen Mengen alter FNFTs von der alten Sperrung zur neuen Sperrung.

(Eine Menge, die größer als die Gesamtmenge ist, führt zum Rückgängigmachen der Transaktion)

Was ist die Re-entrancy-Schwachstelle?

In diesem Teil erläutern wir, wie der Re-entrancy-Angriff funktioniert und besprechen die Ursache und die Korrekturmethode.

Abbildung 5
Abbildung 6
Abbildung 7

Die obigen drei Abbildungen beschreiben im Wesentlichen den gesamten Prozess des Re-entrancy-Angriffs. Speziell sperrt der Angreifer zunächst null RENA-Token, um 2 1-FNFTs zu prägen, die keinen Wert haben. Zweitens sperrt der Angreifer erneut null RENA-Token, prägt aber 360.000 2-FNFTs, die ebenfalls (noch) keinen Wert haben. Im letzten Schritt re-enteriert der Angreifer die depositAdditionalToFNFT-Funktion des Revest-Vertrags über den Call-Back-Mechanismus des FNFTHandler, der vom ERC-1155-Token-Standard geerbt wird, wodurch der depositAmount der Sperrung mit fnftId als 2 überschrieben wird, bevor fnftId aktualisiert wird. Infolgedessen erhält der Angreifer 360.001 2-FNFTs mit einem depositAmount von 1e18, was bedeutet, dass er 360.001 * 1e18 RENA vom TokenVault-Vertrag abheben kann. Außerdem betragen die einzigen Kosten 1e18 RENA.

Korrekturmethode

Die Codes von Revest Finance entsprechen vollständig dem klassischen Re-entrancy-Muster: fnftId verwenden -> externer Aufruf mit Call-Back-Mechanismus -> fnftId aktualisieren. Daher ist der direkteste Weg, die Probleme zu beheben, dieses Muster zu durchbrechen. Der korrigierte Code ist unten gezeigt:

function mint(
    address account,
    uint id,
    uint amount,
    bytes memory data
) external override onlyRevestController {
    require(amount > 0, "Invalid amount");
    require(supply[id] == 0, "Repeated mint for the same FNFT");
    supply[id] += amount;
    fnftsCreated += 1;
    _mint(account, id, amount, data);
}

Erstens verschiebt er die Aktualisierungsoperation vor den externen Aufruf (_mint), was den Angriff vermeiden kann. Zweitens, da das System die Prägung von null FNFTs und die wiederholte Prägung desselben FNFTs nicht zulässt, fügt er zwei Prüfungen hinzu, um sicherzustellen, dass das System wie erwartet funktioniert, was die Sicherheit des Systems verbessern kann.

Die neue Zero-Day-Schwachstelle

Bei der Analyse des Codes von Revest Finance verwirrte uns die Funktion handleMultipleDeposits im TokenVault-Vertrag immer, deren Code unten gezeigt wird.

function handleMultipleDeposits(
    uint fnftId,
    uint newFNFTId,
    uint amount
) external override onlyRevestController {
    require(amount >= fnfts[fnftId].depositAmount, 'E003');
    IRevest.FNFTConfig storage config = fnfts[fnftId];
    config.depositAmount = amount;
    mapFNFTToToken(fnftId, config);
    if(newFNFTId != 0) {
        mapFNFTToToken(newFNFTId, config);
    }
}

Während des Aufrufs der Funktion depositAdditionalToFNFT ändert die Funktion handleMultipleDeposits den depositAmount der alten Sperrung oder erfasst ihn für die neue. Wenn newFNFTId null ist, erfasst sie den depositAmount der neuen Sperrung nicht, da dies eine Operation zum Hinzufügen zusätzlicher Vermögenswerte zur bestehenden Sperrung ist.

Nach gesundem Menschenverstand erfasst sie, wenn newFNFTId nicht null ist, nur den depositAmount der neuen Sperrung, ändert aber nicht den depositAmount der alten. Der Code zeigt jedoch, dass er nicht nur den depositAmount der neuen Sperrung erfasst, sondern auch den depositAmount der alten ändert.

Wir halten dies für eine schwerwiegende Zero-Day-Logik-Schwachstelle und haben dann eine PoC geschrieben, um dies zu verifizieren. Die folgenden drei Abbildungen beschreiben, wie die PoC funktioniert.

Abbildung 8
Abbildung 9
Abbildung 10

Konkret sperrt der Angreifer zunächst null RENA, um 360.000 1-FNFTs zu prägen. Danach ruft der Angreifer direkt die Funktion depositAdditionalToFNFT auf, um eine neue Sperrung zu erstellen. Aufgrund des Logikfehlers ändert der TokenVault-Vertrag den depositAmount der alten Sperrung fälschlicherweise von null auf 1e18. Infolgedessen erhält der Angreifer 359.999 1-FNFTs im Wert von 359.999 RENA. Offensichtlich ist die PoC weitaus einfacher als der tatsächliche Re-entrancy-Angriff.

Der Workaround zur Behebung der Schwachstelle

Dies ist ein Logikfehler, und wir empfehlen die Verwendung des folgenden Codes zur Behebung:

function handleMultipleDeposits(
    uint fnftId,
    uint newFNFTId,
    uint amount
) external override onlyRevestController {
    require(amount >= fnfts[fnftId].depositAmount, 'E003');
    IRevest.FNFTConfig memory config = fnfts[fnftId];
    config.depositAmount = amount;
    if(newFNFTId != 0) {
        mapFNFTToToken(newFNFTId, config);
    } else {
        mapFNFTToToken(fnftId, config);
    }
}

Da die beiden verwundbaren Verträge: TokenVault und FNFTHandler viele kritische Zustände speichern, kann das Projekt den TokenVault-Vertrag und den FNFTHandler-Vertrag nicht neu bereitstellen, ohne Zustände zu migrieren. Um weitere Angriffe auf diese Schwachstelle zu vermeiden, hat das Projekt eine Lite-Version des Revest-Vertrags neu bereitgestellt, die komplexere Funktionen deaktiviert, um die Angriffsflächen für potenzielle Angreifer zu reduzieren. Nach Prüfung des Workarounds glauben wir, dass der Lite-Revest-Vertrag die in diesem Blog erwähnten möglichen Angriffe abmildern kann.

Fazit

Die Sicherung eines DeFi-Projekts ist keine leichte Aufgabe. Neben der Code-Prüfung sollten wir der Community unserer Meinung nach einen proaktiven Ansatz verfolgen, um den Projektstatus zu überwachen und Angriffe zu blockieren, bevor sie überhaupt stattfinden.

Über BlockSec

BlockSec ist ein wegweisendes Blockchain-Sicherheitsunternehmen, das 2021 von einer Gruppe weltweit renommierter Sicherheitsexperten gegründet wurde. Das Unternehmen engagiert sich für die Verbesserung der Sicherheit und Benutzerfreundlichkeit für die aufstrebende Web3-Welt, um deren Massenadoption zu fördern. Zu diesem Zweck bietet BlockSec Audits für Smart Contracts und EVM-Ketten, die Phalcon-Plattform für die Entwicklung von Sicherheitsmaßnahmen und die proaktive Blockierung von Bedrohungen, die MetaSleuth-Plattform für die Verfolgung von Geldern und Ermittlungen sowie die MetaDock-Erweiterung für Web3-Entwickler, die effizient im Kryptobereich surfen.

Bis heute hat das Unternehmen über 300 angesehene Kunden wie MetaMask, Uniswap Foundation, Compound, Forta und PancakeSwap betreut und in zwei Finanzierungsrunden von namhaften Investoren, darunter Matrix Partners, Vitalbridge Capital und Fenbushi Capital, zweistellige Millionenbeträge erhalten.

Offizielle Website: https://blocksec.com/

Offizieller Twitter-Account: https://twitter.com/BlockSecTeam

Sign up for the latest updates