Browser Extension Pentesting Methodology

Reading time: 30 minutes

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

Información básica

Las extensiones del navegador están escritas en JavaScript y se cargan en segundo plano por el navegador. Tienen su DOM pero pueden interactuar con los DOMs de otros sitios. Esto significa que pueden comprometer la confidencialidad, integridad y disponibilidad (CIA) de otros sitios.

Componentes principales

Los diseños de las extensiones se comprenden mejor visualmente y constan de tres componentes. Veamos cada componente en profundidad.

http://webblaze.cs.berkeley.edu/papers/Extensions.pdf

Scripts de contenido

Cada script de contenido tiene acceso directo al DOM de una única página web y, por tanto, queda expuesto a entradas potencialmente maliciosas. Sin embargo, el script de contenido no tiene permisos aparte de la capacidad de enviar mensajes al núcleo de la extensión.

Núcleo de la extensión

El núcleo de la extensión contiene la mayor parte de los privilegios/acceso de la extensión, pero solo puede interactuar con contenido web mediante XMLHttpRequest y scripts de contenido. Además, el núcleo de la extensión no tiene acceso directo a la máquina anfitriona.

Binario nativo

La extensión puede incluir un binario nativo que puede acceder a la máquina anfitriona con los privilegios completos del usuario. El binario nativo interactúa con el núcleo de la extensión a través de la Netscape Plugin Application Programming Interface (NPAPI) usada por Flash y otros plug-ins del navegador.

Límites

caution

Para obtener los privilegios completos del usuario, un atacante debe convencer a la extensión de pasar una entrada maliciosa desde el script de contenido al núcleo de la extensión y desde el núcleo de la extensión al binario nativo.

Cada componente de la extensión está separado de los demás por fuertes límites de protección. Cada componente se ejecuta en un proceso del sistema operativo separado. Los scripts de contenido y los núcleos de extensión se ejecutan en procesos sandbox no disponibles para la mayoría de los servicios del sistema operativo.

Además, los scripts de contenido están separados de sus páginas web asociadas al ejecutarse en un heap de JavaScript separado. El script de contenido y la página web tienen acceso al mismo DOM subyacente, pero ambos nunca intercambian punteros de JavaScript, impidiendo el leaking de la funcionalidad de JavaScript.

manifest.json

Una extensión de Chrome es simplemente una carpeta ZIP con una .crx file extension. El núcleo de la extensión es el archivo manifest.json en la raíz de la carpeta, que especifica la estructura, permisos y otras opciones de configuración.

Ejemplo:

json
{
"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

Los content scripts se cargan cada vez que el usuario navega a una página que coincide, en nuestro caso cualquier página que coincida con la expresión https://example.com/* y que no coincida con la regex *://*/*/business*. Se ejecutan como los propios scripts de la página y tienen acceso arbitrario al Document Object Model (DOM) de la página.

json
"content_scripts": [
{
"js": [
"script.js"
],
"matches": [
"https://example.com/*",
"https://www.example.com/*"
],
"exclude_matches": ["*://*/*business*"],
}
],

Para incluir o excluir más URLs también es posible usar include_globs y exclude_globs.

Este es un ejemplo de content script que añadirá un botón 'explain' a la página cuando the storage API se use para recuperar el valor message del almacenamiento de la extensión.

js
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 mensaje es enviado a las páginas de la extensión por el content script cuando se pulsa este botón, mediante la utilización de la runtime.sendMessage() API. Esto se debe a la limitación del content script para acceder directamente a las APIs, siendo storage una de las pocas excepciones. Para funcionalidades más allá de estas excepciones, se envían mensajes a las páginas de la extensión con las que los content scripts pueden comunicarse.

warning

Dependiendo del navegador, las capacidades del content script pueden variar ligeramente. Para los navegadores basados en Chromium, la lista de capacidades está disponible en la documentación de Chrome Developers, y para Firefox, la MDN sirve como la fuente principal.
También es importante señalar que los content scripts tienen la capacidad de comunicarse con background scripts, lo que les permite ejecutar acciones y devolver respuestas.

Para ver y depurar los content scripts en Chrome, el menú de herramientas de desarrollador de Chrome puede abrirse desde Opciones > Más herramientas > Herramientas del desarrollador O pulsando Ctrl + Shift + I.

Al mostrarse las herramientas del desarrollador, hay que clicar la pestaña Source, seguida de la pestaña Content Scripts. Esto permite observar los content scripts en ejecución de varias extensiones y establecer puntos de interrupción para seguir el flujo de ejecución.

Scripts de contenido inyectados

tip

Tenga en cuenta que los Content Scripts no son obligatorios, ya que también es posible inyectar scripts de forma dinámica y programáticamente en páginas web mediante tabs.executeScript. Esto proporciona controles más granulares.

Para la inyección programática de un content script, la extensión debe tener host permissions para la página en la que se van a inyectar los scripts. Estos permisos pueden obtenerse solicitándolos en el manifest de la extensión o de forma temporal mediante activeTab.

Ejemplo de extensión basada en activeTab

manifest.json
{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}
  • Inyectar un archivo JS al hacer clic:
javascript
// 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"],
})
})
  • Inyectar una función al hacer clic:
javascript
//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,
})
})

Ejemplo con permisos de scripting

javascript
// 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" })

Para incluir o excluir más URLs también es posible usar include_globs y exclude_globs.

Scripts de contenido run_at

El campo run_at controla cuándo se inyectan archivos JavaScript en la página web. El valor preferido y por defecto es "document_idle".

Los valores posibles son:

  • document_idle: Siempre que sea posible
  • document_start: Después de cualquier archivo de css, pero antes de que se construya cualquier otro elemento del DOM o se ejecute otro script.
  • document_end: Inmediatamente después de que el DOM esté completo, pero antes de que subrecursos como imágenes y frames se hayan cargado.

Vía manifest.json

json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.example.com/*"],
"run_at": "document_idle",
"js": ["contentScript.js"]
}
],
...
}

Mediante service-worker.js

javascript
chrome.scripting.registerContentScripts([
{
id: "test",
matches: ["https://*.example.com/*"],
runAt: "document_idle",
js: ["contentScript.js"],
},
])

background

Los mensajes enviados por content scripts son recibidos por la página en segundo plano, que desempeña un papel central en la coordinación de los componentes de la extensión. Cabe destacar que la página en segundo plano persiste durante la vida útil de la extensión, funcionando de forma discreta sin interacción directa del usuario. Posee su propio Modelo de Objetos del Documento (DOM), lo que permite interacciones complejas y gestión del estado.

Puntos clave:

  • Rol de la página en segundo plano: Actúa como el centro neurálgico de la extensión, garantizando la comunicación y coordinación entre las distintas partes de la extensión.
  • Persistencia: Es una entidad siempre presente, invisible para el usuario pero integral para la funcionalidad de la extensión.
  • Generación automática: Si no se define explícitamente, el navegador creará automáticamente una página en segundo plano. Esta página auto-generada incluirá todos los scripts en segundo plano especificados en el manifest de la extensión, asegurando el funcionamiento fluido de las tareas en segundo plano de la extensión.

tip

La comodidad que ofrece el navegador al generar automáticamente una página en segundo plano (cuando no se declara explícitamente) garantiza que todos los scripts en segundo plano necesarios estén integrados y operativos, simplificando el proceso de configuración de la extensión.

Ejemplo de script en segundo plano:

js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request == "explain") {
chrome.tabs.create({ url: "https://example.net/explanation" })
}
})

It uses runtime.onMessage API to listen to messages. When an "explain" message is received, it uses tabs API to open a page in a new tab.

Para depurar el background script puedes ir a los extension details and inspect the service worker, esto abrirá las developer tools con el background script:

Páginas de opciones y otras

Las extensiones del navegador pueden contener varios tipos de páginas:

  • Action pages are displayed in a drop-down when the extension icon is clicked.
  • Pages that the extension will load in a new tab.
  • Option Pages: Esta página se muestra encima de la extensión cuando se hace clic. En el manifest anterior, en mi caso pude acceder a esta página en chrome://extensions/?options=fadlhnelkbeojnebcbkacjilhnbjfjca o haciendo clic:

Ten en cuenta que estas páginas no son persistentes como los background pages ya que cargan contenido dinámicamente según la necesidad. A pesar de ello, comparten ciertas capacidades con el background page:

  • Communication with Content Scripts: Similar to the background page, these pages can receive messages from content scripts, facilitating interaction within the extension.
  • Access to Extension-Specific APIs: These pages enjoy comprehensive access to extension-specific APIs, subject to the permissions defined for the extension.

permissions & host_permissions

permissions and host_permissions are entries from the manifest.json that will indicate which permissions the browser extensions has (storage, location...) and in which web pages.

Como las extensiones del navegador pueden ser tan privileged, una maliciosa o una que haya sido comprometida podría permitir al atacante different means to steal sensitive information and spy on the user.

Consulta cómo funcionan estos ajustes y cómo podrían ser abusados en:

BrowExt - permissions & host_permissions

content_security_policy

Una content security policy también puede declararse dentro del manifest.json. Si hay una definida, podría ser vulnerable.

La configuración por defecto para las páginas de extensiones del navegador es bastante restrictiva:

bash
script-src 'self'; object-src 'self';

Para más información sobre CSP y potential bypasses, consulta:

Content Security Policy (CSP) Bypass

web_accessible_resources

para que una página web pueda acceder a una página de una extensión del navegador, por ejemplo una .html, esa página debe estar indicada en el campo web_accessible_resources del manifest.json.
Por ejemplo:

javascript
{
...
"web_accessible_resources": [
{
"resources": [ "images/*.png" ],
"matches": [ "https://example.com/*" ]
},
{
"resources": [ "fonts/*.woff" ],
"matches": [ "https://example.com/*" ]
}
],
...
}

Estas páginas son accesibles en URLs como:

chrome-extension://<extension-id>/message.html

En las extensiones públicas el extension-id es accesible:

Sin embargo, si se usa el parámetro manifest.json use_dynamic_url, este id puede ser dinámico.

tip

Ten en cuenta que incluso si una página está mencionada aquí, podría estar protegida contra ClickJacking gracias a la Content Security Policy. Por lo tanto, también debes comprobarla (sección frame-ancestors) antes de confirmar que un ataque ClickJacking es posible.

Permitir el acceso a estas páginas las convierte en potencialmente vulnerables a ClickJacking:

BrowExt - ClickJacking

tip

Permitir que estas páginas se carguen solo por la extensión y no desde URLs aleatorias podría prevenir ataques ClickJacking.

caution

Ten en cuenta que las páginas de web_accessible_resources y otras páginas de la extensión también pueden contactar con background scripts. Por lo tanto, si una de estas páginas es vulnerable a XSS podría derivar en una vulnerabilidad mayor.

Además, ten en cuenta que solo puedes abrir en iframes las páginas indicadas en web_accessible_resources, pero desde una nueva pestaña es posible acceder a cualquier página de la extensión conociendo el extension ID. Por lo tanto, si se encuentra un XSS que abuse de los mismos parámetros, podría explotarse incluso si la página no está configurada en web_accessible_resources.

externally_connectable

Según la docs, la propiedad de manifest "externally_connectable" declara qué extensiones y páginas web pueden conectarse a tu extensión vía runtime.connect y runtime.sendMessage.

  • Si la llave externally_connectable no está declarada en el manifiesto de tu extensión o está declarada como "ids": ["*"], todas las extensiones pueden conectarse, pero ninguna página web puede conectarse.
  • Si se especifican IDs concretos, como en "ids": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], solo esas aplicaciones pueden conectarse.
  • Si se especifican matches, esas web apps podrán conectarse:
json
"matches": [
"https://*.google.com/*",
"*://*.chromium.org/*",
  • If it's specified as empty: "externally_connectable": {}, no app or web will be able to connect.

Cuantas menos extensiones y URLs se indiquen aquí, más pequeña será la superficie de ataque.

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 rogue 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 WebRequest or DeclarativeNetRequest APIs 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.

Resumen de comunicación

Extensión <--> WebApp

Para comunicarse entre el content script y la página web normalmente se usan post messages. Por tanto, en la aplicación web normalmente encontrarás llamadas a la función window.postMessage y en el content script listeners como window.addEventListener. Ten en cuenta, sin embargo, que la extensión también podría comunicarse con la aplicación web enviando un Post Message (y por tanto la web debería esperarlo) o simplemente hacer que la web cargue un nuevo script.

Dentro de la extensión

Normalmente se usa la función chrome.runtime.sendMessage para enviar un mensaje dentro de la extensión (usualmente manejado por el background script) y para recibir y manejarlo se declara un listener que llama a chrome.runtime.onMessage.addListener.

También es posible usar chrome.runtime.connect() para tener una conexión persistente en lugar de enviar mensajes individuales; puede usarse para send y receive messages como en el siguiente ejemplo:

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
})

También es posible enviar mensajes desde un background script a un content script ubicado en una pestaña específica llamando a chrome.tabs.sendMessage, donde necesitarás indicar el ID de la pestaña a la que enviar el mensaje.

Desde los externally_connectable permitidos a la extensión

Web apps y extensiones de navegador externas permitidas en la configuración externally_connectable pueden enviar solicitudes usando :

javascript
chrome.runtime.sendMessage(extensionId, ...

Cuando sea necesario mencionar el extension ID.

Native Messaging

Es posible que los background scripts se comuniquen con binaries dentro del sistema, lo que podría ser propenso a vulnerabilidades críticas como RCEs si esta comunicación no está debidamente asegurada. More on this later.

javascript
chrome.runtime.sendNativeMessage(
"com.my_company.my_application",
{ text: "Hello" },
function (response) {
console.log("Received " + response)
}
)

Web ↔︎ Comunicación con Content Script

Los entornos en los que operan los content scripts y en los que existen las host pages están separados entre sí, garantizando la aislación. A pesar de este aislamiento, ambos pueden interactuar con el Document Object Model (DOM) de la página, un recurso compartido. Para que la host page establezca comunicación con el content script, o indirectamente con la extensión a través del content script, es necesario utilizar el DOM accesible por ambas partes como canal de comunicación.

Post Messages

content-script.js
// 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
)
example.js
document.getElementById("theButton").addEventListener(
"click",
() => {
window.postMessage(
{ type: "FROM_PAGE", text: "Hello from the webpage!" },
"*"
)
},
false
)

Una comunicación Post Message segura debe comprobar la autenticidad del mensaje recibido; esto puede hacerse comprobando:

  • event.isTrusted: This is True only if the event was triggered by a users action
  • El content script podría esperar un mensaje solo si el usuario realiza alguna acción
  • origin domain: might expecting a message only allowlist of domains.
  • 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.

Las comprobaciones anteriores, incluso si se realizan, podrían ser vulnerables, así que consulta en la siguiente página los potential Post Message bypasses:

PostMessage Vulnerabilities

Iframe

Otra posible forma de comunicación puede ser a través de Iframe URLs, puedes encontrar un ejemplo en:

BrowExt - XSS Example

DOM

Esto no es "exactamente" una forma de comunicación, pero el sitio web y el content script tendrán acceso al web DOM. Por lo tanto, si el content script está leyendo información de éste, confiando en el web DOM, el sitio web podría modificar estos datos (porque no se debe confiar en el web, o porque el sitio es vulnerable a XSS) y comprometer el Content Script.

También puedes encontrar un ejemplo de un DOM based XSS to compromise a browser extension en:

BrowExt - XSS Example

Content Script ↔︎ Background Script Communication

A Content Script can use the functions runtime.sendMessage() or tabs.sendMessage() to send a one-time JSON-serializable message.

Para manejar la respuesta, usa la Promise devuelta. Aunque, por compatibilidad retroactiva, aún puedes pasar un callback como último argumento.

Enviar una petición desde un content script se ve así:

javascript
;(async () => {
const response = await chrome.runtime.sendMessage({ greeting: "hello" })
// do something with response here, not outside the function
console.log(response)
})()

Enviando una request desde la extension (usualmente un background script). Ejemplo de cómo enviar un message al content script en la selected tab:

javascript
// 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)
})()

En el lado receptor, debes configurar un runtime.onMessage manejador de eventos para manejar el mensaje. Esto se ve igual desde un content script o extension page.

javascript
// 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" })
})

En el ejemplo resaltado, sendResponse() se ejecutó de forma síncrona. Para modificar el manejador del evento onMessage y permitir la ejecución asíncrona de sendResponse(), es imprescindible incorporar return true;.

Una consideración importante es que, en escenarios donde varias páginas están configuradas para recibir eventos onMessage, la primera página que ejecute sendResponse() para un evento específico será la única capaz de entregar la respuesta efectivamente. Cualquier respuesta posterior al mismo evento no será tenida en cuenta.

Al desarrollar nuevas extensiones, se debe preferir el uso de promises en lugar de callbacks. Respecto al uso de callbacks, la función sendResponse() se considera válida solo si se ejecuta directamente en el contexto síncrono, o si el manejador del evento indica una operación asíncrona devolviendo true. Si ninguno de los manejadores devuelve true o si la función sendResponse() es liberada de memoria (garbage-collected), el callback asociado a sendMessage() se ejecutará por defecto.

Native Messaging

Las extensiones del navegador también permiten comunicarse con binarios en el sistema vía stdin. La aplicación debe instalar un json que lo indique en un json como:

json
{
"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/"]
}

Where the name is the string passed to runtime.connectNative() or runtime.sendNativeMessage() to communicate with the application from the background scripts of the browser extension. The path is the path to the binary, there is only 1 valid type which is stdio (use stdin and stdout) and the allowed_origins indicate the extensions that can access it (and can't have wildcard).

Chrome/Chromium will search for this json in some windows registry and some paths in macOS and Linux (more info in the docs).

tip

The browser extension also needs the nativeMessaing permission declared in order to be able to use this communication.

This is how it looks like some background script code sending messages to a native application:

javascript
chrome.runtime.sendNativeMessage(
"com.my_company.my_application",
{ text: "Hello" },
function (response) {
console.log("Received " + response)
}
)

In this blog post, se propone un patrón vulnerable que abusa de native messages:

  1. La extensión del navegador tiene un patrón wildcard para content script.
  2. El content script pasa mensajes postMessage al background script usando sendMessage.
  3. El background script pasa el mensaje a la aplicación nativa usando sendNativeMessage.
  4. La aplicación nativa maneja el mensaje de forma peligrosa, llevando a code execution.

Y dentro de él se explica un ejemplo de cómo ir desde cualquier página a RCE abusando de una extensión del navegador.

Información sensible en memoria/código/portapapeles

Si una extensión del navegador almacena información sensible en su memoria, esta podría ser volcada (especialmente en máquinas Windows) y buscada para obtener dicha información.

Por lo tanto, la memoria de la extensión del navegador no debe considerarse segura y la información sensible, como credenciales o frases mnemónicas, no debe almacenarse.

Por supuesto, no pongas información sensible en el código, ya que será pública.

Para volcar la memoria desde el navegador podrías volcar la memoria del proceso o ir a las configuraciones de la extensión del navegador y hacer clic en Inspect pop-up -> En la sección Memory -> Take a snaphost y presionar CTRL+F para buscar dentro del snapshot información sensible.

Además, la información altamente sensible, como claves mnemónicas o contraseñas, no debería permitirse copiarla en el portapapeles (o al menos eliminarla del portapapeles pasados unos segundos) porque entonces procesos que monitorizan el portapapeles podrán obtenerla.

Cargar una extensión en el navegador

  1. Descargar la extensión del navegador y descomprimirla
  2. Ve a chrome://extensions/ y activa el Developer Mode
  3. Haz clic en el botón Load unpacked

En Firefox ve a about:debugging#/runtime/this-firefox y haz clic en el botón Load Temporary Add-on.

Obtener el código fuente desde la tienda

El código fuente de una extensión de Chrome puede obtenerse mediante varios métodos. A continuación se ofrecen explicaciones detalladas e instrucciones para cada opción.

Descargar la extensión como ZIP vía línea de comandos

El código fuente de una extensión de Chrome puede descargarse como un archivo ZIP usando la línea de comandos. Esto implica usar curl para obtener el archivo ZIP desde una URL específica y luego extraer el contenido del ZIP a un directorio. Aquí están los pasos:

  1. Sustituye "extension_id" por el ID real de la extensión.
  2. Ejecuta los siguientes comandos:
bash
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"

Usar el sitio web CRX Viewer

https://robwu.nl/crxviewer/

Usar la extensión CRX Viewer

Otro método conveniente es usar el Chrome Extension Source Viewer, que es un proyecto open-source. Se puede instalar desde el Chrome Web Store. El código fuente del viewer está disponible en su GitHub repository.

Ver el código fuente de una extensión instalada localmente

Las extensiones de Chrome instaladas localmente también se pueden inspeccionar. Aquí se explica cómo:

  1. Accede al directorio de perfil local de Chrome visitando chrome://version/ y localizando el campo "Profile Path".
  2. Navega a la subcarpeta Extensions/ dentro del directorio de perfil.
  3. Esta carpeta contiene todas las extensiones instaladas, típicamente con su código fuente en un formato legible.

Para identificar extensiones, puedes mapear sus IDs a nombres:

  • Activa Developer Mode en la página about:extensions para ver los IDs de cada extensión.
  • Dentro de la carpeta de cada extensión, el archivo manifest.json contiene un campo name legible que ayuda a identificar la extensión.

Usar un File Archiver o Unpacker

Ve al Chrome Web Store y descarga la extensión. El archivo tendrá la extensión .crx. Cambia la extensión del archivo de .crx a .zip. Usa cualquier archivador de archivos (como WinRAR, 7-Zip, etc.) para extraer el contenido del archivo ZIP.

Usar Developer Mode en Chrome

Abre Chrome y ve a chrome://extensions/. Habilita "Developer mode" en la esquina superior derecha. Haz clic en "Load unpacked extension...". Navega hasta el directorio de tu extensión. Esto no descarga el código fuente, pero es útil para ver y modificar el código de una extensión ya descargada o desarrollada.

Chrome extension manifest dataset

Para intentar localizar extensiones de navegador vulnerables puedes usar el https://github.com/palant/chrome-extension-manifests-dataset y revisar sus archivos manifest en busca de señales potencialmente vulnerables. Por ejemplo, para buscar extensiones con más de 25000 usuarios, content_scripts y el permiso nativeMessaing:

bash
# 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-explotación: carga forzada de extensión y persistencia (Windows)

Técnica sigilosa para backdoorizar Chromium editando directamente las Preferences por usuario y forjando HMACs válidos, provocando que el navegador acepte y active una extensión unpacked arbitraria sin prompts ni flags.

Forced Extension Load Preferences Mac Forgery Windows

Lista de verificación de auditoría de seguridad

Aunque las extensiones del navegador tienen una superficie de ataque limitada, algunas pueden contener vulnerabilidades o posibles mejoras de hardening. Las siguientes son las más comunes:

  • Limitar al máximo los permissions solicitados
  • Limitar al máximo host_permissions
  • Usar una content_security_policy fuerte
  • Limitar al máximo el externally_connectable; si no es necesario y es posible, no dejarlo por defecto, especificar {}
  • Si aquí se menciona una URL vulnerable a XSS o a takeover, un atacante podrá enviar mensajes directamente a los background scripts. Bypass muy potente.
  • Limitar al máximo los web_accessible_resources, incluso dejarlos vacíos si es posible.
  • Si web_accessible_resources no es none, comprobar ClickJacking
  • Si ocurre cualquier comunicación desde la extensión hacia la página web, check for XSS vulnerabilidades causadas en la comunicación.
  • Si se usan Post Messages, revisar Post Message vulnerabilities.
  • Si el Content Script accede a detalles del DOM, comprobar que no introduzcan XSS si son modificados por la web
  • Hacer especial énfasis si esta comunicación también está involucrada en la Content Script -> Background script communication
  • Si el background script se comunica vía native messaging, comprobar que la comunicación sea segura y esté saneada
  • La información sensible no debería almacenarse dentro del código de la extensión del navegador
  • La información sensible no debería almacenarse en la memoria de la extensión
  • La información sensible no debería almacenarse en el sistema de archivos sin protección

Riesgos de las extensiones del navegador

  • La app https://crxaminer.tech/ analiza algunos datos como los permisos que la extensión solicita para dar un nivel de riesgo al uso de la extensión.

Herramientas

Tarnish

  • Descarga cualquier extensión de Chrome desde un enlace al Chrome webstore proporcionado.
  • manifest.json viewer: simplemente muestra una versión JSON embellecida del manifest de la extensión.
  • Fingerprint Analysis: Detección de web_accessible_resources y generación automática de JavaScript para fingerprinting de extensiones de Chrome.
  • Potential Clickjacking Analysis: Detección de páginas HTML de la extensión con la directiva web_accessible_resources establecida. Estas pueden ser potencialmente vulnerables a clickjacking dependiendo del propósito de las páginas.
  • Permission Warning(s) viewer: muestra una lista de todas las advertencias de permisos de Chrome que se mostrarán cuando un usuario intente instalar la extensión.
  • Dangerous Function(s): muestra la ubicación de funciones peligrosas que podrían ser explotadas por un atacante (p. ej. funciones como innerHTML, chrome.tabs.executeScript).
  • Entry Point(s): muestra dónde la extensión recibe input del usuario/externo. Esto es útil para entender la superficie de la extensión y buscar puntos potenciales para enviar datos maliciosamente creados a la extensión.
  • Ambos escáneres Dangerous Function(s) y Entry Point(s) incluyen para sus alertas generadas:
    • Fragmento de código relevante y la línea que provocó la alerta.
    • Descripción del problema.
    • Un botón “View File” para ver el archivo fuente completo que contiene el código.
    • La ruta del archivo afectado.
    • La URI completa de la extensión de Chrome del archivo afectado.
    • El tipo de archivo, por ejemplo Background Page script, Content Script, Browser Action, etc.
    • Si la línea vulnerable está en un archivo JavaScript, las rutas de todas las páginas donde se incluye, así como el tipo de esas páginas y el estado de web_accessible_resource.
  • Content Security Policy (CSP) analyzer and bypass checker: señalará debilidades en la CSP de tu extensión y también iluminará posibles formas de bypass debido a CDNs en la whitelist, etc.
  • Known Vulnerable Libraries: utiliza Retire.js para comprobar el uso de librerías JavaScript con vulnerabilidades conocidas.
  • Descargar la extensión y versiones formateadas.
  • Descargar la extensión original.
  • Descargar una versión embellecida de la extensión (HTML y JavaScript auto-prettified).
  • Caché automática de resultados de escaneo: ejecutar un escaneo de extensión tomará bastante tiempo la primera vez. Sin embargo, la segunda vez, asumiendo que la extensión no se haya actualizado, será casi instantáneo debido a la caché de resultados.
  • URLs de informe enlazables: facilita compartir con otra persona un informe de extensión generado por tarnish.

Neto

Project Neto es un paquete para Python 3 concebido para analizar y desvelar características ocultas de plugins y extensiones de navegador para navegadores conocidos como Firefox y Chrome. Automatiza el proceso de descomprimir los archivos empaquetados para extraer estas características de recursos relevantes en una extensión como manifest.json, carpetas de localización o archivos fuente JavaScript y HTML.

References

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks