2 points par GN⁺ 2025-01-10 | 1 commentaires | Partager sur WhatsApp
  • La conversion de caractères Best-Fit de Windows remplace des caractères par d’autres qui leur ressemblent lors du passage d’une chaîne UTF-16 vers une page de code ANSI, et ce comportement devient une surface d’attaque WorstFit menant à des Path Traversal, des injections d’arguments et du RCE
  • Le problème vient d’une combinaison entre les API ANSI, le runtime C/C++, le code de démarrage injecté par le compilateur et l’usage d’API de caractères non wide par les développeurs ; les chemins GetCommandLineA, GetEnvironmentVariableA, getenv et int main() sont concernés
  • CVE-2024-4577 a contourné un correctif de PHP-CGI dans les pages de code chinoises/japonaises, car U+00AD y est converti en -, tandis que le Filename Smuggling fait basculer ¥, et la slash pleine chasse en / ou \, créant une confusion de chemin
  • Argument Splitting permet à des guillemets doubles pleine chasse ou à des signes yen/won de produire des caractères de parsing de ligne de commande, injectant des arguments dans des outils CLI comme wget.exe, tar.exe, openssl.exe ou java.exe, et les échappements d’arguments classiques de PHP, Python, Node.js et Rust ne suffisent généralement pas à l’empêcher
  • Pour atténuer le problème, il faut activer l’option UTF-8 de Windows ou utiliser les Wide Character API ainsi que des chemins wide comme _wgetcwd, _wgetenv et wmain() ; tant que Microsoft n’activera pas UTF-8 par défaut sur toutes les éditions de Windows, des problèmes similaires pourront se répéter

Structure d’encodage de Windows et Best-Fit

  • Windows utilisait à l’origine des pages de code ANSI, avec des pages de code différentes selon les langues, comme 1252, 932, 936, 949 ou 950
    • L’ACP (ANSI Code Page) est utilisée pour la plupart des applications et réglages système, notamment pour les opérations sur les fichiers et les variables d’environnement
    • L’OEMCP (Original Equipment Manufacturer Code Page) est surtout utilisée pour les communications avec les périphériques, comme la lecture/écriture dans la console
    • chcp affiche l’OEMCP et non l’ACP ; ce n’est donc pas un moyen de vérifier l’ACP, qui est au cœur de cette recherche
  • Windows est passé à Unicode au milieu des années 1990, et ses API principales utilisent aujourd’hui des wide characters basés sur UTF-16
    • Les API principales pour le système de fichiers, les informations système et le traitement de texte ont basculé vers des API wide character
    • La prise en charge d’UTF-8 existe, mais elle n’est pas activée par défaut dans la plupart des langages, et l’article la décrit comme étant au stade bêta
  • Pour préserver la rétrocompatibilité, les API Windows proposent à la fois des versions ANSI et Unicode
    • Les API ANSI portent le suffixe A, comme GetEnvironmentVariableA
    • Les API Unicode portent le suffixe W, comme GetEnvironmentVariableW
    • Lorsqu’une API ANSI est appelée, Windows convertit les chaînes internes UTF-16 en chaînes ANSI via RtlUnicodeStringToAnsiString ou WideCharToMultiByte

Comment Best-Fit devient WorstFit

  • Best-Fit est le mécanisme qui mappe un caractère UTF-16 vers un caractère visuellement ou conceptuellement proche quand il ne peut pas être représenté exactement dans la page de code ANSI cible
    • Par exemple, en Windows-1252, U+221E est mappé vers 8
    • √π⁷≤∞ peut devenir "vp7=8" après passage par une API ANSI
  • Le mapping varie selon la page de code
    • ¥ U+00A5 est mappé vers \ dans la page de code japonaise 932
    • Dans la page de code d’Europe centrale 1250, il est mappé vers Y
    • Dans la plupart des autres pages de code, il reste inchangé
  • Cette même conversion se produit non seulement lors d’appels directs aux API Windows, mais aussi dans les fonctions CRT et dans le chemin classique de main
    • La conversion Best-Fit s’applique dans des fonctions CRT non wide comme getenv
    • Elle intervient aussi quand les arguments et variables d’environnement sont reçus sous la forme int main(int argc, char* argv[], char* envp[])
    • Cela vient de la combinaison entre le code de démarrage CRT injecté par le compilateur et l’usage d’API Windows ANSI
  • Pour vérifier les mappings, on peut consulter Best-fit Mapping Grepper et les données brutes de mapping WindowsBestFit de Unicode.org

Premier cas WorstFit : PHP-CGI CVE-2024-4577

  • CVE-2024-4577 est un cas d’attaque WorstFit qui permettait de compromettre un serveur PHP-CGI configuré avec des pages de code chinoises/japonaises via une simple requête ?%ADs
    • Les pages de code concernées sont 932 (japonais), 936 (chinois simplifié) et 950 (chinois traditionnel)
    • Le caractère menaçant est ­ U+00AD
  • La vulnérabilité PHP-CGI de 2012 était une injection d’arguments causée par le traitement automatique de la chaîne de requête par Apache comme premier argument du programme CGI
    • En ajoutant ?-s, il était possible de divulguer le code source de la page et d’obtenir du RCE
    • Le correctif PHP consistait à arrêter le parsing des arguments si la chaîne de requête commençait par un tiret
  • À cause de Best-Fit, le soft hyphen U+00AD est converti en - dans les pages de code chinoises/japonaises, ce qui contourne le correctif existant
    • ?%ADs peut être interprété comme -s du point de vue de PHP-CGI
    • C’est à partir de ce cas que l’équipe de recherche a découvert pour la première fois le terme Best-Fit

Filename Smuggling : quand les caractères de chemin sont convertis

  • Filename Smuggling est une attaque dans laquelle des caractères Unicode présents dans un nom de fichier deviennent / ou \ dans un chemin passant par une API ANSI, ce qui peut provoquer une traversée de répertoires
    • Les API concernées incluent GetCurrentDirectoryA, getcwd, FindFirstFileA, findfirst* et GetFullPathNameA
    • Les pages de code affectées sont 874, 125x, 932 (JP) et 949 (KR)
    • Les caractères menaçants sont U+FF0F, U+FF3C, ¥ U+00A5 (JP) et U+20A9 (KR)
  • d8.exe, le Developer Shell de Chrome V8, utilise GetCurrentDirectoryA() dans son implémentation interne pour obtenir le répertoire de travail courant
    • S’il est possible de créer un répertoire de travail contenant des caractères Unicode malveillants, l’accès via une API ANSI peut les transformer en charge utile de traversée de chemin
    • L’exemple donné montre un accès non intentionnel à C:/windows/win.ini
  • L’implémentation Windows de Dir.getwd() dans mruby dépend de la fonction CRT ANSI _getcwd()
    • La valeur renvoyée peut être altérée
    • Cela peut déboucher sur une Path Traversal

Cuckoo Sandbox : de la traversée de chemin au RCE

  • L’accès de Python au système de fichiers Windows pouvait utiliser l’API wide ou l’API ANSI selon que la chaîne était wide ou narrow
    • Après PEP 529, l’encodage du système de fichiers Windows a été standardisé sur UTF-8
    • Python 2 et Python 3 avant Python 3.6 restaient vulnérables aux attaques WorstFit
  • Cuckoo Sandbox est une plateforme automatisée d’analyse de malware, et sa dernière version officielle dépend de Python 2.7
    • Cuckoo se compose de Cuckoo Host et d’un cluster de VM
    • Les échantillons téléversés sont exécutés de manière isolée dans une VM, et les paquets réseau, fichiers déposés et logs sont synchronisés par son propre mécanisme
  • Si un malware crée un fichier déposé avec un nom de fichier Unicode, une traversée de chemin peut se produire dans le traitement des chemins Python du Cuckoo Host
    • Le PoC d’exemple crée le chemin AAAA\u00a5..\u00a5..\u00a5..\u00a5..\u00a5..\u00a5conf\u00a5cuckoo.conf
    • Une fois l’analyse terminée, lorsque l’utilisateur clique sur le bouton de téléchargement dans l’interface web, une opération de fichier Python est déclenchée
    • Le Cuckoo Host traite alors un chemin converti contenant ../ et peut envoyer des données sensibles à l’attaquant
  • L’attaquant peut télécharger cuckoo.conf et collecter des informations sensibles nécessaires au calcul du PIN Flask, afin d’obtenir un RCE sur le Sandbox Host
    • La vidéo de démonstration est disponible ici : Video 11

Argument Splitting : le Best-Fit qui modifie l’analyse de la ligne de commande

  • Argument Splitting est une attaque où la chaîne de ligne de commande est modifiée puis les arguments sont séparés, via la sortie de GetCommandLineA ou le chemin non-Unicode int main()
    • API et chemins concernés : GetCommandLineA, int main()
    • Pages de code affectées : 874, 125x, 932 (JP), 949 (KR)
    • Caractères menaçants : U+FF02, U+FF3C, ¥ U+00A5 (JP), U+20A9 (KR)
  • Un exemple en PHP exécute wget.exe -q après avoir encapsulé l’URL de façon sûre avec escapeshellarg(), mais l’entrée " --use-askpass=calc " permet d’exécuter calc.exe
    • La même entrée ne se trouve pas bloquée non plus en remplaçant le code par Node.js, Rust ou Python
    • Cela fonctionne aussi dans un exemple récent de Python avec subprocess.run(["wget", "-q", ...])
  • Windows transmet la ligne de commande complète à un nouveau processus sous la forme d’une seule chaîne, que l’exécutable analyse lui-même
    • Contrairement aux systèmes de type UNIX, il ne s’agit pas d’une structure où un tableau d’arguments est toujours transmis
    • L’API CreateProcess reçoit directement le paramètre lpCommandLine
  • Dans l’analyse habituelle d’une ligne de commande, les caractères importants sont l’espace et la tabulation, le guillemet double et le backslash
    • L’espace et la tabulation séparent les arguments quand le mode quote n’est pas actif
    • " bascule le mode quote
    • \ échappe le guillemet double et le backslash dans certaines séquences
  • Les bibliothèques standard de la plupart des langages échappent les arguments utilisateur selon ces règles, mais l’échappement se termine avant la conversion Best-Fit
    • En PHP, escapeshellarg remplace le guillemet double par un espace, entoure l’argument de guillemets et traite le backslash
    • En Python, subprocess utilise list2cmdline pour échapper selon les règles d’analyse de ligne de commande du CRT Microsoft
    • Ensuite, si lors de la conversion ANSI U+FF02 devient " U+0022, la syntaxe initiale de la ligne de commande est modifiée
  • Les programmes qui n’utilisent que int main() peuvent aussi être vulnérables
    • Le compilateur génère mainCRTStartup dans le binaire, et cette fonction de démarrage est liée à la bibliothèque CRT
    • Si l’intérieur de la CRT récupère et analyse la ligne de commande via une API ANSI, la conversion Best-Fit entre en jeu
    • À cause de ce comportement, il est difficile de bloquer complètement l’attaque avec la seule bibliothèque standard d’un langage donné

Cas réels d’Argument Splitting

  • ElFinder est un gestionnaire de fichiers web open source avec backend PHP, qui prend en charge par défaut les serveurs Windows ainsi que la création et l’extraction d’archives
    • Le traitement des archives est implémenté via l’exécution de commandes shell, et les arguments sont échappés avec escapeshellarg
    • Le format tar est traité avec tar.exe, intégré à Windows
    • Un nom de fichier tar comme aaa" "--use-compress-program=calc" "bbb.tar permet d’injecter l’argument --use-compress-program et d’exécuter une commande arbitraire
    • La démonstration est faite sur un serveur Windows configuré en anglais, avec la page de code 1252, et il est indiqué que cela devrait aussi fonctionner sur les pages de code 125x et la page de code 874
    • La vidéo de démonstration est disponible ici : Video 12
  • Dans le cas du plink.exe modifié utilisé par TortoiseGit, l’injection d’un URI malveillant dans l’entrée de clone peut déclencher une exécution de code
  • RStudio prend en charge la gestion de versions SVN, et si un projet SVN se trouve dans un dossier malveillamment préparé, un seul clic peut lancer la calculatrice
  • Le cas de Microsoft Excel est CVE-2024-49026, qui combine Argument Splitting et la fonction « Open-With » de Windows
    • Windows maintient une table de handlers par extension de fichier, que l’on peut vérifier avec ftype et assoc
    • Comme le nom de fichier devient une partie des arguments du programme handler, l’attaque peut être appliquée via le nom de fichier
    • Un nom de fichier où les points, slashs, backslashes et guillemets doubles sont remplacés par leurs formes fullwidth provoque une injection d’arguments dans Excel.exe
    • Excel lui-même n’ayant pas d’arguments adaptés à une exploitation supplémentaire, NTLM Relay et RBCD/ADCS sont utilisés ensemble pour obtenir un RCE
    • La vidéo de démonstration est Video 15

Confusion autour des variables d’environnement

  • La confusion autour des variables d’environnement survient lorsque GetEnvironmentVariableA, GetEnvironmentStringsA et char *getenv() renvoient une version convertie en Best-Fit des variables d’environnement
    • Les pages de code affectées et les caractères problématiques ne sont pas spécifiés
    • Dans le cas d’Apache HTTPd, la plage 0x00-0xFF est concernée
  • Pour que cette attaque fonctionne, les variables d’environnement doivent pouvoir être contrôlées par l’utilisateur
    • C’est notamment le cas lorsqu’un processus parent transmet des informations à un processus enfant qu’il crée
    • En CGI, une grande partie des informations de la requête HTTP, comme la query string et les en-têtes HTTP, est transmise via des variables d’environnement
  • L’exemple de contournement de WAF traite d’un scénario où le script CGI agit comme un service de routage
    • La configuration Apache contient une règle qui rejette REQUEST_URI s’il inclut /admin, afin de bloquer l’accès distant à /cgi.pl/admin
    • En raison du comportement WorstFit de Perl sous Windows, il est possible de contourner cette règle en remplaçant une partie de admin par un équivalent Best-Fit
    • Dans la page de code 1250, à U+00E0 est transformé en a lors de la conversion ANSI
    • Une requête vers /cgi.pl/%E0dmin apparaît comme un chemin différent pour les règles côté serveur, mais si le script CGI Perl lit PATH_INFO via une API ANSI, il la traite comme /admin
  • Avec PHP-CGI sur Windows, un oracle de présence de fichier et une potentielle LFI ont été observés dans certaines configurations
    • Le problème vient de la manière dont PATH_INFO et d’autres variables d’environnement liées au chemin sont traités
    • Une requête vers /index.php/foo/bar est transmise par Apache via des variables d’environnement comme REDIRECT_URL, REQUEST_URI, PATH_INFO et PATH_TRANSLATED
    • À partir de ces seules informations, il est difficile de distinguer clairement le nom du fichier PHP de la partie PATH_INFO ajoutée, et c’est php-cgi.exe qui en fait l’interprétation
  • Dans une page de code japonaise, l’utilisation de ¥ entraîne une divergence d’interprétation du chemin entre le serveur web et PHP-CGI
    • Le serveur web traite l’ensemble de /..¥..¥windows/win.ini/foo comme un PATH_INFO supplémentaire
    • PHP-CGI reçoit une valeur convertie comme REQUEST_URI=/index.php/..\\..\\windows/win.ini/foo, ce qui crée une confusion lorsqu’il tente de distinguer le fichier PHP réel de PATH_INFO
    • Avec Apache, la différence de réponse entre un fichier inexistant et un fichier existant permet un oracle de présence de fichier
    • Sous IIS, si la directive doc_root est définie, un chemin comme /index.php/..¥..¥..¥windows/win.ini/ permet d’inclure et de lire C:\Windows\win.ini, ce qui constitue une LFI
    • Si le fichier inclus est exécutable ou contient du code contrôlé par l’utilisateur, cela peut potentiellement mener à une RCE, mais ce scénario est classé comme un bug plutôt rare dans des applications réelles

Difficultés de la divulgation et de la correction

  • L’équipe de recherche a signalé plusieurs problèmes dans des langages de programmation, des projets open source et des programmes CLI intégrés à Windows à leurs maintainers upstream respectifs
    • Le plus gros débat a porté sur Argument Splitting
    • Certains éditeurs estiment que le simple fait de transmettre une entrée utilisateur à la ligne de commande constitue déjà la vulnérabilité
  • L’attribution des responsabilités posait aussi problème
    • Le code en cause s’étend entre mainCRTStartup(), inséré automatiquement à la compilation, et les appels d’API ANSI internes à MSVCRT/UCRT
    • Il est difficile de déterminer si le problème vient du fait que le développeur n’a pas utilisé wmain(), ou du fait que le CRT découpe mal la ligne de commande et transmet des arguments incorrects à main()
    • Certains projets ne fournissent que le code source, tandis que les exécutables Windows précompilés sont distribués par des bénévoles tiers sur Internet
  • La correction ne consiste pas simplement à remplacer main() par une version wide-character
    • Dès que la signature de fonction change, il faut réécrire les définitions de variables et la logique de parsing des arguments de char * vers wchar_t *
    • Ce processus est pénible et propice aux erreurs
  • Curl a répondu qu’il s’agissait d’une fonctionnalité de Windows et n’a pas prévu de correctif, mais la version de Curl portée par Microsoft modifie l’entrée en wmain(), si bien que le curl.exe intégré à Windows n’est pas affecté
    • Les binaires officiels de Curl sont affectés par l’attaque d’Argument Splitting
    • Le rapport complet est public sur HackerOne
  • OpenSSL peut traiter les arguments au format wide character via la variable d’environnement OPENSSL_WIN32_UTF8
    • Son objectif initial était de corriger les problèmes d’affichage UTF-8 dans l’interface, mais elle atténue aussi l’attaque d’Argument Splitting
    • Dans un usage standard d’OpenSSL, les développeurs ignorent souvent qu’ils doivent définir cette variable d’environnement, et l’argument -engine peut permettre une exécution de code arbitraire
  • La distribution officielle de Perl ne fournit pas d’exécutables Windows précompilés, et des installateurs tiers comme Strawberry Perl et ActiveState Perl sont couramment utilisés
    • Ces deux distributions sont affectées par l’attaque d’Argument Splitting
    • Après discussion avec les maintainers de Perl, la conclusion a été qu’il s’agit « davantage d’un bug Microsoft que d’un bug Perl », et le problème reste non résolu à ce jour
  • Trois cas ont été signalés à Microsoft via le MSRC, et tous ont d’abord été rejetés comme n’atteignant pas le seuil de gravité
    • Après plusieurs relances, seul le cas d’Excel a été accepté, à la troisième tentative
    • Les autres cas restent non résolus à ce jour
    • Le MSRC a répondu que ces cas dépendent d’une vulnérabilité où une application distincte exécute une ligne de commande contenant une entrée non fiable, et que la technique qui rend cette exploitation possible ne remplit pas, en elle-même, les critères d’une vulnérabilité
  • De l’aide a aussi été demandée au CERT/CC, et Microsoft a ajouté quelques mois plus tard un avertissement de sécurité à la documentation de GetCommandLineA
    • L’avertissement n’a été ajouté qu’à GetCommandLineA, alors que d’autres API ANSI demandant de la prudence subsistent

Cibles affectées signalées et état

  • Les éléments identifiés et signalés durant le processus de divulgation sont les suivants
    • 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 — Correctif en attente
    • 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 — Corrigé
    • 2024/08/19: OpenSSL openssl.exe — Other
    • 2024/08/19: wkhtmltopdf wkhtmltopdf.exe — EOL
    • 2024/08/19: GNU Wget — Pas de réponse

Mesures d’atténuation et surface d’attaque restante

  • L’attaque WorstFit étant un problème au niveau du système d’exploitation, des problèmes similaires pourraient continuer à réapparaître tant que Microsoft n’activera pas UTF-8 par défaut sur toutes les éditions de Windows
  • Les utilisateurs peuvent vérifier et activer l’option UTF-8 de Windows
    • Cette fonctionnalité est encore indiquée comme étant en bêta, et ses effets secondaires éventuels ne sont pas certains
  • Les développeurs devraient utiliser autant que possible la Wide Character API
    • Le CRT fournit aussi des versions wide character comme _wgetcwd et _wgetenv
    • Si des chemins non-wide continuent d’être utilisés, l’implémentation interne peut appeler l’API ANSI et exposer au risque d’attaque WorstFit
  • En raison de la rétrocompatibilité de Windows, il pourrait y avoir d’autres endroits où l’API ANSI se cache
    • Par exemple, des requêtes du registre Windows comme RegQueryValueA pourraient être affectées, mais il faut encore identifier des scénarios de vulnérabilité
    • L’équipe de recherche a également observé un comportement Best-Fit dans Active Directory

1 commentaires

 
GN⁺ 2025-01-10
Commentaires sur Hacker News
  • C’est un problème assez épineux. Le mappage de code « best fit » de Microsoft est un convertisseur, documenté mais en pratique assez « au feeling », qui transforme un large éventail de caractères Unicode en ASCII, et il est présent un peu partout dans le système
    Ce convertisseur est lié par défaut dans énormément d’endroits et, vu la manière dont Microsoft gère la rétrocompatibilité, il semble inévitable qu’il reste inclus. Les exploits viennent généralement du fait que des points de code inhabituels sont mappés de manière « intuitive » vers des slashs, des tirets ou des guillemets. À l’intérieur d’un langage moderne, ils sont validés comme de l’Unicode correct, mais une fois passés à une commande shell ou à l’API Win32, après transfert du contrôle ils sont réduits différemment. Comme l’a dit le mainteneur de curl, ici « curl est la victime », mais la question est de savoir qui est le coupable. Si le serveur déforme différemment l’entrée utilisateur entre la validation et le passage à une bibliothèque système, le problème finit forcément par apparaître. Une option pour désactiver la conversion best fit côté Win32 pourrait être une solution, mais je ne suis pas spécialiste de Windows, donc ce n’est qu’une supposition. Même dans ce cas, il faudrait encore interagir avec des API officielles ou des logiciels qui ne l’ont pas encore désactivée

    • L’opt-out consiste à utiliser les API Windows Unicode, c’est-à-dire les fonctions qui se terminent par "w" plutôt que "a". Cette approche résout aussi le problème des chemins de plus de 260 caractères si on ajoute le préfixe "\\?\" ou qu’on configure correctement le manifeste, et c’est possible et recommandé depuis Windows XP
      Je ne comprends pas vraiment pourquoi les API non-Unicode restent encore aussi utilisées. Il est difficile d’imaginer que ce soit par volonté de prendre en charge Windows 98 ou Windows 2000
    • Windows dispose depuis Windows XP de fichiers manifeste permettant de désactiver des comportements hérités. Si je me souviens bien, sans manifeste même GetWindowsVersion ne renvoyait pas la version actuelle. Ajouter un opt-out ici, puis en faire un jour la valeur par défaut dans Visual Studio, ne semble pas très compliqué
      Ce qu’il faudrait aussi, c’est une forme de linting. Dans une application moderne, il n’y a généralement aucune raison d’appeler les fonctions ANSI de la WinAPI. On pourrait aussi définir les paramètres régionaux en UTF-8 et n’utiliser que les fonctions 8 bits, mais je ne sais pas à quel point cela fonctionne bien. Il me semble qu’il existe aussi quelques réglages et en-têtes permettant de faire fonctionner argv, printf et std::cout en UTF-8, et de n’utiliser pour la WinAPI que des fonctions de conversion UTF-8/UTF-16 sans transformations bizarres. Microsoft devrait documenter ces procédures en un seul endroit
    • Qu’il s’agisse ou non d’une faille de sécurité, si curl ne sait pas traiter correctement les arguments Unicode sous Windows, c’est aussi un bug de curl
    • Cette manière de mapper souplement des points de code vers des caractères m’a toujours dérangé avec Unicode
  • C’était dans une certaine mesure prévisible, mais même pour quelqu’un qui a fait une dizaine d’années de développement Windows et de bidouillage d’API Wine à l’époque où la confusion W/A était courante, c’était nouveau
    Windows ressemble au jeu de cartes Munchkin : quand plusieurs fonctionnalités s’imbriquent par hasard, elles peuvent se combiner en des exploits incroyablement aléatoires et puissants. Le fait que le sous-système ANSI soit en train d’être converti en UTF-8 est une bonne nouvelle et, en théorie, cela pourrait atténuer beaucoup de ces problèmes. Je me demande aussi si l’équipe Rust devra apporter encore une autre correction à son API de création de processus

    • La bibliothèque standard de Rust n’utilise pratiquement pas les API ANSI par défaut. L’article ne montre pas d’attaque qui fonctionnerait contre Rust, et s’il en existe une, il vaudrait vraiment mieux la signaler
      Bien sûr, Rust ne peut pas contrôler ce qui se passe au-delà des frontières du processus. Si l’application lancée par Rust utilise des API ANSI, c’est elle qui aura un problème, mais c’est la responsabilité de cette application
  • Si je me souviens bien, la suppression progressive d’ANSI et la recommandation d’utiliser les API Wide Character étaient la position officielle de Microsoft depuis NT 3.5
    Malheureusement, l’un des gros obstacles est l’implémentation de msvcrt.dll, la bibliothèque runtime C/C++ de Microsoft. Les fonctions wide non standard comme _wfopen() ou _wgetenv() utilisent en interne les fonctions W de la WinAPI, mais les fonctions narrow standard comme fopen() ou getenv() appellent directement les fonctions A au lieu de les convertir vers leurs équivalents wide. Et les fonctions A ne signalent généralement pas les échecs de conversion Unicode et les écrasent simplement avec du best fit. Les personnes qui portent un logiciel écrit en C vers Windows n’ont pas forcément envie de remplacer partout l’usage des fonctions standard par des fonctions non portables de Microsoft. À partir de là, c’est pratiquement une réécriture complète

    • Mon impression, après avoir lu la documentation Microsoft ces deux dernières années, était plutôt l’inverse. Elle allait dans le sens de régler activeCodePage sur UTF-8 dans le manifeste de l’application et d’utiliser uniquement les fonctions « ANSI »
    • Dans le code portable, on #define les fonctions standard comme main et fopen vers leurs équivalents wide quand on compile pour Windows
      Cela empêche d’utiliser simplement char* et les littéraux de chaîne bruts, donc on définit un type tchar, qui est char sous Linux et wchar_t sous Windows, ainsi qu’une macro _T() pour les littéraux de chaîne. En général, ça fonctionne sans trop y penser
    • Ce qui est vraiment agaçant aujourd’hui, c’est que lorsqu’on cherche la Win32 API sur Google, c’est toujours la variante -A qui apparaît en premier plutôt que la variante -W. Je ne sais pas s’il y a quelque chose de bizarre dans le robots.txt, mais c’est étrange qu’une API recommandant d’utiliser la variante -W pour le nouveau code renvoie par défaut l’API legacy
    • La runtime C/C++ msvcrt.dll de Microsoft a été remplacée par la Universal C Runtime (UCRT)[1], et l’UCRT est conforme à C99
    • Windows aurait dû fournir une API qui traite simplement les noms de chemin comme des séquences d’octets, sans toute cette gestion d’encodage stupide. Ça aurait peut-être pu être fait lors de l’introduction des chemins UNC
  • Il existe deux façons de forcer réellement la page de code « Ansi » en UTF-8 pour une application que l’on a écrite soi-même ou pour un EXE patché
    L’une consiste à utiliser un fichier manifeste, ce qui fonctionne depuis certaines builds de Windows 10. On peut aussi l’appliquer à n’importe quel EXE après compilation, ce qui permet d’imposer de force la prise en charge UTF-8 à un programme. C’est particulièrement utile pour les programmes en mode console. L’autre consiste à utiliser le type de hack employé par les outils de type « App Locale ». Une méthode inclut l’appel à des fonctions non documentées de NTDLL. Je ne sais pas exactement quelles fonctions sont nécessaires, mais RtlInitNlsTables et RtlResetRtlTranslations pourraient être impliquées

  • Il est difficile de dire si Microsoft pourra activer UTF-8 par défaut dans toutes les éditions de Windows. Beaucoup d’anciennes applications risqueraient de se casser, car elles supposent une page de code donnée ou 1 octet par caractère
    Plus subtilement, certaines applications réutilisent aussi des tampons existants en supposant que le nombre d’octets n’augmente pas lors d’une conversion de caractères wide vers ANSI. Ce n’est pas vrai avec UTF-8, et c’était généralement vrai avec la plupart des anciennes pages de code, ce qui pourrait créer de nouvelles vulnérabilités. Il semblerait bien moins casseur de supprimer la logique Best-Fit des API Win32 xxxA et de remplacer les caractères non convertibles par un caractère comme x, sans signification méta commune

    • Adobe After Effects en est un exemple[0]. C’était au moins le cas auparavant, mais je n’utilise plus Windows aujourd’hui
      [0] https://tambre.ee/blog/adobe_after_effects_windows_utf-8/
    • S’il n’existe pas déjà quelque chose de ce genre, on pourrait peut-être introduire une version d’API côté OS, afin que les nouvelles applications ou les applications mises à jour ciblant une nouvelle version d’API ou un nouveau SDK supposent UTF-8 par défaut. En dessous d’une certaine version d’API, il suffirait d’émuler un mode legacy. Windows dispose déjà du concept de shim pour reproduire le comportement de différentes versions de Windows
    • Même avant UTF-8 sur Windows, changer la page de code par défaut pouvait déjà faire dysfonctionner des applications. Il est donc raisonnable de laisser aux utilisateurs une option UTF-8
      Vu les problèmes causés par le mappage Best-Fit, il serait aussi raisonnable d’en faire la valeur par défaut, mais Microsoft devrait aider les utilisateurs à trouver un moyen d’exécuter facilement les vieux logiciels. Une méthode moins raisonnable consisterait à supprimer du mappage Best-Fit tous les mappages vers des caractères ASCII « spéciaux », mais cela n’aiderait pas les applications qui lient statiquement le CRT. Et comme cela ne corrigerait pas la vulnérabilité, ce ne serait pas une bonne solution. Parfois, une faille de sécurité sert de motivation pour imposer une rupture de compatibilité ascendante
  • Microsoft connaît ce problème depuis au moins un an. Ils ont publié une règle spéciale d’analyse de code, CA2101[1], qui déconseille explicitement l’usage du mappage best-fit
    La description de la règle mentionnait une vulnérabilité de sécurité, mais en restait délibérément vague sur les détails
    [1] https://learn.microsoft.com/en-us/dotnet/fundamentals/code-a...

  • Il n’est pas nécessaire de tout remplacer de char * par wchar *. Il suffit de convertir les caractères wide reçus en UTF-8, ou, si l’on veut accepter même des séquences invalides comme des surrogates non appariés, en quelque chose comme WTF-8 de Rust, puis de continuer à utiliser char
    Bien sûr, il faut veiller à ne pas mélanger des chaînes ANSI ou OEMCP avec des chaînes UTF-8, mais si l’on n’utilise que UTF-8, c’est simple. C’est l’approche recommandée par le site classique https://utf8everywhere.org/

  • J’évitais ce bug par hasard depuis quelques années en ayant activé le mode UTF-8 sur mon PC Windows personnel. C’est le réglage mentionné en bas de l’article
    Je l’avais activé parce que d’anciens jeux étrangers affichaient des caractères corrompus, et même si c’est marqué « Beta », je n’ai remarqué ni bug ni effet secondaire

    • Intéressant, mais dans mon cas cette case à cocher n’a surtout servi qu’à faire planter bien trop d’apps au hasard. Il semble que leur bon fonctionnement dépende de la page de code par défaut de l’utilisateur quand elle est désactivée
    • Je viens d’activer l’option « Beta: Use Unicode UTF-8 for worldwide language support ». Ce sera intéressant de voir combien d’apps vont se casser
  • Je me demandais si cette case beta était équivalente au fait de définir ActiveCodePage sur UTF-8 dans le manifeste, mais la documentation[0] indique clairement que GDI ne suit pas une page de code par processus et ne suit que l’unique page de code globale définie par cette case
    C’est un peu dommage qu’on ne puisse pas faire un opt-in totalement UTF-8 pour son app avec les API *A. Cela dit, je pense que cela peut quand même rester un contournement valable ou une mesure de défense en profondeur pour les problèmes soulignés dans l’article
    [0] https://learn.microsoft.com/en-us/windows/apps/design/global...

  • Mon dieu. Je savais que l’API Windows proposait ce type de conversion best-fit, mais je ne savais pas que c’était le comportement par défaut de nombreuses fonctions ANSI avec ma page de code par défaut, 949[1]
    À ce stade, cela devrait simplement être interdit, comme gets. [1] Je sais qu’il existe la page de code UTF-8 65001. Pendant longtemps, elle a été franchement inutilisable, et elle pose encore aujourd’hui des problèmes de compatibilité.