Browser Extension Pentesting Methodology
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Informations de base
Les browser extensions sont écrites en JavaScript et chargées par le browser en arrière-plan. Elles ont leur DOM mais peuvent interagir avec les DOM d’autres sites. Cela signifie qu’elles peuvent compromettre la confidentialité, l’intégrité et la disponibilité (CIA) d’autres sites.
Composants principaux
Les layouts des extensions sont mieux compris lorsqu’ils sont visualisés et se composent de trois composants. Examinons chaque composant en détail.
 (1) (1).png)
Content Scripts
Chaque content script a un accès direct au DOM d’une seule page web et est donc exposé à des entrées potentiellement malveillantes. Cependant, le content script ne contient aucun privilège autre que la capacité d’envoyer des messages au core de l’extension.
Extension Core
Le core de l’extension contient la plupart des privilèges/accès de l’extension, mais le core de l’extension ne peut interagir avec le contenu web que via XMLHttpRequest et les content scripts. De plus, le core de l’extension n’a pas d’accès direct à la machine hôte.
Native Binary
L’extension permet un native binary qui peut accéder à la machine hôte avec tous les privilèges de l’utilisateur. Le native binary interagit avec le core de l’extension via le standard Netscape Plugin Application Programming Interface (NPAPI) utilisé par Flash et d’autres browser plug-ins.
Frontières
Caution
Pour obtenir tous les privilèges de l’utilisateur, un attaquant doit convaincre l’extension de transmettre une entrée malveillante du content script au core de l’extension, puis du core de l’extension au native binary.
Chaque composant de l’extension est séparé des autres par des frontières de protection fortes. 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 inaccessibles à 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 JavaScript heap distinct. 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, ce qui empêche le leak de fonctionnalités JavaScript.
manifest.json
Une extension Chrome n’est qu’un dossier ZIP avec une extension de fichier .crx file extension. Le core de l’extension est le fichier manifest.json à la racine du dossier, qui spécifie le layout, 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 à la regex *://*/*/business*. Ils s’exécutent comme les scripts de la page elle-même et ont un accès arbitraire au Document Object Model (DOM) de la page.
"content_scripts": [
{
"js": [
"script.js"
],
"matches": [
"https://example.com/*",
"https://www.example.com/*"
],
"exclude_matches": ["*://*/*business*"],
}
],
Afin d’inclure ou d’exclure davantage d’URLs, il est également possible d’utiliser include_globs et exclude_globs.
Voici un exemple de content script qui ajoutera un bouton explain à la page lorsqu’l’API storage pour récupérer la valeur message depuis le storage 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)
})
.png)
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. Cela est dû à la limitation du content script concernant l’accès direct aux APIs, storage étant l’une des rares exceptions. Pour les fonctionnalités au-delà de ces exceptions, des messages sont envoyés aux pages de l’extension avec lesquelles les content scripts peuvent communiquer.
Warning
Selon le browser, 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, la MDN sert de source principale.
Il est aussi à noter que les content scripts ont la capacité de communiquer avec les background scripts, leur permettant d’effectuer des actions et de relayer les réponses en retour.
Pour afficher et déboguer les content scripts dans Chrome, le menu des outils de développement Chrome peut être ouvert depuis Options > More tools > Developer tools OU en appuyant sur Ctrl + Shift + I.
Une fois les developer tools affichés, il faut cliquer sur l’onglet Source, 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 définir des breakpoints pour suivre le flux d’exécution.
Injected content scripts
Tip
Notez que les Content Scripts ne sont pas obligatoires puisqu’il est aussi possible d’injecter dynamiquement des scripts et de les injecter programatically dans des pages web via
tabs.executeScript. Cela offre en réalité des contrôles plus granular.
Pour l’injection programmatique d’un content script, l’extension doit disposer des host permissions pour la page dans laquelle les scripts doivent être injectés. Ces permissions peuvent être obtenues soit en les requesting dans le manifest de l’extension, soit de manière temporaire via activeTab.
Example activeTab-based extension
{
"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 des permissions de scripting
// 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" })
Afin d’inclure ou d’exclure davantage d’URLs, il est également possible d’utiliser include_globs et exclude_globs.
Content Scripts 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".
Les valeurs possibles sont :
document_idle: Dès que possibledocument_start: Après tous les fichiers decss, mais avant que tout autre DOM ne soit construit ou que tout autre script ne soit exécuté.document_end: Immédiatement après que le DOM est complet, mais avant que des sous-ressources comme les images et les frames aient été 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 pendant toute la durée de vie de l’extension, en fonctionnant discrètement sans interaction directe avec l’utilisateur. Elle possède son propre Document Object Model (DOM), ce qui permet des interactions complexes et la gestion de l’état.
Key Points:
- Background Page Role: Agit comme le centre nerveux de l’extension, en 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 explicitement définie, le browser créera automatiquement une background page. Cette page générée automatiquement inclura tous les background scripts spécifiés dans le manifest de l’extension, garantissant le bon fonctionnement des tâches de background de l’extension.
Tip
La commodité offerte par le browser en générant automatiquement une background page (lorsqu’elle n’est pas explicitement déclarée) garantit que tous les background scripts nécessaires sont intégrés et opérationnels, simplifiant ainsi le processus de configuration de l’extension.
Example 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 background, vous pouvez aller dans les détails de l’extension et inspecter le service worker, cela ouvrira les developer tools avec le script background :
Options pages and other
Les browser extensions peuvent contenir différents types de pages :
- Les Action pages sont affichées dans un menu déroulant lorsque l’icône de l’extension est cliquée.
- Les pages que l’extension va charger dans un nouvel onglet.
- 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=fadlhnelkbeojnebcbkacjilhnbjfjcaou en cliquant :
.png)
Notez que ces pages ne sont pas persistantes comme les background pages, car elles chargent dynamiquement du contenu à la demande. Malgré cela, elles partagent certaines capacités avec la background page :
- Communication with Content Scripts : comme la background page, ces pages peuvent recevoir des messages depuis les content scripts, facilitant l’interaction au sein de l’extension.
- Access to Extension-Specific APIs : ces pages disposent d’un accès complet aux extension-specific APIs, 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 indiquent quelles permissions a l’extension de browser (storage, location…) et sur quelles web pages.
Comme les browser extensions peuvent être très privilégiées, une extension malveillante ou compromise pourrait permettre à l’attaquant différents moyens de voler des informations sensibles et d’espionner l’utilisateur.
Vérifiez comment ces paramètres fonctionnent et comment ils peuvent être abusés dans :
BrowExt - permissions & host_permissions
content_security_policy
Une content security policy peut aussi être déclarée dans le manifest.json. Si elle est définie, elle pourrait être vulnérable.
Le paramètre par défaut pour les pages d’extension de browser est plutôt restrictif :
script-src 'self'; object-src 'self';
Pour plus d’informations sur CSP et les bypass potentiels, consultez :
Content Security Policy (CSP) Bypass
web_accessible_resources
pour qu’une page web puisse accéder à une page d’une Browser Extension, par exemple une page .html, cette page doit être mentionnée dans le champ web_accessible_resources du 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 :
.png)
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 aussi la vérifier (section frame-ancestors) avant de confirmer qu’une attaque de ClickJacking est possible.
Le fait d’être autorisé à accéder à ces pages rend ces pages potentiellement vulnérables au ClickJacking :
Tip
Autoriser ces pages à être chargées uniquement par l’extension et non par des URLs aléatoires pourrait empêcher les attaques de ClickJacking.
Caution
Notez que les pages de
web_accessible_resourceset les autres pages de l’extension sont également capables de contacter background scripts. Donc, si l’une de ces pages est vulnérable à une XSS, cela pourrait ouvrir une vulnérabilité plus importante.De plus, notez que vous ne pouvez ouvrir les pages indiquées dans
web_accessible_resourcesque dans des iframes, 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 une XSS est trouvée en abusant des mêmes paramètres, elle pourrait être exploitée même si la page n’est pas configurée dansweb_accessible_resources.
externally_connectable
Selon la docs, la propriété de manifest "externally_connectable" déclare quelles extensions et pages web peuvent se connecter à votre extension via runtime.connect et runtime.sendMessage.
- Si la clé
externally_connectablen’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és, comme dans
"ids": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], seules ces applications peuvent se connecter. - Si des matches sont indiqués, ces web apps pourront se connecter :
"matches": [
"https://*.google.com/*",
"*://*.chromium.org/*",
- If it’s specified as empty:
"externally_connectable": {}, no app or web will be able to connect.
The less extensions and URLs indicated here, the smaller the attack surface will be.
Caution
If a web page vulnerable to XSS or takeover is indicated in
externally_connectable, an attacker will be able to send messages directly to the background script, completely bypassing the Content Script and its CSP.Therefore, this is a very powerful bypass.
Moreover, if the client installs a rouge extension, even if it isn’t allowed to communicate with the vulnerable extension, it could inject XSS data in an allowed web page or abuse
WebRequestorDeclarativeNetRequestAPIs to manipulate requests on a targeted domain altering a page’s request for a JavaScript file. (Note that CSP on the targeted page could prevent these attacks). This idea comes from this writeup.
Wildcard-trusted web origins to privileged action injection
If an extension exposes a high-privilege message handler to the web via externally_connectable, avoid trusting a broad pattern such as https://*.example.com/*. A single XSS, subdomain takeover, or vendor widget compromise on any matching subdomain becomes equivalent to owning the extension’s web-facing API.
Typical exploitation path:
- Find the extension ID and enumerate
externally_connectable.matches. - Identify a message type that triggers a privileged action (
open tab,read page,submit prompt,fetch with extension privileges,native messaging, and so on). - Get JavaScript execution in any trusted origin.
- Send the request directly to the extension:
chrome.runtime.sendMessage("<extension-id>", {
type: "privileged_action",
payload: { attacker: "controlled" },
})
Ceci est particulièrement dangereux dans les agentic extensions ou assistants, car le “message” peut être un instruction prompt complet qui est exécuté avec les permissions d’hôte de l’extension.
Caution
Traitez le code vendor hébergé sur un sous-domaine de confiance first-party comme faisant partie de la frontière de confiance. Si
captcha.example.com,cdn.example.com, ou l’origine d’un support widget est autorisé parexternally_connectable, un XSS dans ce composant tiers peut pivoter vers l’extension exactement comme si le bug existait sur l’application principale.
Auditing chrome.runtime.sendMessage / onMessageExternal trust
Lors de la revue d’une browser extension qui accepte des messages depuis des pages web :
- Recherchez
chrome.runtime.onMessageExternal.addListener,chrome.runtime.onConnectExternal.addListener, et les handlers sensibles accessibles depuis ces listeners. - Vérifiez que l’extension effectue des vérifications d’égalité exacte de l’origine sur l’expéditeur, et non un matching par suffixe, regex, ou wildcard.
- Vérifiez que le handler autorise type de message + origine ensemble. Une origine sûre pour la télémétrie ou l’onboarding n’est pas automatiquement sûre pour des actions privilégiées.
- Confirmez que l’extension ne fait pas confiance à un sous-domaine simplement parce qu’il partage le parent eTLD+1.
- Vérifiez si une origine autorisée héberge du JavaScript tiers, du contenu généré par les utilisateurs, ou des legacy static assets.
Bad patterns:
if (sender.origin.endsWith(".example.com")) { /* trust */ }
if (/^https:\/\/.*\.example\.com$/.test(sender.origin)) { /* trust */ }
Préférez une correspondance stricte avec le plus petit ensemble d’origins :
if (sender.origin !== "https://app.example.com") return
Rollback hunting on trusted static assets
Si l’origine de confiance sert des versioned static assets ou des widgets intégrés depuis des chemins prévisibles, essayez d’explorer les versions plus anciennes à la recherche de builds vulnérables encore accessibles. C’est utile lorsque la version actuelle est patchée mais que l’extension fait toujours confiance à l’origine qui héberge des assets archivés.
Schémas courants :
/assets/widget/1.26.0/index.html/static/app-2024.12.1//cdn/component/v1234/
Vérifications pratiques :
- Commencez à partir d’une version observée dans le trafic réseau ou le code source de la page.
- Décrémentez les versions sémantiques / numéros de build et demandez les chemins plus anciens.
- Recherchez des
200,301, ou des cache hits sur les anciens builds. - Examinez les anciens bundles JS pour du DOM XSS, des gestionnaires de messages non sûrs, ou des gadget endpoints qui peuvent rétablir l’exécution JavaScript sur l’origine de confiance.
Communication summary
Extension <–> WebApp
Pour communiquer entre le content script et la page web, des messages post sont généralement utilisés. Ainsi, dans la web application, 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 la web application en envoyant un Post Message (et donc la web doit s’y attendre) ou simplement faire charger à la web un nouveau script.
Inside the extension
En général, la fonction chrome.runtime.sendMessage est utilisée pour envoyer un message à l’intérieur de l’extension (généralement géré par le script background) et, pour le recevoir et le traiter, 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 uniques ; on peut l’utiliser pour envoyer et recevoir des messages comme dans l’exemple suivant :
chrome.runtime.connect() example
```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 background script vers un content script situé dans un onglet spécifique en appelant **`chrome.tabs.sendMessage`** où vous devrez indiquer l'**ID de l'onglet** auquel envoyer le message.
### Depuis un `externally_connectable` autorisé vers l'extension
**Les web apps et les browser extensions externes autorisés** dans la configuration `externally_connectable` peuvent envoyer des requêtes en utilisant :
```javascript
chrome.runtime.sendMessage(extensionId, ...
Là où il est nécessaire de mentionner l’extension ID.
Native Messaging
Il est possible pour les background scripts de communiquer avec des binaires à l’intérieur du système, ce qui pourrait être sujet à des vulnérabilités critiques telles que des RCEs si cette communication n’est pas correctement sécurisée. Plus d’informations plus tard.
chrome.runtime.sendNativeMessage(
"com.my_company.my_application",
{ text: "Hello" },
function (response) {
console.log("Received " + response)
}
)
Communication ↔︎ Web Script de contenu
Les environnements dans lesquels les content scripts opèrent et où les pages hôtes existent sont séparés les uns des autres, garantissant l’isolation. Malgré cette isolation, les deux ont la capacité d’interagir avec le Document Object Model (DOM) de la page, une ressource partagée. Pour que la page hôte puisse communiquer avec le content script, ou indirectement avec l’extension via le content script, il est nécessaire d’utiliser le DOM accessible aux deux parties 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 se faire en vérifiant :
event.isTrusted: Cela est True uniquement si l’événement a été déclenché par une action de l’utilisateur- Le content script peut s’attendre à un message uniquement si l’utilisateur effectue une certaine action
- origin domain : peut s’attendre à ce qu’un message ne soit autorisé que depuis une allowlist de domaines.
- Si une regex est utilisée, soyez très prudent
- Source :
received_message.source !== windowpeut être utilisé pour vérifier si le message provenait de la même fenêtre où le Content Script écoute.
Les vérifications précédentes, même si elles sont effectuées, pourraient être vulnérables, alors consultez la page suivante pour les potential Post Message bypasses :
Iframe
Une autre façon possible de communication pourrait passer par des Iframe URLs, vous pouvez trouver un exemple dans :
DOM
Ce n’est pas une façon de communication exactement, mais le web et le content script auront accès au web DOM. Donc, si le content script lit certaines 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 pour compromettre une browser extension dans :
Content Script ↔︎ Background Script Communication
Un Content Script peut utiliser les fonctions runtime.sendMessage() ou tabs.sendMessage() pour envoyer un message one-time JSON-serializable.
Pour gérer la response, utilisez le Promise retourné. Cependant, pour la compatibilité ascendante, vous pouvez toujours passer un callback comme dernier argument.
L’envoi d’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 d’envoi d’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é récepteur, vous devez configurer un écouteur d’événements runtime.onMessage pour traiter le message. Cela se présente de la même manière depuis un content script ou une page d’extension.
// 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 d’exécuter sendResponse() de façon asynchrone, il est impératif d’ajouter return true;.
Un point important à considérer est que, dans les scénarios où plusieurs pages sont configurées pour recevoir des événements onMessage, la première page à exécuter sendResponse() pour un événement spécifique sera la seule à pouvoir fournir la réponse efficacement. Toute réponse ultérieure au même événement ne sera pas prise en compte.
Lors de la création de nouvelles extensions, il faut privilégier les promises plutôt que les callbacks. Concernant l’utilisation des 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 renvoyant true. Si aucun des gestionnaires ne renvoie true ou si la fonction sendResponse() est supprimée de la mémoire (garbage-collected), le callback associé à la fonction sendMessage() sera déclenché par défaut.
Native Messaging
Les browser extensions permettent aussi de communiquer avec des binaires dans le système via stdin. L’application doit installer un json indiquant cela 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ù name est la chaîne passée à runtime.connectNative() ou runtime.sendNativeMessage() pour communiquer avec l’application depuis les background scripts de l’extension browser. Le path est le chemin vers le binaire, il n’existe qu’un seul type valide qui est stdio (use stdin and stdout) et allowed_origins indique les extensions qui peuvent y accéder (et ne peut pas avoir de wildcard).
Chrome/Chromium recherchera ce json dans certaines windows registry et dans certains paths sur macOS et Linux (plus d’informations dans les docs).
Tip
L’extension browser a aussi besoin de la permission
nativeMessaingdéclarée afin de pouvoir utiliser cette communication.
Voici à quoi ressemble du code de background script envoyant des messages à une native application :
chrome.runtime.sendNativeMessage(
"com.my_company.my_application",
{ text: "Hello" },
function (response) {
console.log("Received " + response)
}
)
Dans ce billet de blog, un pattern vulnérable abusant des native messages est proposé :
- L’extension browser a un wildcard pattern pour le content script.
- Le content script passe des messages
postMessageau script de fond en utilisantsendMessage. - Le script de fond passe le message à l’application native en utilisant
sendNativeMessage. - L’application native gère le message de manière dangereuse, menant à l’exécution de code.
Et à l’intérieur, un exemple de passer de n’importe quelle page à RCE en abusant d’une browser extension est expliqué.
Sensitive Information in Memory/Code/Clipboard
Si une Browser Extension stocke des informations sensibles dans sa mémoire, celles-ci pourraient être dumped (surtout sur des machines Windows) et searched pour ces informations.
Par conséquent, la mémoire de la Browser Extension ne devrait pas être considérée comme sûre et les informations sensibles telles que des credentials ou des mnemonic phrases ne devraient pas être stockées.
Bien sûr, n’insérez pas d’informations sensibles dans le code, car elles seront publiques.
Pour dumper la mémoire du navigateur, vous pouvez dump le process memory ou aller dans les settings de la browser extension, cliquer sur Inspect pop-up -> dans la section Memory -> Take a snaphost et CTRL+F pour rechercher des informations sensibles dans le snapshot.
De plus, les informations hautement sensibles comme les mnemonic keys ou les passwords ne devraient pas pouvoir être copiées dans le clipboard (ou au moins être retirées du clipboard après quelques secondes) car des processus surveillant le clipboard pourront alors les récupérer.
Loading an Extension in the Browser
- Download la Browser Extension & unzipped
- Allez dans
chrome://extensions/et activez leDeveloper Mode - Cliquez sur le bouton
Load unpacked
Dans Firefox, allez dans about:debugging#/runtime/this-firefox et cliquez sur le bouton Load Temporary Add-on.
Getting the source code from the store
Le source code d’une extension Chrome peut être obtenu via différentes méthodes. Ci-dessous se trouvent des explications détaillées et des instructions pour chaque option.
Download Extension as ZIP via Command Line
Le source code d’une extension Chrome peut être téléchargé sous forme de fichier ZIP en utilisant la command line. Cela implique d’utiliser curl pour récupérer le fichier ZIP depuis une URL spécifique, puis d’extraire le contenu du fichier ZIP vers 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 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 dépôt GitHub.
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 local du profil Chrome en visitant
chrome://version/et en localisant le champ “Profile Path”. - Naviguez vers le sous-dossier
Extensions/dans le répertoire du 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 ID à leurs noms :
- Activez le Developer Mode sur la page
about:extensionspour voir les ID de chaque extension. - Dans le dossier de chaque extension, le fichier
manifest.jsoncontient un champnamelisible, ce qui vous aide à identifier l’extension.
Utiliser un archiveur de fichiers ou un décompresseur
Allez sur le Chrome Web Store et téléchargez l’extension. Le fichier aura une extension .crx. Changez l’extension du fichier de .crx à .zip. Utilisez n’importe quel archiveur de fichiers (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 vers le répertoire de votre extension. Cela ne télécharge pas le code source, mais c’est utile pour visualiser et modifier le code d’une extension déjà téléchargée ou développée.
Chrome extension manifest dataset
Afin d’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 à la recherche de signes potentiellement vulnérables. Par exemple, pour vérifier les extensions avec plus de 25000 users, 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)
Technique furtive pour backdoor Chromium en modifiant directement les Preferences par utilisateur et en forgeant des HMAC valides, ce qui amène le navigateur à accepter et activer une extension unpacked arbitraire sans prompt ni flags.
Forced Extension Load Preferences Mac Forgery Windows
Détection des mises à jour malveillantes d’extensions (Static Version Diffing)
Les compromissions de supply-chain arrivent souvent sous forme de mises à jour malveillantes d’extensions jusque-là bénignes. Une approche pratique et discrète consiste à comparer un nouveau package d’extension avec la dernière version connue comme saine à l’aide d’une analyse statique (par exemple, Assemblyline). L’objectif est d’alerter sur des deltas à fort signal plutôt que sur n’importe quel changement.
Workflow
- Soumettre les deux versions (ancienne + nouvelle) au même profil d’analyse statique.
- Signaler les nouveaux scripts background/service worker ou mis à jour (persistence + logique privilégiée).
- Signaler les nouveaux content scripts ou mis à jour (accès au DOM et collecte de données).
- Signaler les nouvelles permissions/host_permissions ajoutées dans
manifest.json. - Signaler les nouveaux domaines extraits du code (potentiels points de C2/exfil).
- Signaler les nouvelles détections d’analyse statique (par exemple, décodage base64, collecte de cookies, générateurs de requêtes réseau, patterns d’obfuscation).
- Signaler les anomalies statistiques comme de fortes hausses d’entropie ou des z-scores aberrants dans les scripts modifiés.
Détecter précisément les changements de script
- Nouveau script ajouté → détecté via le diff de
manifest.json. - Script existant modifié (manifest inchangé) → comparer les hashs par fichier depuis l’arborescence extraite des fichiers (par exemple, la sortie
Extractd’Assemblyline). Cela permet de détecter les mises à jour furtives de workers ou de content scripts existants.
Détections avant divulgation
Pour éviter des détections en “easy mode” basées sur des IOCs déjà connus, désactivez les services alimentés par threat-intel et fiez-vous aux signaux intrinsèques (domains, signatures heuristiques, deltas de scripts, anomalies d’entropie). Cela augmente les chances de détecter des mises à jour malveillantes avant les signalements publics.
Exemple de logique d’alerte à forte confiance
- Combo faible bruit: nouveaux domains + nouvelles détections d’analyse statique + background/service worker mis à jour + content scripts mis à jour ou ajoutés.
- Détection plus large: nouveau domain + background/service worker nouveau ou mis à jour (rappel plus élevé, bruit plus élevé).
Services Assemblyline clés pour ce workflow :
- Extract: décompresse l’extension et fournit des hashs par fichier.
- Characterize: calcule les caractéristiques des fichiers (par exemple, l’entropie).
- JsJAWS / FrankenStrings / URLCreator: font ressortir les heuristiques JS, les strings et les domains à différencier entre versions.
Security Audit Checklist
Même si les Browser Extensions ont une surface d’attaque limitée, certaines peuvent contenir des vulnerabilities ou des améliorations de hardening potentielles. Les suivantes sont les plus courantes :
- Limiter autant que possible les
permissionsdemandées - Limiter autant que possible les
host_permissions - Utiliser une forte
content_security_policy - Limiter autant que possible les
externally_connectable; si rien n’est nécessaire et possible, ne pas le laisser par défaut, spécifier{} - Si une URL vulnérable à XSS ou à takeover est mentionnée ici, un attaquant pourra envoyer des messages directement aux scripts background. Bypass très puissant.
- Refuser les wildcard ou les suffix trust comme
*.example.compour les gestionnaires de messages externes privileged. - Vérifier si une origin autorisée sert des vendor-hosted widgets, du user content, ou des legacy assets versionnés qui pourraient restaurer l’exécution de code sur un sous-domaine de confiance.
- Limiter autant que possible les
web_accessible_resources, même vide si possible. - Si
web_accessible_resourcesn’est pas none, vérifier ClickJacking - Si une communication a lieu de l’extension vers la web page, vérifier les vulnérabilités XSS causées dans la communication
- Si des Post Messages sont utilisés, vérifier les Post Message vulnerabilities.
- Si le Content Script accède aux détails du DOM, vérifier qu’il n’introduit pas de XSS s’il est modifié par le web
- Mettre un accent particulier si cette communication intervient aussi dans la communication Content Script -> Background script
- Si le background script communique via native messaging, vérifier que la communication est sûre et assainie
- Les informations sensibles ne devraient pas être stockées dans le code de la Browser Extension
- Les informations sensibles ne devraient pas être stockées dans la mémoire de la Browser Extension
- Les informations sensibles ne devraient pas être stockées dans le file system sans protection
Browser Extension Risks
L’application https://crxaminer.tech/ analyse certaines données comme les permissions demandées par une browser extension pour attribuer un niveau de risque à son utilisation.
Tools
Tarnish
- Récupère n’importe quelle extension Chrome à partir d’un lien Chrome webstore fourni.
- Visionneur manifest.json : affiche simplement une version JSON prettified du manifest de l’extension.
- Fingerprint Analysis : détection de web_accessible_resources et génération automatique de JavaScript de fingerprinting pour extension Chrome.
- Potential Clickjacking Analysis : détection des pages HTML de l’extension avec la directive web_accessible_resources définie. Elles sont potentiellement vulnérables au clickjacking selon l’objectif des pages.
- Visionneur des Permission Warning(s) : affiche la liste de tous les avertissements du prompt de permission Chrome qui seront présentés lorsqu’un utilisateur tentera d’installer l’extension.
- Dangerous Function(s) : affiche l’emplacement des fonctions dangereuses qui pourraient potentiellement être exploitées par un attaquant (par exemple, des fonctions comme innerHTML, chrome.tabs.executeScript).
- Entry Point(s) : montre où l’extension prend des entrées utilisateur/externes. C’est utile pour comprendre la surface de l’extension et repérer des points potentiels pour envoyer des données malformées à l’extension.
- Les analyseurs Dangerous Function(s) et Entry Point(s) ont les éléments suivants pour leurs alertes générées :
- Extrait de code pertinent et ligne ayant déclenché l’alerte.
- Description du problème.
- Bouton “View File” pour afficher le fichier source complet contenant le code.
- Chemin du fichier alerté.
- URI Chrome extension complète du fichier alerté.
- Type de fichier, comme Background Page script, Content Script, Browser Action, etc.
- Si la ligne vulnérable est dans un fichier JavaScript, les chemins de toutes les pages où il est inclus ainsi que le type de ces pages et le statut web_accessible_resource.
- Content Security Policy (CSP) analyzer and bypass checker : met en évidence les faiblesses dans le CSP de votre extension et toute manière potentielle de le bypass à cause de CDN whitelisted, etc.
- Known Vulnerable Libraries : utilise Retire.js pour vérifier l’usage de bibliothèques JavaScript connues comme vulnérables.
- Télécharger l’extension et les versions formatées.
- Télécharger l’extension originale.
- Télécharger une version beautified de l’extension (HTML et JavaScript automatiquement prettified).
- Cache automatique des résultats d’analyse : lancer un scan d’extension prendra pas mal de temps la première fois. En revanche, la deuxième fois, en supposant que l’extension n’a pas été mise à jour, ce sera presque instantané grâce au cache des résultats.
- URLs de rapports partageables, pour facilement envoyer quelqu’un vers un rapport d’extension généré par tarnish.
Neto
Project Neto est un package Python 3 conçu pour analyser et dévoiler les fonctionnalités cachées des plugins et extensions de browser pour des browsers connus comme Firefox et Chrome. Il automatise le processus de décompression des fichiers packagés afin d’extraire ces fonctionnalités depuis les ressources pertinentes d’une extension comme manifest.json, les dossiers de localisation ou les fichiers source Javascript et HTML.
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/reference/manifest/externally-connectable
- 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
- https://redcanary.com/blog/threat-detection/assemblyline-browser-extensions/
- https://www.koi.ai/blog/shadowprompt-how-any-website-could-have-hijacked-anthropic-claude-chrome-extension
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.


