42 KiB
Méthodologie de pentesting des extensions de navigateur
{{#include ../../banners/hacktricks-training.md}}
Informations de base
Les extensions de navigateur sont écrites en JavaScript et chargées par le navigateur en arrière-plan. Elles possèdent leur DOM mais peuvent interagir avec le DOM d'autres sites. Cela signifie qu'elles peuvent compromettre la confidentialité, l'intégrité et la disponibilité (CIA) d'autres sites.
Principaux composants
La structure d'une extension se visualise mieux et se compose de trois composants. Examinons chacun en détail.

Content Scripts
Chaque content script a un accès direct au DOM d'une page web unique et est ainsi exposé à des entrées potentiellement malveillantes. Cependant, le content script ne possède aucune permission autre que la possibilité d'envoyer des messages à l'extension core.
Extension Core
L'extension core contient la plupart des privilèges/accès de l'extension, mais l'extension core ne peut interagir avec le contenu web que via XMLHttpRequest et les content scripts. De plus, l'extension core n'a pas d'accès direct à la machine hôte.
Native Binary
L'extension peut inclure un binaire natif qui peut accéder à la machine hôte avec les privilèges complets de l'utilisateur. Le binaire natif interagit avec l'extension core via l'API standard Netscape Plugin Application Programming Interface (NPAPI) utilisée par Flash et d'autres plug-ins de navigateur.
Boundaries
Caution
Pour obtenir les privilèges complets de l'utilisateur, un attaquant doit convaincre l'extension de transmettre une entrée malveillante du content script vers le core de l'extension, puis du core de l'extension vers le binaire natif.
Chaque composant de l'extension est séparé des autres par des barrières de protection solides. Chaque composant s'exécute dans un processus distinct du système d'exploitation. Les content scripts et les extension cores s'exécutent dans des processus sandbox non accessibles à la plupart des services du système d'exploitation.
De plus, les content scripts sont séparés de leurs pages web associées en s'exécutant dans un tas JavaScript séparé. Le content script et la page web ont accès au même DOM sous-jacent, mais les deux n'échangent jamais de pointeurs JavaScript, empêchant le leak de fonctionnalités JavaScript.
manifest.json
Une extension Chrome n'est qu'un dossier ZIP avec une extension de fichier .crx. Le core de l'extension est le fichier manifest.json
à la racine du dossier, qui spécifie la structure, les permissions et d'autres options de configuration.
Exemple:
{
"manifest_version": 2,
"name": "My extension",
"version": "1.0",
"permissions": ["storage"],
"content_scripts": [
{
"js": ["script.js"],
"matches": ["https://example.com/*", "https://www.example.com/*"],
"exclude_matches": ["*://*/*business*"]
}
],
"background": {
"scripts": ["background.js"]
},
"options_ui": {
"page": "options.html"
}
}
content_scripts
Les content scripts sont chargés chaque fois que l'utilisateur navigue vers une page correspondante, dans notre cas toute page correspondant à l'expression https://example.com/*
et ne correspondant pas au regex *://*/*/business*
. Ils s'exécutent comme les propres scripts de la page et ont un accès arbitraire au Document Object Model (DOM).
"content_scripts": [
{
"js": [
"script.js"
],
"matches": [
"https://example.com/*",
"https://www.example.com/*"
],
"exclude_matches": ["*://*/*business*"],
}
],
Pour inclure ou exclure davantage d'URLs, il est également possible d'utiliser include_globs
et exclude_globs
.
Ceci est un exemple de content script qui ajoutera un bouton explain à la page et utilisera the storage API pour récupérer la valeur message
depuis le stockage de l'extension.
chrome.storage.local.get("message", (result) => {
let div = document.createElement("div")
div.innerHTML = result.message + " <button>Explain</button>"
div.querySelector("button").addEventListener("click", () => {
chrome.runtime.sendMessage("explain")
})
document.body.appendChild(div)
})

Un message est envoyé aux pages de l'extension par le content script lorsque ce bouton est cliqué, via l'utilisation de la runtime.sendMessage() API. Ceci est dû à la limitation du content script en matière d'accès direct aux APIs, storage
étant l'une des rares exceptions. Pour les fonctionnalités dépassant ces exceptions, des messages sont envoyés aux pages de l'extension avec lesquelles les content scripts peuvent communiquer.
Warning
Selon le navigateur, les capacités du content script peuvent varier légèrement. Pour les navigateurs basés sur Chromium, la liste des capacités est disponible dans la Chrome Developers documentation, et pour Firefox, le MDN sert de source principale.
Il est également important de noter que les content scripts peuvent communiquer avec les background scripts, leur permettant d'effectuer des actions et de renvoyer des réponses.
Pour visualiser et déboguer les content scripts dans Chrome, le menu Chrome developer tools est accessible via Options > More tools > Developer tools OU en appuyant sur Ctrl + Shift + I.
Une fois les developer tools affichés, cliquez sur l'onglet Source tab, puis sur l'onglet Content Scripts. Cela permet d'observer les content scripts en cours d'exécution provenant de différentes extensions et de poser des points d'arrêt pour suivre le flux d'exécution.
Content scripts injectés
Tip
Notez que Content Scripts ne sont pas obligatoires car il est aussi possible d'injecter dynamiquement des scripts et de les injecter programmatiquement dans des pages web via
tabs.executeScript
. Cela fournit en fait des contrôles plus granulaires.
Pour l'injection programmatique d'un content script, l'extension doit disposer des host permissions pour la page dans laquelle les scripts seront injectés. Ces permissions peuvent être obtenues soit en les demandant dans le manifest de l'extension, soit temporairement via activeTab.
Exemple d'extension basée sur activeTab
{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}
- Injecter un fichier JS au clic:
// content-script.js
document.body.style.backgroundColor = "orange"
//service-worker.js - Inject the JS file
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["content-script.js"],
})
})
- Injecter une fonction au clic :
//service-worker.js - Inject a function
function injectedFunction() {
document.body.style.backgroundColor = "orange"
}
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: injectedFunction,
})
})
Exemple avec autorisations de script
// service-workser.js
chrome.scripting.registerContentScripts([
{
id: "test",
matches: ["https://*.example.com/*"],
excludeMatches: ["*://*/*business*"],
js: ["contentScript.js"],
},
])
// Another example
chrome.tabs.executeScript(tabId, { file: "content_script.js" })
Pour inclure ou exclure davantage d'URLs, il est également possible d'utiliser include_globs
et exclude_globs
.
Scripts de contenu run_at
Le champ run_at
contrôle quand les fichiers JavaScript sont injectés dans la page web. La valeur préférée et par défaut est "document_idle"
.
The possible values are:
document_idle
: Dès que possibledocument_start
: Après les fichierscss
, mais avant que le DOM ne soit construit ou que d'autres scripts ne s'exécutent.document_end
: Immédiatement après que le DOM est complet, mais avant que les sous-ressources comme les images et les frames ne soient chargées.
Via manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.example.com/*"],
"run_at": "document_idle",
"js": ["contentScript.js"]
}
],
...
}
Via service-worker.js
chrome.scripting.registerContentScripts([
{
id: "test",
matches: ["https://*.example.com/*"],
runAt: "document_idle",
js: ["contentScript.js"],
},
])
background
Les messages envoyés par les content scripts sont reçus par la background page, qui joue un rôle central dans la coordination des composants de l'extension. Notamment, la background page persiste tout au long de la durée de vie de l'extension, fonctionnant discrètement sans interaction directe avec l'utilisateur. Elle possède son propre Document Object Model (DOM), permettant des interactions complexes et la gestion d'état.
Points clés:
- Background Page Role: Agit comme le centre névralgique de l'extension, assurant la communication et la coordination entre les différentes parties de l'extension.
- Persistence: C'est une entité toujours présente, invisible pour l'utilisateur mais essentielle au fonctionnement de l'extension.
- Automatic Generation: Si elle n'est pas définie explicitement, le navigateur créera automatiquement une background page. Cette page auto-générée inclura tous les background scripts spécifiés dans le manifest de l'extension, garantissant le fonctionnement transparent des tâches en arrière-plan de l'extension.
Tip
La commodité offerte par le navigateur en générant automatiquement une background page (lorsqu'elle n'est pas déclarée explicitement) garantit que tous les background scripts nécessaires sont intégrés et opérationnels, simplifiant le processus de configuration de l'extension.
Exemple de background script:
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request == "explain") {
chrome.tabs.create({ url: "https://example.net/explanation" })
}
})
Il utilise runtime.onMessage API pour écouter les messages. Lorsqu'un message "explain"
est reçu, il utilise tabs API pour ouvrir une page dans un nouvel onglet.
Pour déboguer le script d'arrière-plan vous pouvez aller aux extension details and inspect the service worker, cela ouvrira les outils de développement avec le script d'arrière-plan :
Options pages and other
Les extensions de navigateur peuvent contenir différents types de pages :
- Action pages sont affichées dans un drop-down when the extension icon est cliqué.
- Pages que l'extension load in a new tab.
- Option Pages : Cette page s'affiche au-dessus de l'extension lorsqu'on clique dessus. Dans le manifest précédent, dans mon cas j'ai pu accéder à cette page via
chrome://extensions/?options=fadlhnelkbeojnebcbkacjilhnbjfjca
ou en cliquant :

Notez que ces pages ne sont pas persistantes comme les pages d'arrière-plan car elles chargent dynamiquement le contenu selon les besoins. Malgré cela, elles partagent certaines capacités avec la page d'arrière-plan :
- Communication with Content Scripts: De la même façon que la page d'arrière-plan, ces pages peuvent recevoir des messages depuis des content scripts, facilitant l'interaction au sein de l'extension.
- Access to Extension-Specific APIs: Ces pages bénéficient d'un accès complet aux APIs spécifiques à l'extension, sous réserve des permissions définies pour l'extension.
permissions
& host_permissions
permissions
et host_permissions
sont des entrées du manifest.json
qui indiqueront quelles permissions l'extension du navigateur possède (storage, location...) et sur quelles pages web.
Comme les extensions de navigateur peuvent être très privilégiées, une extension malveillante ou compromise pourrait permettre à un attaquant différents moyens de voler des informations sensibles et d'espionner l'utilisateur.
Voir comment ces paramètres fonctionnent et comment ils peuvent être abusés dans :
{{#ref}} browext-permissions-and-host_permissions.md {{#endref}}
content_security_policy
Une content security policy peut également être déclarée dans le manifest.json
. Si une est définie, elle pourrait être vulnérable.
Le paramètre par défaut pour les pages d'extension du navigateur est plutôt restrictif :
script-src 'self'; object-src 'self';
Pour plus d'infos sur CSP and potential bypasses, consultez:
{{#ref}} ../content-security-policy-csp-bypass/ {{#endref}}
web_accessible_resources
Pour qu'une page web puisse accéder à une page d'une extension de navigateur, par exemple une page .html
, cette page doit être mentionnée dans le champ web_accessible_resources
de manifest.json
.
Par exemple:
{
...
"web_accessible_resources": [
{
"resources": [ "images/*.png" ],
"matches": [ "https://example.com/*" ]
},
{
"resources": [ "fonts/*.woff" ],
"matches": [ "https://example.com/*" ]
}
],
...
}
Ces pages sont accessibles via une URL comme :
chrome-extension://<extension-id>/message.html
Dans les extensions publiques, l'extension-id est accessible :

Cependant, si le paramètre manifest.json
use_dynamic_url
est utilisé, cet id peut être dynamique.
Tip
Notez que même si une page est mentionnée ici, elle peut être protégée contre le ClickJacking grâce à la Content Security Policy. Vous devez donc également la vérifier (frame-ancestors section) avant de confirmer qu'une attaque ClickJacking est possible.
Le fait de pouvoir accéder à ces pages les rend potentiellement vulnérables au ClickJacking :
{{#ref}} browext-clickjacking.md {{#endref}}
Tip
Autoriser le chargement de ces pages uniquement par l'extension et non par des URL aléatoires pourrait empêcher les attaques ClickJacking.
Caution
Notez que les pages définies dans
web_accessible_resources
et d'autres pages de l'extension sont également capables de contacter les background scripts. Donc si l'une de ces pages est vulnérable à XSS, cela pourrait déboucher sur une vulnérabilité plus importante.De plus, notez que vous ne pouvez ouvrir dans des iframes que les pages indiquées dans
web_accessible_resources
, mais depuis un nouvel onglet il est possible d'accéder à n'importe quelle page de l'extension en connaissant l'extension ID. Par conséquent, si un XSS est découvert en abusant des mêmes paramètres, il pourrait être exploité même si la page n'est pas configurée dansweb_accessible_resources
.
externally_connectable
D'après la docs, la propriété de manifest "externally_connectable"
déclare quelles extensions et quelles pages web peuvent se connecter à votre extension via runtime.connect et runtime.sendMessage.
- Si la clé
externally_connectable
n'est pas déclarée dans le manifest de votre extension ou si elle est déclarée comme"ids": ["*"]
, toutes les extensions peuvent se connecter, mais aucune page web ne peut se connecter. - Si des IDs spécifiques sont indiquées, comme dans
"ids": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
, seules ces applications peuvent se connecter. - Si des matches sont spécifiés, ces web apps pourront se connecter :
"matches": [
"https://*.google.com/*",
"*://*.chromium.org/*",
- Si c'est spécifié comme vide :
"externally_connectable": {}
, aucune app ou page web ne pourra se connecter.
Moins d'extensions et d'URLs indiquées ici, plus petite sera la surface d'attaque.
Caution
Si une page web vulnerable to XSS or takeover est indiquée dans
externally_connectable
, un attaquant pourra envoyer des messages directement au background script, contournant complètement le Content Script et son CSP.Par conséquent, il s'agit d'un contournement très puissant.
De plus, si le client installe une extension malveillante, même si elle n'est pas autorisée à communiquer avec l'extension vulnérable, elle pourrait injecter XSS data in an allowed web page ou abuser des APIs
WebRequest
ouDeclarativeNetRequest
pour manipuler les requêtes sur un domaine ciblé, altérant la requête d'une page pour un JavaScript file. (Notez que le CSP de la page ciblée pourrait prévenir ces attaques). Cette idée provient de cet article.
Communication summary
Extension <--> WebApp
Pour communiquer entre le content script et la page web, on utilise généralement des post messages. Ainsi, dans l'application web vous trouverez généralement des appels à la fonction window.postMessage
et, dans le content script, des listeners comme window.addEventListener
. Notez cependant que l'extension peut aussi communiquer avec l'application web en envoyant un Post Message (et donc la page web doit s'y attendre) ou simplement faire charger un nouveau script par la page web.
Inside the extension
Généralement la fonction chrome.runtime.sendMessage
est utilisée pour envoyer un message au sein de l'extension (généralement pris en charge par le background
script) et, pour le recevoir et le gérer, un listener est déclaré en appelant chrome.runtime.onMessage.addListener
.
Il est aussi possible d'utiliser chrome.runtime.connect()
pour avoir une connexion persistante au lieu d'envoyer des messages isolés ; on peut l'utiliser pour envoyer et recevoir des messages comme dans l'exemple suivant :
chrome.runtime.connect()
exemple
```javascript
var port = chrome.runtime.connect()
// Listen for messages from the web page window.addEventListener( "message", (event) => { // Only accept messages from the same window if (event.source !== window) { return }
// Check if the message type is "FROM_PAGE" if (event.data.type && event.data.type === "FROM_PAGE") { console.log("Content script received: " + event.data.text) // Forward the message to the background script port.postMessage({ type: "FROM_PAGE", text: event.data.text }) } }, false )
// Listen for messages from the background script port.onMessage.addListener(function (msg) { console.log("Content script received message from background script:", msg) // Handle the response message from the background script })
</details>
Il est aussi possible d'envoyer des messages depuis un script d'arrière-plan vers un script de contenu situé dans un onglet spécifique en appelant **`chrome.tabs.sendMessage`** où vous devrez indiquer le **ID de l'onglet** auquel envoyer le message.
### Depuis les `externally_connectable` autorisés vers l'extension
**Les web apps et les extensions de navigateur externes autorisées** dans la configuration `externally_connectable` peuvent envoyer des requêtes en utilisant :
```javascript
chrome.runtime.sendMessage(extensionId, ...
Lorsque nécessaire pour mentionner l'extension ID.
Native Messaging
Il est possible que les background scripts communiquent avec des binaries du système, ce qui peut être exposé à des vulnérabilités critiques telles que les RCEs si cette communication n'est pas correctement sécurisée. More on this later.
chrome.runtime.sendNativeMessage(
"com.my_company.my_application",
{ text: "Hello" },
function (response) {
console.log("Received " + response)
}
)
Communication Web ↔︎ Content Script
Les environnements où opèrent les content scripts et où résident les pages hôtes sont séparés les uns des autres, garantissant l'isolation. Malgré cette isolation, les deux peuvent interagir avec le Document Object Model (DOM) de la page, une ressource partagée. Pour que la page hôte communique avec le content script, ou indirectement avec l'extension via le content script, elle doit utiliser le DOM accessible aux deux comme canal de communication.
Post Messages
// This is like "chrome.runtime.sendMessage" but to maintain the connection
var port = chrome.runtime.connect()
window.addEventListener(
"message",
(event) => {
// We only accept messages from ourselves
if (event.source !== window) {
return
}
if (event.data.type && event.data.type === "FROM_PAGE") {
console.log("Content script received: " + event.data.text)
// Forward the message to the background script
port.postMessage(event.data.text)
}
},
false
)
document.getElementById("theButton").addEventListener(
"click",
() => {
window.postMessage(
{ type: "FROM_PAGE", text: "Hello from the webpage!" },
"*"
)
},
false
)
Une communication Post Message sécurisée devrait vérifier l'authenticité du message reçu, cela peut être fait en vérifiant :
event.isTrusted
: This is True only if the event was triggered by a users action- Le content script peut n'attendre un message que si l'utilisateur effectue une action
- origin domain: n'accepter un message que d'une allowlist de domaines.
- If a regex is used, be very careful
- Source:
received_message.source !== window
can be used to check if the message was from the same window where the Content Script is listening.
Les vérifications précédentes, même si elles sont effectuées, pourraient être vulnérables — consultez la page suivante : potential Post Message bypasses :
{{#ref}} ../postmessage-vulnerabilities/ {{#endref}}
Iframe
Un autre moyen de communication possible peut être via des Iframe URLs, vous trouverez un exemple dans :
{{#ref}} browext-xss-example.md {{#endref}}
DOM
Ce n'est pas "exactement" une voie de communication, mais le web et le content script auront accès au web DOM. Donc, si le content script lit des informations à partir de celui-ci, en faisant confiance au web DOM, le web pourrait modifier ces données (parce que le web ne devrait pas être digne de confiance, ou parce que le web est vulnérable à XSS) et compromettre le Content Script.
Vous pouvez également trouver un exemple de DOM based XSS to compromise a browser extension dans :
{{#ref}} browext-xss-example.md {{#endref}}
Communication Content Script ↔︎ Background Script
Un Content Script peut utiliser les fonctions runtime.sendMessage() or tabs.sendMessage() pour envoyer un message one-time JSON-serializable.
Pour gérer la réponse, utilisez la Promise retournée. Cependant, pour la rétrocompatibilité, vous pouvez toujours passer un callback comme dernier argument.
Envoyer une requête depuis un content script ressemble à ceci :
;(async () => {
const response = await chrome.runtime.sendMessage({ greeting: "hello" })
// do something with response here, not outside the function
console.log(response)
})()
Envoyer une requête depuis l'extension (généralement un background script). Exemple montrant comment envoyer un message au content script dans l'onglet sélectionné :
// From https://stackoverflow.com/questions/36153999/how-to-send-a-message-between-chrome-extension-popup-and-content-script
;(async () => {
const [tab] = await chrome.tabs.query({
active: true,
lastFocusedWindow: true,
})
const response = await chrome.tabs.sendMessage(tab.id, { greeting: "hello" })
// do something with response here, not outside the function
console.log(response)
})()
Du côté destinataire, vous devez mettre en place un runtime.onMessage event listener pour gérer le message. Cela est identique depuis un content script ou une extension page.
// From https://stackoverflow.com/questions/70406787/javascript-send-message-from-content-js-to-background-js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
console.log(
sender.tab
? "from a content script:" + sender.tab.url
: "from the extension"
)
if (request.greeting === "hello") sendResponse({ farewell: "goodbye" })
})
Dans l'exemple mis en évidence, sendResponse()
a été exécuté de manière synchrone. Pour modifier le gestionnaire d'événement onMessage
afin que sendResponse()
s'exécute de façon asynchrone, il est impératif d'incorporer return true;
.
Il est important de noter que lorsque plusieurs pages sont configurées pour recevoir des événements onMessage
, la première page à exécuter sendResponse()
pour un événement donné sera la seule à pouvoir délivrer la réponse efficacement. Les réponses ultérieures au même événement ne seront pas prises en compte.
Lors de la création de nouvelles extensions, la préférence doit aller aux promises plutôt qu'aux callbacks. En ce qui concerne l'utilisation de callbacks, la fonction sendResponse()
n'est considérée comme valide que si elle est exécutée directement dans le contexte synchrone, ou si le gestionnaire d'événement indique une opération asynchrone en retournant true
. Si aucun des gestionnaires ne retourne true
, ou si la fonction sendResponse()
est supprimée de la mémoire (garbage-collected), le callback associé à sendMessage()
sera déclenché par défaut.
Native Messaging
Les extensions de navigateur permettent également de communiquer avec des binaires du système via stdin. L'application doit installer un json l'indiquant dans un json comme :
{
"name": "com.my_company.my_application",
"description": "My Application",
"path": "C:\\Program Files\\My Application\\chrome_native_messaging_host.exe",
"type": "stdio",
"allowed_origins": ["chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/"]
}
Où le name
est la chaîne passée à runtime.connectNative()
ou runtime.sendNativeMessage()
pour communiquer avec l'application depuis les scripts d'arrière-plan de l'extension de navigateur. Le path
est le chemin vers le binaire, il n'y a qu'un seul type
valide qui est stdio (utiliser stdin et stdout) et les allowed_origins
indiquent les extensions qui peuvent y accéder (et ne peuvent pas contenir de caractère générique).
Chrome/Chromium recherchera ce json dans certaines clés de registre Windows et dans certains chemins sur macOS et Linux (plus d'infos dans les docs).
Tip
L'extension du navigateur a également besoin que la permission
nativeMessaing
soit déclarée pour pouvoir utiliser cette communication.
Voici à quoi ressemble le code d'un script d'arrière-plan envoyant des messages à une application native :
chrome.runtime.sendNativeMessage(
"com.my_company.my_application",
{ text: "Hello" },
function (response) {
console.log("Received " + response)
}
)
In this blog post, un schéma vulnérable abusant des native messages est proposé :
- L'extension de navigateur a un pattern wildcard pour le content script.
- Le content script transmet des messages
postMessage
au background script en utilisantsendMessage
. - Le background script transmet le message à l'application native en utilisant
sendNativeMessage
. - L'application native traite le message de manière dangereuse, conduisant à une exécution de code.
Et à l'intérieur, un exemple expliquant comment passer de n'importe quelle page à RCE en abusant d'une extension de navigateur est présenté.
Sensitive Information in Memory/Code/Clipboard
Si une extension de navigateur stocke des informations sensibles dans sa mémoire, celles-ci peuvent être extraites (surtout sur les machines Windows) et recherchées pour retrouver ces informations.
Par conséquent, la mémoire de l'extension de navigateur ne doit pas être considérée comme sûre et les informations sensibles telles que des identifiants ou des phrases mnémoniques ne doivent pas y être stockées.
Bien sûr, ne mettez pas d'informations sensibles dans le code, car il sera public.
Pour dumper la mémoire du navigateur, vous pouvez dump the process memory ou aller dans les settings de l'extension de navigateur, cliquer sur Inspect pop-up
-> Dans la section Memory
-> Take a snaphost
et faire CTRL+F
pour rechercher dans le snapshot des informations sensibles.
De plus, des informations hautement sensibles comme des clés mnémoniques ou des mots de passe ne devraient pas pouvoir être copiées dans le presse-papiers (ou au moins les supprimer du presse-papiers après quelques secondes), car des processus surveillant le presse-papiers pourraient alors les récupérer.
Loading an Extension in the Browser
- Download the Browser Extension & unzipped
- Go to
chrome://extensions/
and enable theDeveloper Mode
- Click the
Load unpacked
button
In Firefox you go to about:debugging#/runtime/this-firefox
and click Load Temporary Add-on
button.
Getting the source code from the store
Le code source d'une extension Chrome peut être obtenu de plusieurs manières. Vous trouverez ci-dessous des explications détaillées et des instructions pour chaque option.
Download Extension as ZIP via Command Line
Le code source d'une extension Chrome peut être téléchargé en tant que fichier ZIP en utilisant la ligne de commande. Cela implique d'utiliser curl
pour récupérer le fichier ZIP depuis une URL spécifique puis d'extraire le contenu du ZIP dans un répertoire. Voici les étapes :
- Remplacez
"extension_id"
par l'ID réel de l'extension. - Exécutez les commandes suivantes :
extension_id=your_extension_id # Replace with the actual extension ID
curl -L -o "$extension_id.zip" "https://clients2.google.com/service/update2/crx?response=redirect&os=mac&arch=x86-64&nacl_arch=x86-64&prod=chromecrx&prodchannel=stable&prodversion=44.0.2403.130&x=id%3D$extension_id%26uc"
unzip -d "$extension_id-source" "$extension_id.zip"
Utiliser le site CRX Viewer
Utiliser l'extension CRX Viewer
Une autre méthode pratique consiste à utiliser le Chrome Extension Source Viewer, qui est un projet open-source. Il peut être installé depuis le Chrome Web Store. Le code source du viewer est disponible dans son GitHub repository.
Voir le code source d'une extension installée localement
Les extensions Chrome installées localement peuvent aussi être inspectées. Voici comment :
- Accédez au répertoire de profil local de Chrome en visitant
chrome://version/
et en localisant le champ "Profile Path". - Naviguez vers le sous-dossier
Extensions/
dans le répertoire de profil. - Ce dossier contient toutes les extensions installées, généralement avec leur code source dans un format lisible.
Pour identifier les extensions, vous pouvez faire correspondre leurs IDs à leurs noms :
- Activez le Developer Mode sur la page
about:extensions
pour voir les IDs de chaque extension. - Dans le dossier de chaque extension, le fichier
manifest.json
contient un champname
lisible, vous aidant à identifier l'extension.
Utiliser un archiveur ou un outil de décompression
Allez sur le Chrome Web Store et téléchargez l'extension. Le fichier aura l'extension .crx
. Changez l'extension du fichier de .crx
en .zip
. Utilisez un archiveur (comme WinRAR, 7-Zip, etc.) pour extraire le contenu du fichier ZIP.
Utiliser le Developer Mode dans Chrome
Ouvrez Chrome et allez sur chrome://extensions/
. Activez "Developer mode" en haut à droite. Cliquez sur "Load unpacked extension...". Naviguez jusqu'au répertoire de votre extension. Cela ne télécharge pas le code source, mais c'est utile pour consulter et modifier le code d'une extension déjà téléchargée ou développée.
Chrome extension manifest dataset
Pour essayer de repérer des browser extensions vulnérables, vous pouvez utiliser lehttps://github.com/palant/chrome-extension-manifests-dataset et vérifier leurs fichiers manifest pour des signes potentiellement vulnérables. Par exemple, pour rechercher des extensions ayant plus de 25000 utilisateurs, content_scripts
et la permission nativeMessaing
:
# Query example from https://spaceraccoon.dev/universal-code-execution-browser-extensions/
node query.js -f "metadata.user_count > 250000" "manifest.content_scripts?.length > 0 && manifest.permissions?.includes('nativeMessaging')"
Post-exploitation: Forced extension load & persistence (Windows)
Stealthy technique to backdoor Chromium by directly editing per-user Preferences and forging valid HMACs, causing the browser to accept and activate an arbitrary unpacked extension without prompts or flags.
{{#ref}} forced-extension-load-preferences-mac-forgery-windows.md {{#endref}}
Security Audit Checklist
Even though Browser Extensions have a limited attack surface, some of them might contain vulnerabilities or potential hardening improvements. The following ones are the most common ones:
- Limit as much as possible requested
permissions
- Limit as much as possible
host_permissions
- Use a strong
content_security_policy
- Limit as much as possible the
externally_connectable
, if none is needed and possible, do not leave it by default, specify{}
- If URL vulnerable to XSS or to takeover is mentioned here, an attacker will be able to send messages to the background scripts directly. Very powerful bypass.
- Limit as much as possible the
web_accessible_resources
, even empty if possible. - If
web_accessible_resources
is not none, check for ClickJacking - If any communication occurs from the extension to the web page, check for XSS vulnerabilities caused in the communication.
- If Post Messages are used, check for Post Message vulnerabilities.
- If the Content Script access DOM details, check that they aren't introducing a XSS if they get modified by the web
- Make a special emphasis if this communication is also involved in the Content Script -> Background script communication
- If the background script is communicating via native messaging check the communication is secure and sanitized
- Sensitive information shouldn't be stored inside the Browser Extension code
- Sensitive information shouldn't be stored inside the Browser Extension memory
- Sensitive information shouldn't be stored inside the file system unprotected
Browser Extension Risks
- The app https://crxaminer.tech/ analyzes some data like the permissions browser extension requests to give a risk level of using the browser extension.
Tools
Tarnish
- Pulls any Chrome extension from a provided Chrome webstore link.
- manifest.json viewer: simply displays a JSON-prettified version of the extension’s manifest.
- Fingerprint Analysis: Detection of web_accessible_resources and automatic generation of Chrome extension fingerprinting JavaScript.
- Potential Clickjacking Analysis: Detection of extension HTML pages with the web_accessible_resources directive set. These are potentially vulnerable to clickjacking depending on the purpose of the pages.
- Permission Warning(s) viewer: which shows a list of all the Chrome permission prompt warnings which will be displayed upon a user attempting to install the extension.
- Dangerous Function(s): shows the location of dangerous functions which could potentially be exploited by an attacker (e.g. functions such as innerHTML, chrome.tabs.executeScript).
- Entry Point(s): shows where the extension takes in user/external input. This is useful for understanding an extension’s surface area and looking for potential points to send maliciously-crafted data to the extension.
- Both the Dangerous Function(s) and Entry Point(s) scanners have the following for their generated alerts:
- Relevant code snippet and line that caused the alert.
- Description of the issue.
- A “View File” button to view the full source file containing the code.
- The path of the alerted file.
- The full Chrome extension URI of the alerted file.
- The type of file it is, such as a Background Page script, Content Script, Browser Action, etc.
- If the vulnerable line is in a JavaScript file, the paths of all of the pages where it is included as well as these page’s type, and web_accessible_resource status.
- Content Security Policy (CSP) analyzer and bypass checker: This will point out weaknesses in your extension’s CSP and will also illuminate any potential ways to bypass your CSP due to whitelisted CDNs, etc.
- Known Vulnerable Libraries: This uses Retire.js to check for any usage of known-vulnerable JavaScript libraries.
- Download extension and formatted versions.
- Download the original extension.
- Download a beautified version of the extension (auto prettified HTML and JavaScript).
- Automatic caching of scan results, running an extension scan will take a good amount of time the first time you run it. However the second time, assuming the extension hasn’t been updated, will be almost instant due to the results being cached.
- Linkable Report URLs, easily link someone else to an extension report generated by tarnish.
Neto
Project Neto is a Python 3 package conceived to analyse and unravel hidden features of browser plugins and extensions for well-known browsers such as Firefox and Chrome. It automates the process of unzipping the packaged files to extract these features from relevant resources in a extension like manifest.json
, localization folders or Javascript and HTML source files.
References
- Thanks to @naivenom for the help with this methodology
- https://www.cobalt.io/blog/introduction-to-chrome-browser-extension-security-testing
- https://palant.info/2022/08/10/anatomy-of-a-basic-extension/
- https://palant.info/2022/08/24/attack-surface-of-extension-pages/
- https://palant.info/2022/08/31/when-extension-pages-are-web-accessible/
- https://help.passbolt.com/assets/files/PBL-02-report.pdf
- https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts
- https://developer.chrome.com/docs/extensions/mv2/background-pages
- https://thehackerblog.com/kicking-the-rims-a-guide-for-securely-writing-and-auditing-chrome-extensions/
- https://gist.github.com/LongJohnCoder/9ddf5735df3a4f2e9559665fb864eac0
{{#include ../../banners/hacktricks-training.md}}