Dans un précédent article, j'avais glosé des difficultés à écrire des javascripts au comportement cohérent sur l'ensemble des “principaux” navigateurs web dans la gestion des arbres DOM. Cette fois-ci, c'est un autre souci qui a été pointé cet été par Bojan Zdrnja, information qui aura sûrement échappé à la plupart des webdev... à leurs tords.
Relevant de nouvelles méthodes d'infecter d'innocentes machines par des virus, ce chercheur en sécurité du SANS a isolé un specimen de Javascript infectieux découvert par Daniel Kluge et mis sous son microscope afin de comprendre pourquoi il est si méchant. Malgré qu'il soit écrit dans un langage théoriquement “ouvert” (car interprété) et même en ayant son code source sous les yeux, il est difficile à disséquer, obfusquant son ADN source afin de protéger ses patrons maffieux. Rapidement, notre expert remarque que ce malicieux code Javascript ne peut s'exécuter que dans Internet Explorer. Pourquoi ? Ce n'est pas la grammaire, ni l'arbre DOM, ni une extension propriétaire de Microsoft qui est en cause, mais... les expressions régulières.

Pour “simplifier”, ces expressions rationnelles correspondent aux théories de Kurt Gödel [NB1], mais appliqués à un alphabet. C'est ÀMHA la meilleure explication jamais trouvée pour pondre le code le plus imbitable pondu histoire de faire criser les collègues de l'openspace [NB2]. Pourtant, je m'en sers pas mal, comme on peut le voir dans mes marques-pages, section PHP. La lecture d'un regex tient à la fois du bénédictin décryptage de langue morte digne de Champollion mais aussi d'une pointe de mystère religieux dans la contemplation de l'arc-en-ciel de votre éditeur à coloration syntaxique, aux motifs ressemblants à une rosace romane [NB3].
Objectivement, le regex est l'ultime solution thermonucléaire pour un question que vous ne vous êtes jamais posée.

Pouf ! Pouf ! Je reprends...

Où le Javascript, c'est comme une Tour de Babel...

Donc, dans cette publication « Raising the bar: dynamic JavaScript obfuscation », Daniel Kluge et Bojan Zdrnja mettent en évidence plusieurs bugs, ou plutôt des différences comportementales dans les regex selon les navigateurs ! Le comportement peut se tester sur cette page. Le premier nombre de la popup d'alerte est à surveiller : S'il indique une valeur de “94”, c'est que l'interprétation de regex est correcte dans votre navigateur.

Ainsi, le moteur JScript de MSIE retourne “98”, indépendamment de MSIE6 et MSIE7. Il considère qu'il peut faire précéder un nombre de plusieurs 0 sans que cela prête à conséquences... ou presque puisque JScript peut considérer qu'un nombre précédé de zéro non significatifs est en fait une chaîne de caractères. C'est le cas dans certaines versions du langage de macro d'Excel.
Safari (versions 2 et antérieures) retourne “93”. En fait, il voit mal le commutateur « /g », signifiant de répéter plusieurs fois l'opération. Ce qui est un bug (corrigé dans Safari 3) et qui fout potentiellement en l'air mon soit-disant script universel déblatéré ici. Ce bug est propre au moteur d'Apple puisqu'aucune des versions de Konqueror que j'ai sous la main (KHTML est la base utilisée par Apple pour son WebKit) ne retourne cette valeur erronée.

Mais c'est surtout un élément très important : Qu'un code supposé être universel peut subir des différences d'interprétation. Quelque chose à ne pas négliger quand il s'agit de “compresser/obfusquer” sa bibliothèque JS. Et une des raisons pour laquelle je déconseille cette pratique.

Contrairement à ce que l'on pourrait croire, les auteurs de ce virus jouent intentionnellement sur ce bug : La suite de leur infection ne fonctionne que sur MSIE, et les débuggueurs Javascripts travaillant plutôt avec un moteur Gecko (donc Firefox), toute personne qui tentera de comprendre leur code (pour justement le bloquer), aura encore plus de mal à le comprendre.

Là où on ne s'en sort plus, c'est quand on se souvient que les regex furent créés à une époque où les ordinateurs avaient rarement des caractères accentués. De cette vénérable histoire, ils ont gardé l'héritage curieux de travailler parfaitement avec les 128 premières caractères de l'alphabet informatique. Et après ? Point de salut : ISO-Latin (16 versions), MS-Dos page, Mac, et pire... Unicode (en UCS-2, UCS-4, UTF-7, UTF-8,...). Vous avez bien peu de chances d'être sûr de votre résultat... L'article Wikipédia a une section qui résume bien le problème [NB4].

L'autre souci, et non le moindre, c'est que les regex sont extrêmement pratiques, souvent utilisés dans le cas de projets compliqués (import/export de xml, analyse de texte,...) et avec une syntaxe... vague. Car de leur très longue histoire, ils ont hérités d'une généalogie très compliquée. Personne n'a jamais normalisé le langage regex, mais chacun y est allé de sa petite touche. Donc un programme, une fonction, voire une formule en regex est susceptibles de différences subtiles suivant où il tourne.
Tout dépend qui a fourni la bibliothèque et qui s'en sert : TCL, Python, Perl, Grep,... Ainsi Microsoft utilise des dialectes très différents ; sa notation dans JScript du navigateur MSIE diffère sensiblement de celle de ses serveurs ASP.NET ou de C#. Certains sites (comme celui-ci) s'emploient à proposer des codes universels. Personnellement, la plupart du temps, j'utilise l'éditeur de remplacement de kwrite pour écrire et tester mes regex, mais ça ne fonctionne pas toujours. Arg.

Transition PHP ? Prépare-toi à des nervousses breakdownes

Quand j'ai refait le design de mon site dascritch.net, comme vous le savez, j'en ai profité pour changer complètement le code (moteur blog, mais aussi présentation de fichiers, de liens, planet, et le reste...) mais aussi de serveur. Et c'est ainsi que je suis passé de PHP4.3 à PHP5. Mais là... surprise ! Entre les deux versions majeures de PHP, le moteur regex a changé... subtilement.

Ainsi, dans ma planet, pour classer les flux rss en fonction de la date de chaque billet, je décodais la balise date, écrite dans un format très spécifique (l'écriture d'une date dans un document RSS mérite un billet tellement que c'est le bazar).
Le code (basé sur magpieRSS) se présentait ainsi :

$regex_de_date = "/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/";
if ( preg_match( $regex_de_date, $date_str, $blocules ) ) {
list( $annee, $mois, $jours, $heures, $minutes, $secondes) = array( $blocules[1], $blocules[2], $blocules[3], $blocules[4], $blocules[5], $blocules[6]);
[...]
}

Et ben, vous allez rire [NB5], mais entre PHP4 et PHP5, le singleton ([-+]) [NB6] est devenu “significatif”. Ce qui veut dire que dans mon tableau renvoyé par mon regex, les secondes n'étaient plus en $blocules[6] mais en $blocules[7] si mon code est interprété par PHP5.

Visiblement, cela me semble être un bug d'interprétation de la bibliothèque PCRE qui a été corrigé, mais ceux qui avaient été confrontés à lui s'étaient plus ou moins arrangé avec ce comportement irrationnel [NB7]. Et donc des milliers de scripts, de programmes, d'applicatifs, de sites métiers,... sont basés dessus et vont avoir un comportement “incohérent” lors de leur passage à une infrastructure PHP5. C'est justement pour éviter la fronde des développeurs que Microsoft a instauré la compatibilité des bugs features comme une religion sacro-sainte. Avec fanatisme, donc avec absurdité.

Pour en revenir avec la sale bête

Dernier élément intéressant de l'étude du SANS : la faille exploitée par le virus réside dans la méthode d'appel d'un logiciel tiers. Un problème qui a valu à Firefox deux version successives (les 2.0.0.5 et 2.0.0.6), en fait dû à une très mauvaise habitude de Microsoft Windows dans la gestion des URI. Ce qui ouvre la voie à de nouvelles méthodes d'attaques par document audio ou vidéo malformé. Ce que l'on appelle du “fuzzing”, c'est-à-dire qu'on fait ouvrir des documents mals foutus exprès à un logiciel pour voir s'il plante. Rien de neuf sous le soleil, Windows Media Player était déjà connu pour être sensible à ce genre d'attaques, alors que MS Word et MS Excel sont plutôt bien écrits et très très bien testés de ce côté-là. La nouveauté, c'est que la moindre de ces applications de “lecture” est potentiellement victime d'erreurs de programmations. Des petites failles où le script décrit plus haut pourrait se nicher. Ou tout simplement de très mauvaise méthodes de programmation comme dans le plugin Flash

À noter que l'équipe Firefox a justement développé un outil de fuzzing pour tester la solidité de leur moteur javascript. Présenté publiquement lors du même évènement (la Black Hat Convention à Las Vegas cet été), l'outil conçu à ces fins a été mis à la libre disposition de tous... les navigateurs. Et comme bien souvent l'interprétation d'un code source en Javascript est écrit en regex...

Une horreur de slash

Ce qui est grave, c'est que ce n'est plus une extension qui est attaquable, mais l'implémentation dans la syntaxe de base d'un autre langage supposé lui aussi être quasi-universel. Sauf que les deux erreurs pointées le sont sur des règles de base qui sont uniformes partout ailleurs, et dont l'interprétation fantaisiste étonne les rédacteurs de l'article du SANS.
Un tel oubli chez deux importants constructeurs de navigateur montre qu'il y a encore des choses très importantes à tester dans les moteurs Javascripts. Alors que, contrairement aux DOM, les regex sont décrits avec précision dans la norme ECMA 262. La norme dont tous les moteurs Javascripts se targuent d'être parfaitement compatibles.

Encore une pierre de plus dans le jardin de John Resig. Pas de quoi le démonter dans son inoxydable enthousiasme sur le Javascript, puisqu'il annonce l'arrivée les nouveaux moteurs ECMAScript. Enfin conformes ?


Notes de bas de pages.

  1. ↑Kurt Gödel : Malgré son patronyme, et le fait qu'Hitler aie changé la nationalité de ses compatriotes, tu noteras, chère Katell, que son nom se prononce avec une pointe d'accent Tchèque plutôt que Germanique. Ce que tu n'avais pas compris la première fois.
  2. : D'ailleurs, ce langage est tellement l33t que la doc web de la lib PCRE est sciemment présentée comme si elle sortait sur un vieux terminal texte avec la commande man. C'est vraiment du terrorisme social pour empêcher les petits jeunôts de se croire plus compétents que vous parce qu'ils sortent d'une formation quelconque... Hinhinhin...
  3. ↑ coloration syntaxique d'un regex : Très honnêtement, si vim ne me proposait pas la coloration syntaxique, jamais j'aurais compris que le code en perl proposé dans le roman « Le Cryptonomicon » pour décoder du cryptage Enigma était erroné suite à un oubli d'un “/” dans une règle de substitution. Pour les curieux, page 227 du tome II, « Le réseau Kinakuta » de la première édition Francophone chez Payot SF.
  4. ↑ référence Wikipédia : Pour ceux qui me liront plus tard, la version de l'article au moment de l'écriture de ce billet.
  5. ↑ « vous allez rire » : humour geek au 0xFE degré, forcément.
  6. ([-+]) : Ce bout de regex signifie qu'à cette position, il y a un caractère unique pouvant avoir la valeur “+” ou “-”. Et uniquement celles-ci. Si ça marche pas, la fonction regex est fausse, preg_match() retourne la valeur false et donc la partie suivante du script est ignorée. Ce bug me semble être lié à celui-ci
  7. ↑ « ceux qui [...] » : ou plus probablement, ces développeurs perçoivent les regex comme de la sorcellerie. Ils ont chacun dû bidouiller au pif un code pioché dans un “Grand Albert” quelconque, formule magique se trouvait être fonctionnelle, et faute d'expertise, n'ont même pas relevé un comportement incohérent. Après tout, les gens qui invoquent pour eux des regex en égorgeant un coq noir par une nuit de pleine lune autour de braises de VT100 n'ont pas d'explication à donner. D'ailleurs, la syntaxe des rituels sacrés regex n'accorde aucune facilité de commentaires, qui pourraient briser l'invocation. Généralement, on évite soigneusement de provoquer leur ire par une question qu'ils pourraient trouver offensante. D'abord ça brise la magie, et ensuite si le sorcier voudou vous jette un œil noir, ça pourrait vous faire partir en sucette votre config Apache... Ugh.