Aller au contenu principal

Brèche dans la barrière : Comment Renforcer les applications avec le bac à sable

· 5 mins de lecture

Plus d’une semaine s’est écoulée depuis que CVE-2023-4863 : Heap buffer overflow in WebP a été rendu public, entraînant une vague de nouvelles versions des webp de rendu logiciel : macOS, iOS, Chrome, Firefox et diverses distributions Linux ont tous reçu des mises à jour. Cela fait suite aux enquêtes menées par Citizen Lab, qui a découvert qu’un iPhone utilisé par une « organisation de la société civile basée à Washington DC » était attaqué à l’aide d’un exploit sans clic dans iMessage.

Pour Electron, nous sommes également passé à l’action et a publié de nouvelles versions le jour même : Si votre application affiche du contenu fourni par l’utilisateur, vous devez mettre à jour votre version d’Electron - v27.0.0-beta.2, v26.2.1, v25.8.1, v24.8.3 et v22.3.24 contiennent toutes une version corrigée de libwebp, la bibliothèque responsable du rendu des images webp.

Maintenant que nous sommes tous nouvellement conscients qu’une interaction aussi innocente que « le rendu d'une image » soit une activité potentiellement dangereuse, nous voulons profiter de cette occasion pour rappeler à tous que Electron est livré avec un bac à sable des processus qui limitera le rayon d’action d'une éventuelle grosse attaque - quelle qu’elle soit.

Le bac à sable était disponible depuis Electron v1 et activé par défaut dans v20, mais nous savons que de nombreuses applications (en particulier celles qui existent depuis un certain temps) peuvent avoir un sandbox: false quelque part dans leur code – ou un nodeIntegration: true, qui désactive également la sandbox lorsqu’il n’y a pas de paramètre sandbox explicite. C’est compréhensible : Si vous êtes avec nous depuis longtemps, vous avez probablement apprécié le pouvoir de lancer un require("child_process") ou un require("fs") dans le même code qui exécute votre HTML/CSS.

Avant de parler de comment migrer vers le bac à sable, voyons d’abord pourquoi vous le souhaitez.

Le bac à sable isole strictement tous les processus de rendu et garantit que, quoi qu’il arrive à l’intérieur, le code s’exécute dans un environnement limité. En tant que concept, c’est bien plus ancien que Chromium, et c’est une fonctionnalité proposée par tous les principaux systèmes d’exploitation. Le bac à sable d’Electron et de Chromium s’appuie sur ces fonctionnalités système. Même si vous n’affichez jamais de contenu généré par les utilisateurs, vous devez envisager la possibilité que votre moteur de rendu soit compromis : des scénarios aussi sophistiqués que des attaques sur la chaîne d’approvisionnement, ou aussi simples que de petits bugs, peuvent amener votre moteur de rendu à faire des choses que vous n’aviez pas vraiment prévues.

Le bac à sable rend ce scénario beaucoup moins inquiétant : un processus à l’intérieur peut utiliser librement le processeur et la mémoire — et c’est tout. Les processus ne peuvent pas écrire sur le disque ni créer leurs propres fenêtres. Dans le cas de notre bug libwep, le bac à sable garantit qu’un attaquant ne peut ni installer ni exécuter de malware. En fait, dans le cas de l’attaque Pegasus initial sur l’iPhone d’un employé, l’attaque ciblait spécifiquement un processus d’image non isolé pour accéder au téléphone, en sortant d’abord des limites de l’iMessage, normalement sandboxée. When a CVE like the one in this example is announced, you still have to upgrade your Electron apps to a secure version — but in the meantime, the amount of damage an attacker can do is limited dramatically.

Migrating a vanilla Electron application from sandbox: false to sandbox: true is an undertaking. I know, because even though I have personally written the first draft of the Electron Security Guidelines, I have not managed to migrate one of my own apps to use it. That changed this weekend, and I recommend that you change it, too.

Don’t be scared by the number of line changes, most of it is in package-lock.json

There are two things you need to tackle:

  1. If you’re using Node.js code in either preload scripts or the actual WebContents, you need to move all that Node.js interaction to the main process (or, if you are fancy, a utility process). Given how powerful renderers have become, chances are high that the vast majority of your code doesn’t really need refactoring.

    Consult our documentation on Inter-Process Communication. In my case, I moved a lot of code and wrapped it in ipcRenderer.invoke() and ipcMain.handle(), but the process was straightforward and quickly done. Be a little mindful of your APIs here - if you build an API called executeCodeAsRoot(code), the sandbox won't protect your users much.

  2. Since enabling the sandbox disables Node.js integration in your preload scripts, you can no longer use require("../my-script"). In other words, your preload script needs to be a single file.

    There are multiple ways to do that: Webpack, esbuild, parcel, and rollup will all get the job done. I used Electron Forge’s excellent Webpack plugin, users of the equally popular electron-builder can use electron-webpack.

All in all, the entire process took me around four days — and that includes a lot of scratching my head at how to wrangle Webpack’s massive power, since I decided to use the opportunity to refactor my code in plenty of other ways, too.