Zusammenfassung
Am 5. Dezember 2023 meldete die Web3-Entwicklungsplattform [Thirdweb] (https://thirdweb.com/) Sicherheitsschwachstellen in ihren vorgefertigten Smart Contracts. Diese Schwachstelle betraf alle ERC20-, ERC721- und ERC1155-Token, die mit diesen Verträgen bereitgestellt wurden. In den folgenden Tagen wurden Token, die mit den anfälligen Verträgen eingesetzt wurden, nach und nach für Angriffe missbraucht.
Schwachstellenanalyse
ERC-2771
Diese EIP definiert ein Protokoll auf Vertragsebene für "Empfänger"-Verträge zur Annahme von "Meta-Transaktionen" durch vertrauenswürdige "Forwarder"-Verträge. Es werden keine Protokolländerungen vorgenommen. Empfänger-Verträge erhalten den effektiven msg.sender (bezeichnet als _msgSender()) und msg.data (bezeichnet als _msgData()) durch Anhängen zusätzlicher calldata.
In der Praxis wird häufig die OpenZeppelin-Implementierung ERC2771Context verwendet. Insbesondere werden die letzten 20 Bytes der Rufdaten des vertrauenswürdigen Weiterleiters als _msgSender() behandelt. Für gewöhnliche Bibliotheksbenutzer scheint es nur notwendig zu sein, alle Verwendungen von "msg.sender" durch "msgSender()" zu ersetzen.
function _msgSender() internal view virtual override returns (address) {
uint256 calldataLength = msg.data.length;
uint256 contextSuffixLength = _contextSuffixLength();
if (isTrustedForwarder(msg.sender) && calldataLength >= contextSuffixLength) {
return address(bytes20(msg.data[calldataLength - contextSuffixLength:]));
} else {
return super._msgSender();
}
}
function _msgData() internal view virtual override returns (bytes calldata) {
uint256 calldataLength = msg.data.length;
uint256 contextSuffixLength = _contextSuffixLength();
if (isTrustedForwarder(msg.sender) && calldataLength >= contextSuffixLength) {
return msg.data[:calldataLength - contextSuffixLength];
} else {
return super._msgData();
}
}
Multicall
Mit Multicall können Benutzer mehrere Anrufe in einen einzigen Anruf integrieren. Die Rufdaten mehrerer Anrufe werden aus den Rufdaten eines einzigen Anrufs extrahiert. In der Praxis wird OpenZeppelin's MulticallUpgradeable häufig verwendet. (Der Fehler ist jetzt behoben)
function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
results[i] = _functionDelegateCall(address(this), data[i]);
}
return results;
}
ERC-2771 + Multicall
Das Problem entsteht durch Unstimmigkeiten beim Packen und Entpacken von Anrufdaten zwischen diesen beiden Komponenten (ERC-2771 und Multicall). Gemäß ERC-2771 sollte der vertrauenswürdige Spediteur die Meldungsdaten und Absenderinformationen zusammen verpacken. Der Vertrag verwendet dann _msgData() und _msgSender() zum Entpacken der Meldungsdaten bzw. der Absenderinformationen.
Die Multicall-Funktion ist jedoch nicht mit der Art und Weise kompatibel, wie ERC-2771 die Daten verpackt. Bei korrekter Implementierung sollte Multicall _msgSender() verwenden, um die Absenderinformationen auszupacken und sie an die Aufrufdaten jeder Nachricht anzuhängen, damit sie bei nachfolgenden Aufrufen ordnungsgemäß ausgepackt werden können. Aber dieser Schritt wird in der aktuellen Implementierung übersehen.
In der Zwischenzeit versucht der Vertrag, der ERC-2771 folgt, Nachrichtendaten und Absenderinformationen mit Hilfe von _msgData() und _msgSender() zu entpacken. Ohne die an calldata angehängten Absenderinformationen werden die Absenderinformationen jedoch als die letzten 20 Bytes von _msgData() entpackt, was der Angreifer kontrollieren kann. Dies ermöglicht es einem Angreifer, manipulierte calldata zu konstruieren, die eine bösartige Logik mit einer beliebigen Absenderinformation ausführen und damit gegen die in den Spezifikationen festgelegten Erwartungen verstoßen.
Angriffsanalyse
Nehmen wir [eine Instanz einer Angriffstransaktion] (https://app.blocksec.com/explorer/tx/eth/0xecdd111a60debfadc6533de30fb7f55dc5ceed01dfadd30e4a7ebdb416d2f6b6) als Beispiel.
- Schritt 1: Tausch von 5 $WETH gegen 3.455.399.346 $TIME.
- Schritt 2: Aufrufen eines vertrauenswürdigen Forwarders mit sorgfältig erstellten Daten. Nach dem Parsen durch Multicall wird die Burn-Funktion mit Uniswap Pool als
_msgSender()aufgerufen. Der $TIME-Saldo des Pools wird verringert. - Schritt 3: Synchronisierung des Pools, der Preis von $TIME wird angehoben.
- Schritt 4: Tausche 3.455.399.346 $TIME gegen 94 $WETH.
Der Schritt 2 ist der Schlüssel des Angriffs. Der Angreifer ruft Forwarder.execute auf, um Daten weiterzuleiten:

Es wird jedoch als Bytes[] der Länge 1 geparst und dann als Daten für den Aufruf des Vertrags verwendet.

Hervorhebung & Lektion
Im DeFi-Bereich ist es von entscheidender Bedeutung, auf die Sicherheit von Bibliotheken Dritter zu achten. Leider können diese Bibliotheken manchmal auf unerwartete und verdeckte Weise miteinander interagieren, was eine Bedrohung für die Sicherheit des Vertrags darstellt. Dies macht deutlich, wie wichtig eine gründliche Prüfung und Überwachung ist, um potenzielle Schwachstellen, die sich aus solchen Interaktionen ergeben können, zu entschärfen.
Lesen Sie weitere Artikel dieser Serie:
- Lead-In: Top Ten "Awesome" Security Incidents in 2023
- #1: Ernte von MEV-Bots durch Ausnutzung von Schwachstellen in Flashbots Relay
- #2: Euler Finance Vorfall: Der größte Hack des Jahres 2023
- #3: KyberSwap-Vorfall: Meisterhafte Ausnutzung von Rundungsfehlern mit äußerst subtilen Berechnungen
- #4: Curve-Vorfall: Compiler-Fehler erzeugt fehlerhaften Bytecode aus unschädlichem Quellcode
- #5: Platypus Finance: Drei Angriffe mit einem Glücksfall überlebt
- #6: Hundred Finance Incident: Auslöser der Welle präzisionsbezogener Exploits in anfälligen Forked Protocols
- #7: ParaSpace-Vorfall: Ein Wettlauf gegen die Zeit, um den bislang kritischsten Angriff der Branche zu vereiteln
- #8: SushiSwap-Vorfall: Ein ungeschickter Rettungsversuch führt zu einer Reihe von Nachahmungsangriffen
- #9: MEV Bot 0xd61492: Vom Raubtier zur Beute in einem genialen Exploit



