- La combinaison de la frontière de privilèges WebUI de Chromium, de la fonctionnalité de test des stratégies d’entreprise et d’une faille dans l’API d’extension DevTools permettait à une extension Chrome malveillante, avec seulement une légère interaction utilisateur, d’aller jusqu’à l’exécution de commandes shell
- Le chemin d’attaque consistait à appeler depuis
chrome://policyune API de test de stratégies non documentée pour modifier les stratégies utilisateur, puis à détourner le chemin et les arguments du navigateur alternatif de Browser Switcher en commandes shell chrome.devtools.inspectedWindow.reload()autorisait l’exécution deinjectedScript, et du code pouvait s’exécuter dans une WebUI privilégiée à cause du délai de blocage de l’accès lors d’une navigation vers WebUI, ou d’une requêtePage.reloadrestée en attente après un crash- Google a classé la vulnérabilité P1/S1 et ajouté une vérification du
loaderIddePage.reload, une vérification d’URL dansinspectedWindow.reload(), ainsi qu’un contrôle de l’activation des tests de stratégies dans les gestionnaires WebUI - Les vulnérabilités associées ont reçu les identifiants CVE-2024-5836 et CVE-2024-6778 ; toutes deux ont obtenu un score CVSS 8.8 High, et la récompense finale a été de 20 000 $
Chromium WebUI et frontière du bac à sable
- Chromium exécute le code non fiable dans un bac à sable, et le JavaScript des extensions Chrome doit lui aussi fonctionner uniquement dans les limites des autorisations accordées et des API accessibles
- Les seules permissions d’extension peuvent déjà permettre de voler des identifiants ou l’historique du navigateur, mais, en principe, la portée de l’impact doit rester à l’intérieur du navigateur
- Une partie de l’interface graphique de Chromium est implémentée sous forme de WebUI, comme
chrome://settingsouchrome://history- Les WebUI sont écrites en HTML, CSS et JavaScript, mais comme elles doivent afficher et modifier des informations internes au navigateur, elles disposent de privilèges plus élevés que les pages web ordinaires
- Le JavaScript du frontend WebUI peut communiquer avec le code C++ natif du navigateur via des API privées
- Si l’exécution de code devient possible dans une WebUI, cela peut mener à un contournement du bac à sable de Chromium ; il est donc important d’empêcher un attaquant d’exécuter du JavaScript non fiable sur des pages
chrome:// - Par exemple, cliquer sur un téléchargement
.exedanschrome://downloadspeut ouvrir l’exécutable ; Chromium vérifie donc que l’action d’ouverture du fichier provient bien d’une saisie utilisateur réelle
Contournement de la fonctionnalité de test des stratégies d’entreprise
- La recherche de vulnérabilités a commencé dans le système de stratégies d’entreprise de Chromium
- Ce système permet aux administrateurs d’imposer certains paramètres sur des appareils appartenant à une entreprise ou à une école
- Les stratégies sont généralement liées à un compte Google et téléchargées depuis les serveurs d’administration de Google
- Les stratégies se divisent en stratégies d’appareil et stratégies utilisateur
- Les stratégies d’appareil gèrent les paramètres globaux des appareils Chrome OS
- Les stratégies utilisateur s’appliquent à un utilisateur ou à une instance de navigateur spécifique, et sont disponibles sur toutes les plateformes
- Sous Linux, il est possible d’appliquer des stratégies utilisateur à une instance de Google Chrome en plaçant des fichiers JSON dans
/etc/opt/chrome/policies, mais l’écriture dans ce répertoire nécessite les privilèges root
- Les stratégies actuellement appliquées à l’appareil peuvent être consultées dans la WebUI
chrome://policy- Cette page fournit la liste des stratégies appliquées, les journaux du service de stratégies et une fonction d’export JSON
- En général, il n’existe aucun moyen de modifier les stratégies depuis cette page
- Les notes de version Chrome Enterprise de Chrome v117 mentionnaient une page
chrome://policy/testpermettant de tester des stratégies dans les canaux Beta, Dev et Canary- Cette fonctionnalité n’était mentionnée dans la documentation de Chromium nulle part ailleurs que dans ces notes de version
- Pour l’activer normalement, une stratégie non documentée
PolicyTestPageEnabledest nécessaire - Sans cette stratégie,
chrome://policy/testredirige verschrome://policy
Échec de validation dans setLocalTestPolicies
- Le code JavaScript de
chrome://policy/testutilisesendWithPromise('setLocalTestPolicies', ...)pour définir des stratégies de testsendWithPromise()est un wrapper autour dechrome.send(), une API privée de WebUI- Cet appel envoie une requête à une fonction gestionnaire C++, qui peut effectuer des opérations internes au navigateur
- En appelant directement
setLocalTestPoliciesdepuis la console dechrome://policy, le navigateur a d’abord planté, et les journaux indiquaient qu’un tableau de stratégies était nécessaire - En fournissant une stratégie utilisateur comme
AllowDinosaurEasterEggau bon format de tableau de stratégies, une stratégie arbitraire a été définie alors même que la fonctionnalité n’avait pas été explicitement activée - Côté C++, le gestionnaire
HandleSetLocalTestPoliciesvérifiait seulement l’existence delocal_test_provider, sans vérifier si la fonctionnalité de test de stratégies était réellement autorisée LocalTestPolicyProvider::CreateIfAllowed()appelleIsPolicyTestingEnabled(nullptr, channel)- Le premier argument,
pref_service, étantnull, la vérification dePolicyTestPageEnabledest ignorée - Le seul contrôle restant consiste à vérifier si le canal de publication est
CANARYouDEFAULT
- Le premier argument,
- Dans les builds Chromium non brandés, le code
GOOGLE_CHROME_BRANDINGn’est pas compilé, si bien que le canal resteUNKNOWN- Dans l’enum,
UNKNOWN = 0etDEFAULT = UNKNOWN, donc le contrôle de canal réussit dans Chromium et les builds dérivés - Dans les builds stables brandés Google Chrome, le canal de publication est correctement défini ; ce bug ne fonctionne donc pas dans Google Chrome stable
- Dans l’enum,
Exécution de commandes shell via Browser Switcher
- Une fois la définition arbitraire de stratégies utilisateur possible, le module Legacy Browser Support des stratégies d’entreprise de Chrome est devenu un chemin de sortie du bac à sable
- Legacy Browser Support est aussi appelé Browser Switcher et est conçu pour lancer un navigateur alternatif lorsqu’un utilisateur visite certaines URL dans Chromium
- Cette fonctionnalité a été créée pour prendre en charge les utilisateurs d’Internet Explorer
- Son comportement est contrôlé par des stratégies
- En combinant les stratégies
AlternativeBrowserPathetAlternativeBrowserParameters, Chromium peut exécuter une commande shell arbitraire en tant que « navigateur alternatif »- Ces stratégies Browser Switcher existent uniquement sous Linux, macOS et Windows
- Le déroulement d’exemple est le suivant
- Définir
BrowserSwitcherEnabledàtrue - Ajouter
example.comàBrowserSwitcherUrlList - Définir
AlternativeBrowserPathsur/bin/bashsous Linux - Définir
AlternativeBrowserParameterssur["-c", "xcalc # ${url}"], par exemple
- Définir
- Lorsque le navigateur se rend sur
example.com, Browser Switcher s’active et une commande de la forme/bin/bash -c 'xcalc # https://example.com'est exécutée- La valeur substituée à
${url}est placée après#afin d’être traitée comme un commentaire shell
- La valeur substituée à
- Après avoir défini les stratégies depuis
chrome://policy, appelerwindow.open("https://example.com")suffit pour aller jusqu’à l’exécution d’une commande shell arbitraire uniquement via JavaScript
Voie de contournement via l’API d’extension DevTools
- Les étapes précédentes imposaient à la victime de coller du code malveillant dans la console du navigateur sur
chrome://policy, ce qui était peu pratique - Une voie d’exécution automatique a été trouvée via une extension Chrome malveillante
- Les extensions peuvent injecter du JavaScript dans des pages, mais elles ne devraient pas pouvoir exécuter de JavaScript dans des pages WebUI privilégiées
- Il existait quatre API principales permettant à une extension d’exécuter du JavaScript dans une page
chrome.scriptingchrome.tabsdans Manifest v2chrome.debuggerchrome.devtools.inspectedWindow
- L’investigation s’est concentrée sur
chrome.devtools.inspectedWindow, jugée probablement moins durcie- Les extensions qui utilisent l’API
chrome.devtoolsdoivent avoir un champdevtools_pagedans leur manifeste - Quand l’utilisateur ouvre DevTools, cette page est chargée dans une iframe, depuis laquelle les API
chrome.devtoolspeuvent être utilisées
- Les extensions qui utilisent l’API
- Un précédent rapport de bug de David Erceg décrivait un cas où
chrome.devtools.inspectedWindow.eval()permettait d’atteindre l’exécution de code dans WebUI- Normalement, lorsque la page inspectée navigue vers une WebUI, l’utilisation des API DevTools devrait être désactivée
- Le cœur du contournement consistait à envoyer une requête eval avant que Chrome ne désactive l’API, puis à faire arriver cette requête sur la page WebUI
inspectedWindow.reload() et particularité de about:blank
chrome.devtools.inspectedWindow.reload()peut lui aussi exécuter du JavaScript dans la page inspectée lorsqu’il reçoit un argumentinjectedScript- En appelant
inspectedWindow.reload()depuis une pageabout:blankouverte par une WebUI, l’exécution de JavaScript dans une page privilégiée devenait possible- L’URL
about:blankn’a rien de particulier en elle-même, mais elle hérite des privilèges et de l’origine de la page qui l’a ouverte - Une page
about:blankouverte parchrome://settingsétait une page privilégiée dont l’origine étaitchrome://settings
- L’URL
- Le code de désactivation de l’API DevTools vérifiait uniquement l’URL de la cible inspectée, pas son origine
- Même si l’URL semble ordinaire, l’origine peut être une origine privilégiée
- Le chemin via
about:blankseul était difficile à utiliser directement dans une chaîne d’exploitation, carchrome://policyn’ouvre pas de pop-upabout:blank - Cependant, même dans des situations où
inspectedWindow.eval()échouait,inspectedWindow.reload()exécutait du JavaScript danschrome://settings- Cela montrait que
eval()disposait de ses propres vérifications équivalentes à un contrôle d’origine, contrairement àreload()qui n’avait pas le même niveau de contrôle
- Cela montrait que
Stabilisation d’une race condition par une méthode basée sur un crash
- La première chaîne d’exploitation appelait
inspectedWindow.reload()en boucle pour viser la courte fenêtre de temps entre la navigation de la page inspectée vers une WebUI et la désactivation de l’API par la page DevTools- Elle supposait que la page inspectée et la page DevTools se trouvaient dans des processus distincts
- Si une requête
reload()arrivait entre l’instant où la page naviguait verschrome://policyet la désactivation de l’API DevTools, du code s’exécutait dans la WebUI
- Cette méthode fonctionnait, mais sa fiabilité était faible
- Après réglage, le taux de réussite atteignait environ 70 %
- Il s’agissait d’une vulnérabilité grave, mais son instabilité pouvait en réduire la sévérité
- Ensuite, comme dans l’approche précédente de David Erceg, il a été testé si le comportement laissant des requêtes de débogage après un crash d’onglet pouvait aussi s’appliquer à
inspectedWindow.reload() - En déclenchant deux instructions
debuggerd’affilée, l’onglet plantait, et une requêtePage.reloadrestait en file d’attente, pouvant s’exécuter après la navigation vers une WebUI- Cette méthode ne nécessitait plus de race condition et fonctionnait de façon fiable à 100 %
- Dans le correctif du bug précédent, Google avait modifié Chrome pour supprimer les requêtes de débogage en attente après un crash, mais les requêtes
Page.reloadavaient été laissées comme exceptioninspectedWindow.reload()envoie en interne une requêtePage.reload, il est donc affecté par cette exception- Le correctif de l’époque n’empêchait pas
Page.reloadd’exécuter des scripts
- Un crash d’onglet était aussi possible en provoquant un manque de mémoire, mais le PoC final a utilisé le crash plus rapide via
debugger
Chaîne d’exploitation finale et interaction utilisateur
- Le PoC final fonctionne dans l’ordre suivant
- Utiliser la vulnérabilité
chrome.devtools.inspectedWindow.reload()pour exécuter un payload JavaScript danschrome://policy - Le payload appelle
sendWithPromise("setLocalTestPolicies", policy)pour définir des stratégies utilisateur - Définir
BrowserSwitcherEnabled,BrowserSwitcherUrlList,AlternativeBrowserPathetAlternativeBrowserParameters - Déclencher Browser Switcher via
window.open()ou une navigation de page afin d’exécuter une commande shell
- Utiliser la vulnérabilité
- Le PoC utilise des commandes qui lancent la calculatrice selon l’OS
- Windows :
C:\Windows\System32\cmd.exeetcalc.exe - Linux :
/bin/bashetxcalc - macOS :
/bin/bashetopen -na Calculator
- Windows :
- L’interaction utilisateur se limitait à amener la victime à ouvrir DevTools
- Le message « extension install error » affiché en exemple servait à tromper l’utilisateur pour qu’il ouvre DevTools
- Une fois DevTools ouvert, la chaîne menant à la sortie du bac à sable démarrait
Correctifs de Google et attribution des CVE
- Après le signalement, Google a rapidement confirmé la vulnérabilité et l’a classée P1/S1
- P1/S1 signifie high priority et high severity
- Au cours des semaines suivantes, trois correctifs principaux ont été appliqués
- Ajout d’un argument
loaderIdà la commandePage.reloadet vérification deloaderIDcôté renderer- Cela fait en sorte que la commande ne soit valide que pour une seule origine et qu’elle ne fonctionne pas si elle arrive involontairement sur une page privilégiée
- Vérification d’URL dans la fonction
inspectedWindow.reload()- Le mécanisme ne dépend plus uniquement de la révocation de l’accès à l’API d’extension
- Vérification de l’activation des stratégies de test dans le gestionnaire WebUI
- Les stratégies de test sont ainsi entièrement bloquées
- Ajout d’un argument
- La vulnérabilité liée à la race condition a reçu l’identifiant CVE-2024-5836
- Son score de sévérité CVSS est 8.8 High
- La vulnérabilité liée au crash de la page inspectée a reçu l’identifiant CVE-2024-6778
- Cette vulnérabilité a elle aussi obtenu un score CVSS de sévérité 8.8
- Une fois les correctifs fusionnés dans la branche de release, le panel Chrome VRP a décidé de la récompense, dont le montant final a été de 20 000 $
Calendrier de divulgation et ressources
- La chronologie est la suivante
- 16 avril : découverte du bug des stratégies de test
- 29 avril : découverte du bug de race condition dans
inspectedWindow.reload() - 1er mai : signalement du bug à Google
- 4 mai : classification P1/S1 par Google
- 5 mai : découverte du bug lié au crash de la page inspectée, puis mise à jour du signalement
- 6 mai : Google demande des rapports de bug séparés pour chaque partie de la chaîne
- 8 juillet : le rapport de bug est marqué comme corrigé
- 13 juillet : transmission au panel Chrome VRP pour décision sur la récompense
- 17 juillet : le panel VRP fixe la récompense à 20 000 $
- 15 octobre : publication du rapport de bug complet
- Le rapport de bug original associé est consultable sur crbug.com/338248595
- Les PoC des différentes parties de la vulnérabilité sont publiés dans un dépôt GitHub
- Le bug
inspectedWindow.reloadfonctionnait jusqu’à Chrome v45 - Déployer auprès de tous les utilisateurs des fonctionnalités non documentées, peu finalisées et non sûres peut transformer de simples erreurs combinées en vulnérabilités de forte sévérité
1 commentaires
Avis sur Hacker News
Il était dit que l’URL de la page est remplacée par
${url}, et que si on la place après#, elle devient un commentaire pour ne pas casser la commande ; mais cette stratégie contient-elle une logique de validation exigeant que l’URL soit transmise quelque part dansAlternativeBrowserParameters?Un lycéen intéressé par la programmation, le développement web et la cybersécurité, c’est vraiment impressionnant
Son éthique professionnelle est aussi admirable, jusqu’au respect d’une procédure de divulgation responsable ; il semble promis à un grand avenir
Excellent article et excellent travail ; on avait l’impression de suivre avec lui l’excitation qui montait à mesure que les découvertes s’enchaînaient
La récompense était amplement méritée
L’enchaînement des vulnérabilités est propre et l’article est excellent. J’ai aussi apprécié la façon dont il décortique le fonctionnement du code vulnérable
Les astuces toutes simples du genre « Appuyez sur F12 pour réessayer » m’épatent à chaque fois, c’est vraiment malicieux
Ça me rappelle l’époque où j’avais utilisé la même API pour déboguer le shell
croshde Chrome OS, contourner les protections de l’OS et obtenir un accès root sur une machine de développeur. C’était CVE-2014-3172Cela dit, l’auteur de cet article a dû contourner des obstacles bien plus difficiles, et son travail est vraiment excellent
Il est trop tard ce soir pour creuser en détail ce qui a cassé dans la validation WebUI, mais j’apprécie qu’il soit allé jusqu’au bout pour le découvrir
Se méfier de la chaîne d’outils de ce que nous déployons est une attitude assez standard, mais en même temps nous faisons énormément confiance aux outils de développement magiquement pratiques de grandes entreprises comme Google ou Microsoft. Au final, c’est parce qu’on veut écrire et tester notre code plutôt que de s’inquiéter de ce qui se cache dans Chromium ou VSCode
C’est l’un des meilleurs articles que j’aie lus
Un travail de traque vraiment brillant
L’effort nécessaire pour fouiller dans le code du navigateur et arriver jusque-là est impressionnant, et l’article est très intéressant et détaillé
Un lycéen, vraiment ? Waouh, c’est incroyable
Le projet Chromium a supprimé
chrome://net-internalsau motif que c’était trop complexe, puis a ajoutéchrome://policyavec une prise en charge à moitié finie de l’édition JSON