Une porte blindée sur une tente Quechua : ne nous trompons pas sur la sécurité du Web
Au commencement était le Web, et le Web était troué, et le Web ne se préoccupait pas de sécurité. Destiné à faciliter l’échange d’informations entre chercheurs et chercheuses au moyen d’un format standardisé, le premier objectif était de permettre l’accès à l’information au plus grand nombre plutôt que la protection de cette information. Le Web repose en effet sur une perte de contrôle volontaire, et historiquement inédite dans l’informatique de l’époque : l’information est mise à disposition sur un serveur à qui n’importe quel navigateur peut faire appel afin d’obtenir une copie locale de l’information. L’exécution finale se passe ainsi du côté client, sans contrôle possible par le côté serveur une fois que l’information a été émise.
Les protocoles implémentés à l’époque l’ont ainsi été sans intégrer les problématiques de sécurité qui nous paraissent pourtant évidentes aujourd’hui : protection des communications, gestion des authentifications, intégrité de l’information fournie par le navigateur et autres préoccupations de confidentialité. Nous sommes tristes héritiers et héritières d’une architecture ouverte aux quatre vents, qui conditionne pourtant toujours une large partie de notre vie numérique.
Petit rappel historique à l’intention de celles et ceux qui se demandent comment nous avons pu bâtir monts et merveilles sur une plateforme aussi permissive.
JavaScript, mauvais élève du Web
1995 : Internet est mondial. D’un outil destiné à permettre aux chercheurs d’échanger des informations sur leurs travaux de recherche, nous sommes passés à un vaste réseau qui connecte des centaines de milliers d’ordinateurs personnels. Le phénomène est de telle ampleur que des empires naissent au sein de cet écosystème nouveau, à commencer par les éditeurs de navigateurs. À ce moment-là deux acteurs se livrent à une course acharnée : Netscape et Microsoft, qui cherchent à assurer leur hégémonie en fournissant aux développeurs de l’époque les fonctionnalités les plus intéressantes.
Un mauvais élève au rôle essentiel dans la difficulté à sécuriser le Web pointe alors son nez : il s’agit de JavaScript (omniprésent depuis bientôt 25 ans dans nos navigateurs™) notoirement peu sécurisé, et conçu au départ pour scripter des interactions, enrichir l’expérience utilisateur, et complémenter Java. La légende raconte que Brendan Eich, alors ingénieur chez Netscape, créera en dix jours, pour la release suivante, un prototype de langage permettant de manipuler programmatiquement avec le DOM. C’est révolutionnaire, c’est gadget, personne ne sait quoi en faire, bref : c’est tendance ! Il faudra attendre un peu moins de dix ans et l’arrivée de jQuery pour que le langage révèle son plein potentiel.
Ironiquement, c’est justement jQuery qui fera le plus de mal à la sécurité du Web. En offrant une sur-couche unifiée à JavaScript, il permet de facilement sans recharger sa page, faire des appels à des APIs (le célèbre AJAX), gérer de l’asynchrone, et de rentrer dans une logique applicative plutôt que dans une logique de page statique. Nous ne nous rendions pas compte encore, mais le mal était fait.
Toutes ces logiques applicatives se fondent sur JavaScript. Avec un problème majeur : personne n’a jamais envisagé que JavaScript puisse être utilisé autrement que comme un langage de scripting (Java était là pour les logiques lourdes, rappelez-vous), et il est pourtant utilisé plus massivement qu’aucun autre. Pourquoi ? There Is No Alternative : il est le seul disponible pour programmer sur la plateforme Web. Toutes les tentatives pour le remplacer se sont soldées par un échec. Flash ? Propriétaire, percé comme une passoire, voire vecteur d’infection, tout le monde a été ravi de s’en débarrasser. Java ? Sérieusement, personne n’a jamais compris comment s’en servir, depuis les devs qui implémentaient les applets jusqu’au utilisateurs·trices qui devaient installer Java sur leurs postes, et on l’a abandonné sur le Web depuis longtemps. Toutes les autres tentatives ont toujours été des transpilations vers JS. Car JavaScript, lui, est resté.
Always bet on JavaScript.
2015 : on en est donc toujours à utiliser un langage de scripting pour développer sur le Web. Et JavaScript présente deux inconvénients majeurs pour qui se soucie de sécurité :
- Il est prototypé, c’est-à-dire que n’importe quelle fonction, même native, peut-être étendue pour modifier son fonctionnement. N’importe qui peut donc modifier une méthode d’un prototype existant, et jouer avec les données qui lui sont passées.
- Il est interprété à la volée, il est donc non-déterministe, ce qui signifie que le code qui s’exécute ne peut être que partiellement anticipé, et que n’importe qui peut observer ce qui s’y passe.
Pas génial en termes de sécurité, notamment dans la mesure où vous ne pouvez pas contrôler finement l’environnement d’exécution. Donc pas de contrôle client + langage scripté == Danger ! Certes, il y a eu des tentatives de protéger l’exécution de JavaScript dans un système de conteneurs contrôlant en permanence l’intégrité des méthodes pour s’assurer qu’aucun code malveillant n’agirait hors du chemin prévu. Le résultat s’est soldé par une machine JS environ 10 à 20 fois plus lente.
L’être humain est ainsi fait qu’il préfère les performances à la sécurité…
WebAssembly pour les gouverner tous ?
2017 : un petit nouveau pointe son nez dans l’univers Web : en cherchant à optimiser l’exécution de JavaScript (pour exécuter du code C transpilé vers JS), un substrat du runtime va être identifié comme étant très efficient à optimiser. C’est le projet asm.js. Mais asm.js ne peut optimiser qu’une partie du code qui lui est passé. L’idée d’un langage totalement statique pour le Web, vers lequel on pourrait compiler depuis n’importe quel autre langage fait son chemin. Ce sont les débuts de Webassembly, et l’engouement autour de ce nouveau langage à la syntaxe proche des instructions CPU (de l’assembleur) pour accélérer le rendu dans le navigateur, fait resurgir des questions de sécurité. Pas question de reproduire les mêmes erreurs et de laisser un apprenti sorcier déterminer l’avenir de la sécurité du Web une deuxième fois. Quitte à inventer un nouveau langage pour le Web, tirons parti des erreurs passées, et pensons-le security-first.
Car entre temps, la sécurité du Web est devenu un sujet d’inquiétude, notamment avec l’explosion du nombre d’utilisateurs et utilisatrices non techniques. Le Web est partout, dans nos véhicules et nos téléphones, et la plateforme pousse à une adoption toujours plus large (coucou, les PWA !).
Sur le Web, la compromission est bien souvent indétectable. Entre les questions de chiffrement, d’authentification, les injections de code malveillant, le cross-site scripting et l’invasivité des régies publicitaires, des critiques émergent régulièrement et des modèles de sécurité spécifiques au Web (comme l’OWASP) tentent de répondre à ce qui est attendu d’un site ou d’une application Web.
Modèle de sécurité : qu’attendons-nous du Web ?
2019 : Afin de ne pas nous perdre dans des considérations trop spécifiques, nous avons choisi d’examiner la sécurité Web sous l’angle de la triade CIA (Confidentiality/Integrity/Availability), bien connu dans le secteur de la cybersécurité.
La triade CIA désigne les trois composants cruciaux de la sécurité d’un élément :
- la confidentialité : le fait que l’information arrive à la bonne personne et seulement à cette personne,
- l’intégrité : le fait que ce soit la bonne information qui arrive, et qu’elle n’ait été ni falsifiée ni altérée,
- la disponibilité : le fait que l’information soit effectivement accessible à la personne en question.
Conçu dans un objectif de disponibilité de l’information, le Web lutte historiquement avec les deux autres composants de la triade. La confidentialité de l’information peut être compromise au sein de différentes couches : faille dans la machine elle-même qui permet l’accès d’un attaquant au navigateur, protocole d’échange non sécurisé, authentification mal implémentée, module JavaScript intégré dans une page compromise qui fait fuiter des données, les vecteurs d’attaque sont innombrables. L’intégrité de l’information n’est pas mieux lotie : la facilité d’exécution d’un script malveillant dû aux manquements de JavaScript évoqués plus haut met en danger by design le code qui s’exécute et la donnée qu’il est facile de modifier de manière totalement transparente pour l’internaute.
La sécurité s’est ainsi le plus souvent construite comme une couche supérieure du Web, auquel on a ajouté ceinture de sécurité et airbags : SSL/TLS, Content Security Policy, Subresource Integrity et autres réjouissances qui n’étaient pas présentes à l’origine, mais sont venues s’ajouter en surimpression, comme pour patcher le Web, sans jamais résoudre le point de vulnérabilité principal de la plateforme : l’absence de gestion de sécurité de JavaScript.
La sécurité, à cœur
2020 : En quoi WebAssembly répond-il à ce modèle de menaces mieux que JavaScript ? Notamment parce qu’il porte la sécurité comme une de ses raisons d’être. À tel point d’ailleurs que le 12 novembre dernier naissait la Bytecode Alliance, un consortium composé de Mozilla, Intel, Fastly, et RedHat, visant à faire évoluer WebAssembly dans les meilleures conditions de sécurité.
Concrètement, WebAssembly apporte un contexte d’exécution sûr en isolant ses processus : chaque module WASM lancé par la machine virtuelle (le runtime) est totalement cloisonné. Même un système qui utiliserait des modules externes (comme on le fait souvent avec Node.js et JavaScript notamment) sont isolés les uns des autres. Dans le cas où des modules d’une même application partageraient un espace mémoire (un Shared Memory), le risque de propagation est limité par le design même de la machine virtuelle. WebAssembly offre un environnement où tout est contrôlé en amont. C’est d’ailleurs une de ses autres forces : il présente un modèle déterministe où tout code devant être exécuté est par nature anticipé par le runtime. Seuls quelques cas très marginaux, et parfaitement contrôlés, sont les exceptions au modèle. On ne veut pas que du code arbitraire puisse être exécuté. Pour finir, WebAssembly est particulièrement peu tolérant à la panne : en cas de comportement non-prévu, il préférera s’interrompre plutôt que de courir le moindre risque.
Outre ses capacités d’exécution et de performances très attendues, WASM apporte donc un modèle de développement radicalement nouveau sur le Web. Nous passons d’un langage de script conçu pour s’exécuter facilement, être modifié à la volée, et autoriser même les cas non-prévus, à un paradigme de langage compilé, isolé, digne de Scheme (ce qui boucle la boucle, le projet initial de Nestcape ayant été de porter Scheme sur le Web). Cet environnement nouveau, du point de vue de la sécurité, est totalement bénéfique dans sa réponse à notre modèle de menace :
- Dans son intégrité : le contrôle continu, permanent, dû au modèle déterministe du langage, garantit la non-altération du code, et prévient l’action malicieuse sur la donnée qui lui est confiée.
- Dans sa confidentialité : la construction en boite noire de la machine virtuelle prévient toute fuite en dehors du module. Tout ce qui entre et sort ne peut passer que par des API dédiées. Ce qui s’exécute n’est pas observable de l’extérieur. Le format binaire des fichiers prévient leur altération malicieuse (tampering).
- Pour la disponibilité : WebAssembly réussit là où les tentatives précédentes ont échoué. Il a su fédérer, en proposant un modèle nouveau, tous les grands acteurs du Web moderne. WASM fonctionne sur tous les navigateurs récents, et les alliances qui se forment autour de lui sont la preuve de plus de l’intérêt qu’il suscite, et de son avenir certain.
Voilà comment, en quelques années, nous avons réussi à outiller notre plateforme Web de ce qui lui manquait : un langage de programmation capable de renforcer sa sécurité tout en améliorant sa performance.
Voilà ! Les cabinets de cybersécurité Web n’ont plus qu’à se rhabiller. Pas vrai ?
Une porte blindée sur une tente Quechua
Ne perdons pas nos responsabilités de vue : quels que soient les outils, c’est la rigueur de leur implémentation qui fait la différence. Croire que remplacer JavaScript par WebAssembly est la baguette magique qui réglera tous nos problèmes de sécurité sur le Web est illusoire. Il reste un problème, et de taille : l’environnement. Si WebAssembly protège le traitement des données, qui protège WebAssembly ? La donnée devra fatalement passer par la page Web, que ce soit pour alimenter les modules WASM, ou pour afficher leur résultat en sortie.
On en revient donc à nos considérations précédentes : le Web n’a pas été conçu avec la sécurité en principe de base. Il a progressivement été patché et l’arrivée de WebAssembly ne change rien à cet état. Exécuter un code WASM dans une page non-sécurisée revient à poser une porte blindée sur une tente Quechua.
Il n’existe pas de solution miracle. Avec un impératif de rétro-compatibilité de 30 ans, le Web ne peut se permettre de changer radicalement de paradigme. Il a su muter et s’adapter pour offrir tout l’outillage nécessaire à la protection efficace des données dans ses applications. Le dernier maillon restait JavaScript, WebAssembly constitue sa suite logique. La responsabilité revient aux développeurs et développeuses de concevoir leurs applications en prenant en compte la sécurité dès le départ, et en appliquant les bonnes pratiques de protection de leur environnement.
Le Web a plus que jamais de beaux jours devant lui, le changement à opérer est désormais dans les mentalités de celles et ceux qui utilisent la plateforme : nous ne développons plus uniquement des sites Web, nous développons des applications. Avec les responsabilités qui vont avec.