2 Punkte von GN⁺ 2025-01-10 | 1 Kommentare | Auf WhatsApp teilen
  • Die Best-Fit-Zeichenkonvertierung von Windows ersetzt beim Umwandeln von UTF-16-Strings in ANSI-Codepages Zeichen durch ähnlich aussehende Zeichen; dieses Verhalten bildet die WorstFit-Angriffsfläche für Path Traversal, Argument Injection und RCE
  • Das Problem entsteht aus dem Zusammenspiel von ANSI-APIs, C/C++-Runtime, vom Compiler eingefügtem Startcode und der Nutzung nicht-breiter Zeichen-APIs durch Entwickler; betroffen sind die Pfade GetCommandLineA, GetEnvironmentVariableA, getenv und int main()
  • Bei CVE-2024-4577 wurde U+00AD in chinesischen/japanischen Codepages zu -, wodurch der PHP-CGI-Patch umgangen wurde; beim Filename Smuggling werden ¥, und der Fullwidth Slash zu / oder \\ und erzeugen Pfadverwirrung
  • Argument Splitting kann mit Fullwidth-Doppelanführungszeichen oder Yen-/Won-Zeichen Befehlszeilen-Trennzeichen erzeugen und so Argumente in CLI-Tools wie wget.exe, tar.exe, openssl.exe und java.exe einschleusen; mit den üblichen Argument-Escaping-Methoden in PHP, Python, Node.js und Rust ist das schwer zu verhindern
  • Zur Abmilderung sollte man die UTF-8-Option von Windows aktivieren oder als Entwickler die Wide Character API sowie Wide-Character-Pfade wie _wgetcwd, _wgetenv und wmain() verwenden; bis Microsoft UTF-8 nicht auf allen Windows-Editionen standardmäßig aktiviert, könnten ähnliche Probleme weiter auftreten

Windows-Encoding-Struktur und Best-Fit

  • Windows nutzte anfangs ANSI-Codepages, und je nach Sprachraum unterschieden sich die Codepages wie 1252, 932, 936, 949 und 950
    • ACP (ANSI Code Page) wird für die meisten Anwendungen und Systemeinstellungen wie Dateizugriffe und Umgebungsvariablen verwendet
    • OEMCP (Original Equipment Manufacturer Code Page) wird vor allem für Gerätekommunikation wie das Lesen/Schreiben in der Konsole verwendet
    • chcp zeigt nicht die ACP, sondern die OEMCP an und ist daher kein Mittel zur Prüfung der ACP, um die es in dieser Untersuchung geht
  • Windows wechselte Mitte der 1990er Jahre zu Unicode, und die zentralen APIs basieren heute auf UTF-16 und Wide Characters
    • Kern-APIs für Dateisystem, Systeminformationen und Textverarbeitung wurden auf Wide-Character-APIs umgestellt
    • UTF-8-Unterstützung existiert zwar, ist in den meisten Sprachen aber nicht standardmäßig aktiviert und wird im Artikel als Beta-Stadium beschrieben
  • Aus Gründen der Abwärtskompatibilität bietet die Windows-API sowohl ANSI- als auch Unicode-Versionen an
    • ANSI-APIs tragen den Suffix A, etwa GetEnvironmentVariableA
    • Unicode-APIs tragen den Suffix W, etwa GetEnvironmentVariableW
    • Wird eine ANSI-API aufgerufen, konvertiert Windows den internen UTF-16-String mit RtlUnicodeStringToAnsiString oder WideCharToMultiByte in einen ANSI-String

Wie Best-Fit zu WorstFit wird

  • Best-Fit bezeichnet das Verhalten, UTF-16-Zeichen, die sich in der Ziel-ANSI-Codepage nicht exakt darstellen lassen, auf ähnlich aussehende oder ähnlich wirkende Zeichen abzubilden
    • So wird etwa U+221E in Windows-1252 auf 8 abgebildet
    • √π⁷≤∞ kann nach dem Durchlaufen einer ANSI-API zu \"vp7=8\" werden
  • Das Mapping verhält sich je nach Codepage unterschiedlich
    • ¥ U+00A5 wird in der japanischen Codepage 932 auf \\ abgebildet
    • In der mitteleuropäischen Codepage 1250 wird es zu Y
    • In den meisten anderen Codepages bleibt es unverändert
  • Dieselbe Konvertierung tritt nicht nur bei direkten Windows-API-Aufrufen auf, sondern auch in CRT-Funktionen und über den gewöhnlichen main-Funktionspfad
    • In nicht-breiten CRT-Funktionen wie getenv wird die Best-Fit-Konvertierung angewendet
    • Auch wenn Argumente und Umgebungsvariablen in der Form int main(int argc, char* argv[], char* envp[]) empfangen werden, greift die Konvertierung ein
    • Grund ist die Kombination aus vom Compiler eingefügtem CRT-Startcode und der Nutzung von ANSI-Windows-APIs
  • Zur Prüfung der Mappings kann man auf Best-fit Mapping Grepper und die rohen WindowsBestFit-Mapping-Daten von Unicode.org verweisen

Erster WorstFit-Fall: PHP-CGI CVE-2024-4577

  • CVE-2024-4577 ist ein WorstFit-Angriffsfall, bei dem sich PHP-CGI-Server mit auf chinesische/japanische Codepages gesetzter Konfiguration allein mit einer Anfrage ?%ADs kompromittieren ließen
    • Betroffene Codepages sind 932 (Japanisch), 936 (Vereinfachtes Chinesisch) und 950 (Traditionelles Chinesisch)
    • Das problematische Zeichen ist ­ U+00AD
  • Die PHP-CGI-Schwachstelle von 2012 war eine Argument-Injection, die dadurch entstand, dass Apache den Query-String automatisch als erstes Argument des CGI-Programms behandelte
    • Mit angehängtem ?-s waren Quellcode-Leakage und RCE möglich
    • Der PHP-Patch stoppte das Parsen von Argumenten, wenn der Query-String mit einem Bindestrich begann
  • Durch Best-Fit wird das Soft Hyphen U+00AD in chinesischen/japanischen Codepages in - umgewandelt, wodurch der bestehende Patch umgangen wird
    • ?%ADs kann aus Sicht von PHP-CGI wie -s funktionieren
    • Dieser Fall brachte das Forschungsteam erstmals mit dem Begriff Best-Fit in Kontakt

Filename Smuggling: Wenn Pfadzeichen umgewandelt werden

  • Filename Smuggling ist ein Angriff, bei dem in Dateinamen enthaltene Unicode-Zeichen im ANSI-API-Pfad zu / oder \\ werden und so Path Traversal ermöglichen können
    • Relevante APIs sind unter anderem GetCurrentDirectoryA, getcwd, FindFirstFileA, findfirst* und GetFullPathNameA
    • Betroffene Codepages sind 874, 125x, 932 (JP) und 949 (KR)
    • Bedrohliche Zeichen sind U+FF0F, U+FF3C, ¥ U+00A5 (JP) und U+20A9 (KR)
  • d8.exe, die Developer Shell von Chrome V8, verwendet intern GetCurrentDirectoryA(), um das aktuelle Arbeitsverzeichnis zu ermitteln
    • Kann ein Arbeitsverzeichnis mit schädlichen Unicode-Zeichen erzeugt werden, wird es beim Zugriff über die ANSI-API in ein Path-Traversal-Payload umgewandelt
    • So ist beispielsweise ein unbeabsichtigter Zugriff auf C:/windows/win.ini möglich
  • Die Windows-Implementierung von mrubys Dir.getwd() hängt von der ANSI-CRT-Funktion _getcwd() ab
    • Der Rückgabewert kann kontaminiert werden
    • Daraus kann Path Traversal folgen

Cuckoo Sandbox: Von Path Traversal zu RCE

  • Der Zugriff von Python auf das Windows-Dateisystem konnte je nachdem, ob Zeichenketten wide oder narrow waren, die Wide API oder die ANSI API verwenden
    • Seit PEP 529 ist die Windows-Dateisystemkodierung auf UTF-8 standardisiert
    • Python 2 und Python 3 vor Python 3.6 blieben für WorstFit-Angriffe anfällig
  • Cuckoo Sandbox ist eine automatisierte Plattform zur Malware-Analyse, und die aktuelle offizielle Version hängt von Python 2.7 ab
    • Cuckoo besteht aus dem Cuckoo Host und einem VM-Cluster
    • Hochgeladene Samples werden isoliert in VMs ausgeführt, und Netzwerkpakete, abgelegte Dateien und Logs werden über eigene Mechanismen synchronisiert
  • Wenn Malware eine abgelegte Datei mit einem Unicode-Dateinamen erzeugt, kann bei der Pfadverarbeitung in Python auf dem Cuckoo Host Path Traversal auftreten
    • Das beispielhafte PoC erzeugt den Pfad AAAA\u00a5..\u00a5..\u00a5..\u00a5..\u00a5..\u00a5conf\u00a5cuckoo.conf
    • Nach Abschluss der Analyse wird ein Python-Dateizugriff ausgelöst, wenn der Nutzer in der Weboberfläche auf den Download-Button klickt
    • Der Cuckoo Host verarbeitet dann den konvertierten Pfad mit ../ und kann dem Angreifer sensible Daten senden
  • Der Angreifer kann cuckoo.conf herunterladen und sensible Informationen sammeln, die zur Berechnung der Flask-PIN nötig sind, um RCE auf dem Sandbox Host zu erreichen
    • Das Demo-Video ist als Video 11 verfügbar

Argument Splitting: Best-Fit verändert das Parsing der Kommandozeile

  • Argument Splitting ist ein Angriff, bei dem die Kommandozeichenfolge über die Ausgabe von GetCommandLineA oder über den non-Unicode-int main()-Pfad verändert wird, sodass Argumente getrennt werden
    • Relevante APIs und Pfade sind GetCommandLineA, int main()
    • Betroffene Codepages sind 874, 125x, 932(JP), 949(KR)
    • Kritische Zeichen sind U+FF02, U+FF3C, ¥ U+00A5(JP), U+20A9(KR)
  • Der Beispielcode in PHP führt wget.exe -q aus, nachdem die URL mit escapeshellarg() sicher eingefasst wurde, aber mit der Eingabe " --use-askpass=calc " lässt sich calc.exe starten
    • Dieselbe Eingabe wird auch dann nicht abgewehrt, wenn man auf Node.js, Rust oder Python wechselt
    • Es funktioniert sogar im Beispiel mit aktuellen Python-Versionen und subprocess.run(["wget", "-q", ...])
  • Windows übergibt an einen neuen Prozess die komplette Kommandozeile als einzelne Zeichenkette, die das ausführbare Programm selbst parst
    • Anders als bei UNIX-artigen Systemen wird nicht immer ein Argument-Array übergeben
    • Die API CreateProcess nimmt den Parameter lpCommandLine direkt entgegen
  • Bei üblichem Kommandozeilen-Parsing sind Leerzeichen und Tabs, doppelte Anführungszeichen und Backslashes entscheidend
    • Leerzeichen und Tabs trennen Argumente, wenn nicht der quote mode aktiv ist
    • " schaltet den quote mode um
    • \ maskiert in bestimmten Sequenzen doppelte Anführungszeichen und Backslashes
  • Die Standardbibliotheken der meisten Sprachen maskieren Benutzerargumente gemäß diesen Regeln, aber das Escaping endet vor der Best-Fit-Konvertierung
    • PHP escapeshellarg ersetzt doppelte Anführungszeichen durch Leerzeichen, setzt das Argument in Anführungszeichen und verarbeitet Backslashes
    • Python subprocess maskiert mit list2cmdline gemäß den Microsoft-CRT-Regeln für das Kommandozeilen-Parsing
    • Wenn danach bei der ANSI-Konvertierung U+FF02 in " U+0022 umgewandelt wird, ändert sich die ursprüngliche Kommandozeilensyntax
  • Auch Programme, die nur int main() verwenden, können anfällig sein
    • Der Compiler erzeugt mainCRTStartup im Binärprogramm, und diese Startfunktion ist mit der CRT-Bibliothek verbunden
    • Wenn die CRT intern die Kommandozeile über die ANSI API holt und parst, greift die Best-Fit-Konvertierung ein
    • Deshalb ist es selbst mit den Standardbibliotheken bestimmter Programmiersprachen schwer, den Angriff vollständig zu verhindern

Reale Fälle von Argument Splitting

  • ElFinder ist ein Open-Source-Web-Dateimanager mit PHP-Backend, der standardmäßig Windows-Server sowie das Erstellen und Entpacken von Archiven unterstützt
    • Die Archivverarbeitung ist über die Ausführung von Shell-Kommandos implementiert, und die Argumente werden mit escapeshellarg maskiert
    • Für das tar-Format wird das in Windows integrierte tar.exe verwendet
    • Mit einem tar-Dateinamen wie aaa" "--use-compress-program=calc" "bbb.tar lässt sich das Argument --use-compress-program einschleusen und beliebiger Befehlscode ausführen
    • Die Demo basiert auf einem auf Englisch konfigurierten Windows-Server mit Code Page 1252, und laut Zusammenfassung sollte sie auch auf 125x-Codepages und Code Page 874 funktionieren
    • Das Demo-Video ist als Video 12 verfügbar
  • Im Fall der in TortoiseGit verwendeten modifizierten plink.exe kann die Eingabe einer bösartigen URI in das Clone-Feld Codeausführung auslösen
  • RStudio unterstützt SVN-Versionsverwaltung; wenn sich in einem bösartig präparierten Ordner ein SVN-Projekt befindet, kann mit einem einzigen Klick der Rechner gestartet werden
  • Beim Fall von Microsoft Excel handelt es sich um CVE-2024-49026, das Argument Splitting mit der Windows-Funktion „Open-With“ kombiniert
    • Windows verwaltet für Dateierweiterungen eine Handler-Tabelle, die sich mit ftype und assoc prüfen lässt
    • Dateinamen werden Teil der Argumente des Handler-Programms, sodass sich der Angriff über den Dateinamen anwenden lässt
    • Ein Dateiname, bei dem Punkte, Slash, Backslash und doppelte Anführungszeichen in ihre fullwidth-Formen umgewandelt wurden, verursacht bei Excel.exe eine Argument-Injection
    • Excel selbst hat keine zusätzlichen Argumente, die sich gut für eine weitere Ausnutzung eignen, daher wurden NTLM Relay und RBCD/ADCS zusammen genutzt, um RCE zu erreichen
    • Das Demo-Video ist als Video 15 verfügbar

Verwirrung bei Umgebungsvariablen

  • Environment Variable Confusion entsteht dadurch, dass GetEnvironmentVariableA, GetEnvironmentStringsA und char *getenv() Best-Fit-konvertierte Versionen von Umgebungsvariablen zurückgeben
    • Betroffene Codepages und problematische Zeichen sind nicht abschließend bestimmt
    • Im Fall von Apache HTTPd ist der Bereich 0x00-0xFF relevant
  • Damit dieser Angriff funktioniert, müssen Umgebungsvariablen vom Nutzer kontrollierbar sein
    • Das ist etwa der Fall, wenn ein Elternprozess Informationen an einen von ihm erzeugten Kindprozess weitergibt
    • Bei CGI werden viele HTTP-Anfragedaten wie Query-String und HTTP-Header als Umgebungsvariablen übergeben
  • Das Beispiel zur Umgehung einer WAF behandelt den Fall, dass ein CGI-Skript wie ein Routing-Service arbeitet
    • In der Apache-Konfiguration gibt es eine Regel, die REQUEST_URI mit /admin ablehnt, um den Remote-Zugriff auf /cgi.pl/admin zu blockieren
    • Wegen des WorstFit-Verhaltens von Windows Perl ist eine Umgehung möglich, wenn Teile von admin durch Best-Fit-Äquivalente ersetzt werden
    • In Code Page 1250 wird à U+00E0 bei der ANSI-Konvertierung zu a
    • Die Anfrage /cgi.pl/%E0dmin erscheint serverseitigen Regeln als anderer Pfad, wird aber vom Perl-CGI-Skript bei der Auswertung von PATH_INFO über ANSI-APIs als /admin verarbeitet
  • Bei PHP-CGI unter Windows wurden in bestimmten Konfigurationen ein Oracle zur Dateiexistenzprüfung und potenziell LFI bestätigt
    • Ursache ist die Art, wie PATH_INFO und andere pfadbezogene Umgebungsvariablen verarbeitet werden
    • Eine Anfrage wie /index.php/foo/bar wird aus Sicht von Apache als Umgebungsvariablen wie REDIRECT_URL, REQUEST_URI, PATH_INFO und PATH_TRANSLATED übergeben
    • Allein anhand dieser Informationen lässt sich die Grenze zwischen dem PHP-Dateinamen und zusätzlichem PATH_INFO nicht eindeutig bestimmen; php-cgi.exe interpretiert dies
  • In japanischen Codepages führt die Nutzung von ¥ dazu, dass Webserver und PHP-CGI Pfade unterschiedlich interpretieren
    • Der Webserver behandelt /..¥..¥windows/win.ini/foo vollständig als zusätzliches PATH_INFO
    • PHP-CGI erhält einen konvertierten Wert wie REQUEST_URI=/index.php/..\\..\\windows/win.ini/foo und gerät bei der Trennung zwischen tatsächlicher PHP-Datei und PATH_INFO in Verwirrung
    • Unter Apache ermöglicht der Unterschied in den Antworten für nicht vorhandene und vorhandene Dateien ein File-Existence-Oracle
    • Wenn unter IIS die Direktive doc_root gesetzt ist, ermöglicht ein Pfad wie /index.php/..¥..¥..¥windows/win.ini/ ein Include von C:\\Windows\\win.ini und damit LFI
    • Ist die eingebundene Datei ausführbar oder enthält vom Nutzer kontrollierbaren Code, kann dies potenziell zu RCE führen; dieses Szenario wird in realen Anwendungen jedoch eher als seltener Bug eingestuft

Schwierigkeiten bei Offenlegung und Behebung

  • Das Forschungsteam meldete mehrere Probleme in Programmiersprachen, Open-Source-Projekten und eingebauten Windows-CLI-Programmen an die jeweiligen Upstream-Maintainer
    • Die meisten Diskussionen gab es bei Argument Splitting
    • Einige Anbieter betrachten bereits die Weitergabe von Nutzereingaben an die Kommandozeile selbst als Schwachstelle
  • Unklare Zuständigkeiten waren ebenfalls ein Problem
    • Der problematische Code verteilt sich auf das beim Kompilieren automatisch eingefügte mainCRTStartup() und interne ANSI-API-Aufrufe in MSVCRT/UCRT
    • Es ist schwer zu trennen, ob das Problem darin liegt, dass Entwickler kein wmain() verwenden, oder ob die CRT die Kommandozeile falsch aufteilt und main() dadurch falsche Argumente übergibt
    • Einige Projekte liefern nur Quellcode aus, während vorgebaute Windows-Executables von freiwilligen Dritten im Internet verteilt werden
  • Eine Behebung ist nicht so einfach, wie main() nur durch eine Wide-Character-Version zu ersetzen
    • Wenn sich die Funktionssignatur ändert, müssen Variablendefinitionen und die Argument-Parsing-Logik von char * auf wchar_t * umgeschrieben werden
    • Dieser Prozess ist mühsam und fehleranfällig
  • Curl antwortete, es handle sich um Windows-Funktionalität und man plane keine Behebung; die von Microsoft portierte Curl-Version ändert den Entry Point auf wmain(), sodass das in Windows integrierte curl.exe nicht betroffen ist
    • Die offiziellen Build-Binaries von Curl sind von Argument-Splitting-Angriffen betroffen
    • Der vollständige Bericht ist auf HackerOne veröffentlicht
  • OpenSSL kann Argumente über die Umgebungsvariable OPENSSL_WIN32_UTF8 im Wide-Character-Format verarbeiten
    • Ursprünglich sollte das Probleme bei der UTF-8-Anzeige in der UI beheben, mildert aber auch Argument-Splitting-Angriffe
    • Bei der Standardnutzung von OpenSSL wissen Entwickler oft nicht, dass sie diese Umgebungsvariable setzen müssen; über das Argument -engine ist beliebige Codeausführung möglich
  • Die offizielle Perl-Distribution stellt keine vorgebauten Windows-Executables bereit; häufig genutzt werden Drittanbieter-Installer wie Strawberry Perl und ActiveState Perl
    • Beide Distributionen sind von Argument-Splitting-Angriffen betroffen
    • Nach Gesprächen mit den Perl-Maintainern lautete das Fazit, es handle sich eher um einen Microsoft- als um einen Perl-Bug; der Fall ist derzeit ungelöst
  • An Microsoft wurden drei Fälle an das MSRC gemeldet, die zunächst alle wegen Nichterfüllung der Schweregradkriterien abgelehnt wurden
    • Erst nach mehreren Wiederaufnahmen wurde der Excel-Fall im dritten Anlauf akzeptiert
    • Die anderen Fälle sind bis heute ungelöst
    • Das MSRC antwortete, dass diese Fälle auf einer Schwachstelle beruhen, bei der eine separate Anwendung nicht vertrauenswürdige Eingaben in die Kommandozeile einfügt und ausführt; die Technik selbst, die eine Ausnutzung ermöglicht, erfülle nicht die Kriterien für eine Schwachstelle
  • Auch CERT/CC wurde um Hilfe gebeten; einige Monate später ergänzte Microsoft die Dokumentation von GetCommandLineA um eine Sicherheitswarnung
    • Die Warnung wurde nur bei GetCommandLineA ergänzt, obwohl es weitere ANSI-APIs gibt, bei denen Vorsicht nötig ist

Gemeldete betroffene Ziele und Status

  • Im Verlauf der Offenlegung wurden folgende Punkte bestätigt und gemeldet
    • 2024/05/07: PHP php-cgi.exeCVE-2024-4577
    • 2024/06/13: Curl Official BuildWon’t Fix
    • 2024/06/13: Apache Subversion svn.exeCVE-2024-45720
    • 2024/06/16: Microsoft Tar tar.exe — Won’t Fix
    • 2024/06/19: Microsoft Excel excel.exeCVE-2024-49026
    • 2024/06/19: Microsoft PhoneBook rasphone.exe — Won’t Fix
    • 2024/06/19: Oracle Java java.exe — Pending Fix
    • 2024/06/19: Perl perl.exe — Won’t Fix
    • 2024/07/15: Perforce p4.exeCVE-2024-8067
    • 2024/08/05: PostgreSQL psql.exe — Won’t Fix
    • 2024/08/08: Putty plink.exe — Fixed
    • 2024/08/19: OpenSSL openssl.exe — Other
    • 2024/08/19: wkhtmltopdf wkhtmltopdf.exe — EOL
    • 2024/08/19: GNU Wget — No Reply

Gegenmaßnahmen und verbleibende Angriffsfläche

  • Da WorstFit ein Problem auf Betriebssystemebene ist, könnten ähnliche Probleme weiterhin immer wieder auftauchen, bis Microsoft UTF-8 in allen Windows-Editionen standardmäßig aktiviert
  • Nutzer können prüfen, ob die UTF-8-Option in Windows aktiviert ist, und sie einschalten
    • Diese Funktion ist noch als Beta gekennzeichnet, und ob sie Nebenwirkungen hat, ist nicht sicher
  • Entwickler sollten nach Möglichkeit die Wide Character API verwenden
    • Auch die CRT bietet Wide-Character-Versionen wie _wgetcwd und _wgetenv an
    • Wenn weiterhin Nicht-Wide-Pfade verwendet werden, könnte die interne Implementierung ANSI-APIs aufrufen und dadurch für WorstFit-Angriffe anfällig sein
  • Aufgrund der Abwärtskompatibilität von Windows könnte es weitere Stellen geben, an denen sich ANSI-APIs verbergen
    • Beispielsweise könnten Windows-Registry-Abfragen wie RegQueryValueA betroffen sein, allerdings müssten dafür konkrete Angriffsszenarien gefunden werden
    • Das Forschungsteam beobachtete Best-Fit-Verhalten auch in Active Directory

1 Kommentare

 
GN⁺ 2025-01-10
Meinungen auf Hacker News
  • Das ist ein ziemlich kniffliges Problem. Microsofts „Best Fit“-Code-Mapping ist ein öffentlicher, aber im Grunde „gefühlsbasierter“ Mapper, der den weiten Unicode-Raum nach ASCII überführt, und er steckt im ganzen System
    Dieser Mapper wird standardmäßig an sehr vielen Stellen eingebunden, und angesichts dessen, wie Microsoft Abwärtskompatibilität versteht, scheint er auch weiterhin enthalten bleiben zu müssen. Exploits entstehen meist daraus, dass ungewöhnliche Codepoints „gefühlt“ auf Slashs, Bindestriche oder Anführungszeichen gemappt werden. Innerhalb moderner Sprachen werden sie als korrektes Unicode geprüft, aber sobald sie an Shell-Befehle oder die Win32-API übergeben werden, findet nach der Übergabe der Kontrolle eine andere Art der reduzierenden Konvertierung statt. Wie der curl-Maintainer sagte: „curl ist hier das Opfer“; die Frage ist, wer der Täter ist. Wenn ein Server Nutzereingaben beim Validieren anders glattbügelt als beim Übergeben an Systembibliotheken, entsteht am Ende ein Problem. Eine Option, die Best-Fit-Konvertierung auf Win32-Seite abzuschalten, könnte eine Lösung sein, aber ich bin kein Windows-Experte, also ist das nur eine Vermutung. Selbst dann interagiert man weiter mit offiziellen APIs oder Software, die das noch nicht abgeschaltet hat

    • Der Opt-out besteht darin, die Unicode-Windows-API zu verwenden, also Funktionen, die auf "w" enden, nicht auf "a". Dieser Ansatz löst, wenn man das Präfix "\\?\" verwendet oder das Manifest korrekt setzt, zugleich auch das Problem mit Pfaden über 260 Zeichen; das ist seit Windows XP möglich und wird seitdem empfohlen
      Ich weiß nicht recht, warum Nicht-Unicode-APIs immer noch so verbreitet sind. Es ist schwer vorstellbar, dass das aus dem Wunsch heraus geschieht, Windows 98 oder Windows 2000 zu unterstützen
    • Windows hat seit Windows XP Manifestdateien als Möglichkeit, Legacy-Verhalten abzuschalten. Wenn kein Manifest vorhanden war, gab selbst GetWindowsVersion meines Wissens nicht die aktuelle Version zurück. Hier einen Opt-out hinzuzufügen und ihn irgendwann zum Standard in Visual Studio zu machen, scheint nicht besonders schwierig
      Was außerdem nötig ist, ist eine Art Linting. In modernen Anwendungen gibt es normalerweise keinen Grund, ANSI-WinAPI-Funktionen aufzurufen. Man könnte auch die Locale auf UTF-8 setzen und nur die 8-Bit-Funktionen verwenden, aber ich weiß nicht, wie gut das funktioniert. Soweit ich weiß, gibt es auch einige Einstellungen und Header, die dafür sorgen, dass argv, printf und std::cout mit UTF-8 arbeiten und dass nur UTF-8/UTF-16-Konvertierungsfunktionen für die WinAPI verwendet werden, ohne seltsame Konvertierungen. Microsoft sollte diesen Ablauf an einer Stelle dokumentieren
    • Ob Sicherheitslücke oder nicht: Wenn curl unter Windows Unicode-Argumente nicht korrekt verarbeitet, ist es auch ein Bug in curl
    • Die lose Zuordnung von Codepoints zu Zeichen hat mich bei Unicode schon immer gestört
  • Das ist bis zu einem gewissen Grad absehbar, war aber selbst für mich neu, obwohl ich ungefähr zehn Jahre lang Windows-Entwicklung und Wine-API-Hacking gemacht habe, als die W/A-Verwirrung aufkam
    Windows ist wie das Kartenspiel Munchkin: Wenn mehrere Features zufällig ineinandergreifen, können sie sich zu unglaublich zufälligen und mächtigen Exploits verbinden. Es ist erfreulich, dass das ANSI-Subsystem auf UTF-8 umgestellt wird; theoretisch kann das viele solcher Probleme entschärfen. Ich frage mich, ob das Rust-Team an der Prozess-Erzeugungs-API noch eine weitere Korrektur vornehmen muss

    • Die Rust-Standardbibliothek verwendet standardmäßig kaum ANSI-APIs. Der Artikel zeigte keinen Angriff, der bei Rust funktioniert; falls es einen solchen Angriff gibt, sollte man ihn unbedingt melden
      Natürlich kann Rust nicht kontrollieren, was jenseits von Prozessgrenzen passiert. Wenn eine von Rust gestartete Anwendung ANSI-APIs verwendet, entsteht das Problem dort, aber das liegt in der Verantwortung dieser Anwendung
  • „ANSI schrittweise abzuschaffen und die Verwendung der Wide-Character-API zu empfehlen“ war, wenn ich mich richtig erinnere, seit NT 3.5 Microsofts offizielle Position
    Leider ist eines der großen Hindernisse die Implementierung von Microsofts C/C++-Runtime-Bibliothek msvcrt.dll. Nichtstandardisierte Wide-Funktionen wie _wfopen() und _wgetenv() verwenden intern die W-Funktionen der WinAPI, aber die standardmäßigen Narrow-Funktionen wie fopen() und getenv() verwenden einfach die A-Funktionen, statt in die Wide-Versionen zu konvertieren. Und die A-Funktionen melden Unicode-Konvertierungsfehler normalerweise nicht, sondern überschreiben sie per Best-Fit. Wer in C geschriebene Software nach Windows portiert, möchte nicht alle Standardfunktionen durch Microsoft-spezifische, nicht portable Funktionen ersetzen. Ab diesem Punkt ist es praktisch eine komplette Neuschreibung

    • Mein Eindruck nach der Lektüre der Microsoft-Dokumentation in den letzten zwei Jahren war das Gegenteil: Man solle im Anwendungsmanifest activeCodePage auf UTF-8 setzen und nur „ANSI“-Funktionen verwenden
    • In portablem Code definiert man bei Windows-Builds Standardfunktionen wie main und fopen per #define auf ihre Wide-Gegenstücke um
      Dann kann man char* und unverzierte String-Literale nicht einfach so verwenden, also definiert man einen tchar-Typ, der unter Linux char und unter Windows wchar_t ist, sowie das Makro _T() für String-Literale. Im Großen und Ganzen funktioniert das ohne viel Nachdenken gut
    • Was heutzutage wirklich nervt: Wenn man bei Google nach der Win32-API sucht, erscheint immer zuerst die -A-Variante, nicht die -W-Variante. Ich weiß nicht, ob in robots.txt irgendetwas Seltsames steht, aber es ist merkwürdig, dass eine API, bei der für neuen Code die -W-Variante empfohlen wird, standardmäßig die Legacy-API zurückliefert
    • Microsofts C/C++-Runtime msvcrt.dll wurde durch die Universal C Runtime (UCRT)[1] ersetzt, und die UCRT ist C99-konform
    • Windows hätte eine API bereitstellen sollen, die Pfadnamen ohne diesen dämlichen Encoding-Umgang einfach als Bytefolgen behandelt. Bei der Einführung von UNC-Pfaden hätte man das wohl so machen können
  • Es gibt zwei Möglichkeiten, in selbst geschriebenen Anwendungen oder gepatchten EXE-Dateien die „Ansi“-Codepage tatsächlich auf UTF-8 zu erzwingen
    Die eine ist die Verwendung einer Manifestdatei; das funktioniert ab bestimmten Builds von Windows 10. Man kann es nach dem Build auch auf beliebige EXE-Dateien anwenden und so UTF-8-Unterstützung in ein Programm hineinzwängen. Besonders nützlich ist das für Programme im Konsolenmodus. Die andere Möglichkeit ist, den Hack zu verwenden, den „App Locale“-artige Tools nutzen. Eine Methode umfasst den Aufruf undokumentierter Funktionen in NTDLL. Ich weiß nicht genau, welche Funktionen nötig sind, aber RtlInitNlsTables und RtlResetRtlTranslations könnten damit zusammenhängen

  • Ich bin mir nicht sicher, ob Microsoft UTF-8 in allen Windows-Editionen standardmäßig aktivieren wird. Es gibt viele alte Anwendungen, die eine bestimmte Codepage oder 1 Byte pro Zeichen voraussetzen und dadurch kaputtgehen könnten.
    Subtiler ist, dass es auch Anwendungen gibt, die beim Konvertieren von Wide-Zeichen nach ANSI annehmen, dass die Bytezahl nicht zunimmt, und deshalb vorhandene Buffer wiederverwenden. Bei UTF-8 stimmt das nicht; bei den meisten bisherigen Codepages stimmte es weitgehend, wodurch neue Schwachstellen entstehen könnten. Viel weniger dürfte kaputtgehen, wenn man stattdessen in den Win32-xxxA-APIs die Best-Fit-Logik entfernt und nicht abbildbare Zeichen durch ein Zeichen wie x ersetzt, das keine übliche Meta-Bedeutung hat.

    • Ein Beispiel für so eine Anwendung ist Adobe After Effects[0]. Zumindest war das früher so; inzwischen nutze ich Windows nicht mehr.
      [0] https://tambre.ee/blog/adobe_after_effects_windows_utf-8/
    • Falls es das noch nicht gibt, könnte man vielleicht eine OS-API-Version einführen, sodass neue/aktualisierte Apps, die auf eine neue API-Version oder ein neues SDK zielen, standardmäßig UTF-8 annehmen. Unterhalb einer bestimmten API-Version könnte man einen Legacy-Modus emulieren. Windows hat bereits das Konzept von Shims, die das Verhalten verschiedener Windows-Versionen nachahmen.
    • Auch unter Windows vor UTF-8 gab es schon das Problem, dass Apps sich merkwürdig verhielten, wenn man die Standard-Codepage änderte. Daher ist es sinnvoll, Nutzern eine UTF-8-Option zu geben.
      Angesichts der Probleme, die Best-Fit-Mappings verursachen, wäre es auch sinnvoll, das zum Standard zu machen; Microsoft müsste Nutzern aber dabei helfen, alte Software weiterhin einfach auszuführen. Eine weniger sinnvolle Methode wäre, in den Best-Fit-Mappings alle Zuordnungen zu „speziellen“ ASCII-Zeichen zu entfernen, doch das hilft Apps mit statisch gelinkter CRT nicht. Da es die Schwachstellen auch nicht behebt, ist das keine gute Lösung. Manchmal sind Sicherheitslücken der Antrieb, um Brüche der Abwärtskompatibilität durchzusetzen.
  • Microsoft kennt dieses Problem mindestens seit einem Jahr. Denn sie haben mit CA2101[1] eine spezielle Codeanalyse-Regel veröffentlicht, die ausdrücklich von der Nutzung von Best-Fit-Mapping abrät.
    In der Regelbeschreibung wurden Sicherheitslücken erwähnt, die Details aber absichtlich vage gehalten.
    [1] https://learn.microsoft.com/en-us/dotnet/fundamentals/code-a...

  • Man muss nicht alles von char * auf wchar * umstellen. Man kann die erhaltenen Wide-Zeichen nach UTF-8 konvertieren oder, wenn man auch ungültige Sequenzen wie ungepaarte Surrogate erlauben will, in etwas wie Rusts WTF-8 umwandeln und anschließend weiter char verwenden.
    Natürlich muss man aufpassen, ANSI- oder OEMCP-Strings nicht mit UTF-8-Strings zu vermischen; wenn man einfach nur UTF-8 verwendet, ist es leicht. Genau diesen Ansatz empfiehlt die klassische Website https://utf8everywhere.org/.

  • Auf meinem privaten Windows-Rechner hatte ich den UTF-8-Modus schon seit einigen Jahren aktiviert und bin diesem Bug dadurch zufällig entgangen. Es ist die Einstellung, die unten im Artikel genannt wird.
    Ich hatte sie eingeschaltet, weil alte ausländische Spiele kaputte Zeichen angezeigt haben; obwohl sie als „Beta“ markiert ist, habe ich keine Bugs oder Nebenwirkungen bemerkt.

    • Interessant, aber bei mir hat diese Checkbox nichts bewirkt, außer dass sie viel zu viele zufällige Apps zum Absturz gebracht hat. Ob es gut funktioniert, scheint davon abzuhängen, welche Standard-Codepage der Nutzer hat, wenn sie ausgeschaltet ist.
    • Ich habe gerade die Option „Beta: Use Unicode UTF-8 for worldwide language support“ aktiviert. Es dürfte interessant werden, wie viele Apps kaputtgehen.
  • Ich hatte mich gefragt, ob die Beta-Checkbox dasselbe ist wie ActiveCodePage im Manifest auf UTF-8 zu setzen, aber die Dokumentation[0] stellt klar, dass GDI nicht der prozessspezifischen Codepage folgt, sondern nur der einzigen globalen Codepage, die diese Checkbox setzt.
    Es ist etwas schade, dass man in der eigenen App über die *A-APIs nicht vollständig in UTF-8 opt-in kann. Trotzdem halte ich es für die im Artikel hervorgehobenen Probleme weiterhin für einen brauchbaren Workaround oder eine Maßnahme der Defense in Depth.
    [0] https://learn.microsoft.com/en-us/windows/apps/design/global...

  • Meine Güte. Ich wusste zwar, dass die Windows-API solche Best-Fit-Konvertierungen anbietet, aber nicht, dass das bei meiner Standard-Codepage 949[1] das Standardverhalten mehrerer ANSI-Funktionen ist.
    An diesem Punkt sollte man es wie gets einfach verbieten. [1] Ich weiß, dass es die UTF-8-Codepage 65001 gibt. Sie war lange Zeit wirklich praktisch unbenutzbar und hat auch heute noch Kompatibilitätsprobleme.