Metodología de pentesting de extensiones de navegador

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 de navegador están escritas en JavaScript y son cargadas por el navegador en segundo plano. Tiene su DOM pero puede interactuar con los DOMs de otros sitios. Esto significa que puede comprometer la confidencialidad, integridad y disponibilidad (CIA) de otros sitios.

Componentes principales

Los esquemas de las extensiones se ven mejor cuando se visualizan y consisten en 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 lo tanto está expuesto a entrada potencialmente maliciosa. Sin embargo, el script de contenido no contiene 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ía de los privilegios/accesos de la extensión, pero el núcleo de la extensión solo puede interactuar con el contenido web vía XMLHttpRequest y content scripts. Además, el núcleo de la extensión no tiene acceso directo a la máquina host.

Binario nativo

La extensión permite un binario nativo que puede acceder a la máquina host 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) estándar usada por Flash y otros plug-ins del navegador.

Boundaries

Caution

Para obtener los privilegios completos del usuario, un atacante debe convencer a la extensión de pasar entrada maliciosa desde el content script al extension’s core y desde el extension’s core al native binary.

Cada componente de la extensión está separado entre sí por fuertes límites protectores. Cada componente se ejecuta en un proceso separado del sistema operativo. Los content scripts y los extension cores se ejecutan en sandbox processes no disponibles para la mayoría de los servicios del sistema operativo.

Además, los content scripts se separan de sus páginas web asociadas al ejecutarse en un heap de JavaScript separado. El content script y la página web tienen acceso al mismo DOM subyacente, pero ambos nunca intercambian punteros de JavaScript, previniendo el leaking de 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 el layout, los permisos y otras opciones de configuración.

Ejemplo:

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

Content scripts se cargan siempre que el usuario navega a una página que coincida, 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).

"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.

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

Se envía un mensaje a las páginas de la extensión desde el script de contenido cuando se pulsa este botón, mediante la utilización de la runtime.sendMessage() API. Esto se debe a la limitación del script de contenido 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 scripts de contenido pueden comunicarse.

Warning

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

Para ver y depurar scripts de contenido en Chrome, el menú Chrome developer tools puede abrirse desde Options > More tools > Developer tools O presionando Ctrl + Shift + I.

Al mostrar las developer tools, haz clic en la Source tab, seguido de la pestaña Content Scripts. Esto permite observar los scripts de contenido en ejecución de varias extensiones y establecer puntos de interrupción para rastrear el flujo de ejecución.

Scripts de contenido inyectados

Tip

Ten en cuenta que Content Scripts aren’t mandatory ya que también es posible inyectar scripts de forma dinámica y programáticamente en páginas web mediante tabs.executeScript. Esto realmente proporciona controles más granulares.

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

Ejemplo de extensión basada en activeTab

{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}
  • Inyectar un archivo JS al hacer 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"],
})
})
  • Inject a function al hacer 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,
})
})

Ejemplo con permisos 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" })

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 los 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 el resto del DOM o se ejecute cualquier 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

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

A través de service-worker.js

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 cumple 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 toda la vida útil de la extensión, funcionando de forma discreta sin interacción directa del usuario. Posee su propio Document Object Model (DOM), lo que permite interacciones complejas y la gestión de estado.

Puntos clave:

  • Función de la página en segundo plano: Actúa como el centro neurálgico de la extensión, asegurando la comunicación y la 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 autogenerada incluirá todos los background scripts especificados en el manifest de la extensión, garantizando la operación fluida 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) asegura que todos los background scripts necesarios estén integrados y operativos, simplificando el proceso de configuración de la extensión.

Ejemplo de background script:

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

Usa la runtime.onMessage API para escuchar mensajes. Cuando se recibe un mensaje "explain", usa la tabs API para abrir una página en una nueva pestaña.

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:

Options pages and other

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

  • Action pages se muestran en un drop-down when the extension icon is clicked.
  • Páginas que la extensión cargará en una nueva pestaña.
  • Option Pages: Esta página se muestra encima de la extensión cuando se hace clic. En el manifiesto 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 las background pages, ya que cargan contenido dinámicamente según la necesidad. A pesar de esto, comparten ciertas capacidades con la página de background:

  • Communication with Content Scripts: De forma similar a la página de background, estas páginas pueden recibir mensajes desde content scripts, facilitando la interacción dentro de la extensión.
  • Access to Extension-Specific APIs: Estas páginas disfrutan de acceso completo a las APIs específicas de la extensión, sujeto a los permisos definidos para la extensión.

permissions & host_permissions

permissions and host_permissions son entradas en el manifest.json que indicarán qué permisos tiene la extensión del navegador (storage, location…) y en qué páginas web.

Dado que las extensiones del navegador pueden ser tan privilegiadas, una maliciosa o una comprometida podría permitir al atacante diferentes medios para robar información sensible y espiar al usuario.

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:

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

Para más información sobre CSP y potenciales 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, una página .html por ejemplo, dicha página debe estar mencionada en el campo web_accessible_resources del manifest.json.
Por ejemplo:

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

Estas páginas son accesibles en una URL como:

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

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

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

Tip

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

Poder acceder a estas páginas las convierte en potencialmente vulnerables a ClickJacking:

BrowExt - ClickJacking

Tip

Permitir que estas páginas solo sean cargadas por la extensión y no por URLs aleatorias podría prevenir ataques de ClickJacking.

Caution

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

Además, ten en cuenta que solo puedes abrir las páginas indicadas en web_accessible_resources dentro de iframes, 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 una 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

A per the docs, The "externally_connectable" manifest property declares which extensions and web pages can connect to your extension via runtime.connect and runtime.sendMessage.

  • Si la clave 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:
"matches": [
"https://*.google.com/*",
"*://*.chromium.org/*",
  • Si se especifica vacío: "externally_connectable": {}, ninguna app o web podrá conectarse.

La cuantas menos extensiones y URLs se indiquen aquí, menor será la superficie de ataque.

Caution

Si una página web indicada en externally_connectable es vulnerable a XSS o takeover, un atacante podrá enviar mensajes directamente al background script, eludiendo por completo al Content Script y su CSP.

Por lo tanto, esto es un bypass muy potente.

Además, si el cliente instala una extensión maliciosa, aunque no tenga permiso para comunicarse con la extensión vulnerable, podría inyectar datos XSS en una página web permitida o abusar de las APIs WebRequest o DeclarativeNetRequest para manipular peticiones en un dominio objetivo, alterando la petición de una página por un archivo JavaScript. (Tenga en cuenta que la CSP de la página objetivo podría evitar estos ataques). Esta idea proviene de este writeup.

Resumen de comunicación

Extensión <–> WebApp

Para comunicarse entre el content script y la página web se suelen usar post messages. Por lo 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 lo tanto la web debería esperarlo) o simplemente hacer que la web cargue un script nuevo.

Dentro de la extensión

Normalmente se usa la función chrome.runtime.sendMessage para enviar un mensaje dentro de la extensión (normalmente manejado por el background script) y, para recibirlo y manejarlo, se declara un listener usando chrome.runtime.onMessage.addListener.

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

chrome.runtime.connect() ejemplo ```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>

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 `externally_connectable` permitido a la extensión

**Aplicaciones web 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 scripts en segundo plano se comuniquen con binarios dentro del sistema, lo que podría ser susceptible a vulnerabilidades críticas como RCEs si esta comunicación no está adecuadamente asegurada. More on this later.

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

Web ↔︎ Comunicación con content scripts

Los entornos donde operan los content scripts y donde existen las páginas host están separados entre sí, garantizando aislamiento. A pesar de este aislamiento, ambos pueden interactuar con el Document Object Model (DOM) de la página, un recurso compartido. Para que la página host se comunique 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

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

Una comunicación Post Message segura debe comprobar la autenticidad del mensaje recibido; esto se puede hacer verificando:

  • event.isTrusted: Esto es True solo si el evento fue desencadenado por una acción del usuario
  • El content script podría esperar un mensaje solo si el usuario realiza alguna acción
  • origin domain: podría esperar un mensaje solo de una allowlist de dominios.
  • Si se usa un regex, ten mucho cuidado
  • Source: received_message.source !== window puede usarse para comprobar si el mensaje fue from the same window donde el Content Script está escuchando.

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

PostMessage Vulnerabilities

Iframe

Otra posible vía de comunicación puede ser mediante Iframe URLs, puedes encontrar un ejemplo en:

BrowExt - XSS Example

DOM

Esto no es “exactamente” una forma de comunicación, pero el la web y el content script tendrán acceso al web DOM. Entonces, si el content script está leyendo información de él, confiando en el web DOM, la web podría modificar estos data (porque la web no debería ser confiada, o porque la web 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

Un Content Script puede usar las funciones runtime.sendMessage() or tabs.sendMessage() para enviar un mensaje one-time JSON-serializable.

Para manejar la response, usa la Promise retornada. Aunque, por compatibilidad hacia atrás, aún puedes pasar un callback como último argumento.

Enviar una solicitud desde un content script se ve así:

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

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

// 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, necesitas configurar un runtime.onMessage event listener para manejar el mensaje. Esto es igual tanto desde un content script como desde una 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" })
})

En el ejemplo destacado, sendResponse() se ejecutó de forma síncrona. Para modificar el manejador del evento onMessage para la ejecución asíncrona de sendResponse(), es imperativo 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 de forma efectiva. Cualquier respuesta posterior al mismo evento no será tenida en cuenta.

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

Native Messaging

Browser extensions also allow to communicate with binaries in the system via stdin. The application must install a json indicating so in a json like:

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

Donde el name es la cadena pasada a runtime.connectNative() o runtime.sendNativeMessage() para comunicarse con la aplicación desde los background scripts de la extensión del navegador. El path es la ruta al binario, solo hay 1 type válido que es stdio (use stdin y stdout) y los allowed_origins indican las extensiones que pueden acceder a él (y no pueden tener comodín).

Chrome/Chromium buscará este json en algunas claves del registro de Windows y en algunas rutas en macOS y Linux (más info en los docs).

Tip

La extensión del navegador también necesita declarar el permiso nativeMessaing para poder usar esta comunicación.

Así es como se ve el código de un background script enviando mensajes a una aplicación nativa:

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. Browser extension tiene un patrón wildcard para content script.
  2. Content script pasa mensajes postMessage al background script usando sendMessage.
  3. 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 ello se explica un ejemplo de cómo ir de 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, esto podría ser volcado (especialmente en máquinas Windows) y buscado para encontrar dicha información.

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

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

Para volcar la memoria del navegador podrías dump the process memory o ir a los settings de la extensión del navegador y hacer click en Inspect pop-up -> En la sección Memory -> Take a snaphost y usar CTRL+F para buscar dentro del snapshot información sensible.

Además, información altamente sensible como mnemonic keys o contraseñas no debería permitirse copiarse en el clipboard (o al menos eliminarla del clipboard en unos segundos) porque entonces procesos que monitorizan el clipboard podrán obtenerlas.

Loading an Extension in the Browser

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

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

Getting the source code from the store

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

Download Extension as ZIP via Command Line

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. Reemplaza “extension_id” con el ID real de la extensión.
  2. Ejecuta los siguientes comandos:
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 Chrome Extension Source Viewer, que es un proyecto open-source. Se puede instalar desde el Chrome Web Store. El source code del viewer está disponible en su GitHub repository.

Ver el source de una extensión instalada localmente

También se pueden inspeccionar las Chrome extensions instaladas localmente. Aquí está cómo:

  1. Accede al directorio de tu perfil local de Chrome visitando chrome://version/ y localizando el campo “Profile Path”.
  2. Navega a la subcarpeta Extensions/ dentro del directorio del perfil.
  3. Esta carpeta contiene todas las extensiones instaladas, típicamente con su source code 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 te ayuda a identificar la extensión.

Usar un File Archiver o Unpacker

Ve a la 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 file archiver (como WinRAR, 7-Zip, etc.) para extraer el contenido del archivo ZIP.

Usar Developer Mode en Chrome

Abre Chrome y ve a chrome://extensions/. Activa “Developer mode” en la esquina superior derecha. Haz clic en “Load unpacked extension…”. Navega al directorio de tu extensión. Esto no descarga el source code, pero es útil para ver y modificar el código de una extensión ya descargada o en desarrollo.

Chrome extension manifest dataset

Para intentar detectar browser extensions vulnerables puedes usar el https://github.com/palant/chrome-extension-manifests-dataset y revisar sus manifest files en busca de indicios potencialmente vulnerables. Por ejemplo, para buscar extensiones con más de 25000 usuarios, content_scripts y el permiso 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)

Técnica sigilosa para backdoorear 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

Detección de actualizaciones maliciosas de extensiones (Static Version Diffing)

Los compromisos en la supply-chain a menudo llegan como actualizaciones maliciosas a extensiones previamente benignas. Un enfoque práctico y de bajo ruido es comparar un nuevo paquete de extensión contra la última versión conocida buena usando análisis estático (por ejemplo, Assemblyline). El objetivo es alertar sobre deltas de alta señal en lugar de sobre cualquier cambio.

Workflow

  • Enviar ambas versiones (antigua + nueva) al mismo perfil de análisis estático.
  • Marcar background/service worker scripts nuevos o actualizados (persistencia + lógica privilegiada).
  • Marcar content scripts nuevos o actualizados (acceso al DOM y recolección de datos).
  • Marcar nuevos permissions / host_permissions añadidos en manifest.json.
  • Marcar nuevos dominios extraídos del código (potenciales endpoints C2/exfil).
  • Marcar nuevas detecciones de análisis estático (p. ej., base64 decode, cookie harvesting, network-request builders, patrones de obfuscation).
  • Marcar anomalías estadísticas como saltos bruscos de entropy o z-scores atípicos en scripts cambiados.

Detectar cambios en scripts con precisión

  • Nuevo script añadido → detectar vía diff de manifest.json.
  • Script existente modificado (manifest sin cambios) → comparar hashes por archivo desde el árbol de archivos extraído (p. ej., salida Extract de Assemblyline). Esto detecta actualizaciones sigilosas en workers o content scripts existentes.

Detecciones pre-divulgación

Para evitar detecciones “modo fácil” basadas en IOCs ya conocidos, deshabilita servicios alimentados por threat-intel y confía en señales intrínsecas (dominios, firmas heurísticas, deltas de scripts, anomalías de entropy). Esto aumenta las probabilidades de detectar actualizaciones maliciosas antes de su reporte público.

Ejemplo de lógica de alerta de alta confianza

  • Combo de bajo ruido: nuevos dominios + nuevas detecciones de análisis estático + background/service worker actualizado + content scripts añadidos o actualizados.
  • Captura más amplia: nuevo dominio + background/service worker nuevo o actualizado (mayor recall, mayor ruido).

Servicios clave de Assemblyline para este workflow:

  • Extract: desempaqueta la extensión y produce hashes por archivo.
  • Characterize: calcula características de los archivos (p. ej., entropy).
  • JsJAWS / FrankenStrings / URLCreator: exponen heurísticas JS, strings y dominios para diferenciar entre versiones.

Security Audit Checklist

Aunque las Browser Extensions tienen una superficie de ataque limitada, algunas pueden contener vulnerabilidades o mejoras de hardening potenciales. Las siguientes son las más comunes:

  • Limitar tanto como sea posible los permissions solicitados
  • Limitar tanto como sea posible los host_permissions
  • Usar una fuerte content_security_policy
  • Limitar tanto como sea posible el externally_connectable; si no se necesita y es posible, no dejarlo por defecto, especificar {}
  • Si se menciona una URL vulnerable a XSS o a takeover, un atacante podrá enviar mensajes directamente a los background scripts. Bypass muy potente.
  • Limitar tanto como sea posible los web_accessible_resources, incluso vacíos si es posible.
  • Si web_accessible_resources no es ninguno, comprobar ClickJacking
  • Si cualquier comunicación ocurre desde la extension hacia la web page, comprobar XSS vulnerabilidades causadas en la comunicación.
  • Si se usan Post Messages, comprobar por Post Message vulnerabilities.
  • Si el Content Script accede al DOM, verificar que no estén introduciendo XSS si son modificados por la web
  • Poner 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é sanitizada
  • Información sensible no debe almacenarse dentro del código de la Browser Extension
  • Información sensible no debe almacenarse dentro de la memoria de la Browser Extension
  • Información sensible no debe almacenarse en el file system sin protección

Browser Extension Risks

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

Tools

Tarnish

  • Recupera cualquier Chrome extension desde un enlace proporcionado de Chrome webstore.
  • manifest.json viewer: simplemente muestra una versión JSON-prettified del manifest de la extensión.
  • Fingerprint Analysis: Detección de web_accessible_resources y generación automática de JavaScript para fingerprinting de Chrome extension.
  • 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 todos los avisos de permissions 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 de usuario/externo. Esto es útil para entender la superficie de la extensión y buscar puntos potenciales para enviar datos maliciosamente construidos a la extensión.
  • Tanto los escáneres de Dangerous Function(s) como Entry Point(s) generan para sus alertas:
  • Fragmento de código relevante y línea que causó 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 alertado.
  • El URI completo de la Chrome extension del archivo alertado.
  • El tipo de archivo que es, como 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 está incluido así como el tipo de esas páginas y el estado de web_accessible_resource.
  • Content Security Policy (CSP) analyzer and bypass checker: Señala debilidades en la CSP de tu extensión e ilumina posibles formas de bypass debido a CDNs en listas blancas, etc.
  • Known Vulnerable Libraries: Usa Retire.js para comprobar el uso de librerías JavaScript con vulnerabilidades conocidas.
  • Descargar extensión y versiones formateadas.
  • Descargar la extensión original.
  • Descargar una versión beautified de la extensión (HTML y JavaScript auto-prettified).
  • Caché automática de resultados de escaneo; ejecutar un scan de extensión tomará bastante tiempo la primera vez. Sin embargo la segunda vez, asumiendo que la extensión no fue actualizada, será casi instantáneo debido al cache de resultados.
  • URLs de reportes enlazables, facilita compartir el reporte generado por tarnish.

Neto

Project Neto es un paquete Python 3 concebido para analizar y desentrañar funcionalidades ocultas de plugins y extensiones de navegador conocidos como Firefox y Chrome. Automatiza el proceso de descomprimir los archivos empaquetados para extraer estas funcionalidades 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