oss-sec mailing list archives

CVE-2026-4631 [cockpit] Unauthenticated remote code execution due to SSH command-line argument injection


From: Jelle van der Waa <jelle () vdwaa nl>
Date: Fri, 10 Apr 2026 15:55:58 +0200

Cockpit's remote login feature passes user-supplied hostnames and usernames from the web interface to the SSH client without validation or sanitization. An attacker with network access to the Cockpit web service can craft a single HTTP request to the login endpoint that injects malicious SSH options or shell commands, achieving code execution on the Cockpit host without valid credentials. The injection occurs during the authentication flow before any credential verification takes place, meaning no login is required to exploit the vulnerability.

------------------------------------------------------------------------

This was fixed in version 360 with versions > 326 being vulnerable, a patch for backporting is available in the GitHub security advisory. More extensive details from the bugzilla issue are copy & pasted below: [1] [2]

By default, Cockpit supports logging into remote machines via SSH
(https://github.com/cockpit-project/cockpit/blob/main/doc/authentication.md#remote-machines).
While previous Cockpit versions used the dedicated cockpit-ssh helper
(based on libssh), Cockpit since version 326/327 executes "python3 -m
cockpit.beiboot", which in turn invokes the OpenSSH "ssh" client to
connect to remote machines. The SSH "connect to" feature is available
prior to authentication, meaning an attacker with access to the Cockpit
webservice can trigger the execution of ssh on the Cockpit host. To be
precise: the beiboot process is spawned as part of the authentication
flow, but the attacker only needs to supply an arbitrary "Authorization:
Basic" header with any credentials (even invalid ones) in a request to
"/cockpit+=<hostname>/login" to trigger the ssh invocation. The injected
commands execute before SSH authentication completes or fails.

The security issue is that SSH connection parameters are passed down as
command-line arguments to ssh without any validation or sanitization.
Neither cockpit-ws (C code in cockpitauth.c / cockpitauthorize.c) nor
cockpit.beiboot (Python code in beiboot.py) performs any character or
format checks on the username or hostname before passing them to the ssh
process. In particular, this allows an attacker to invoke ssh on the
Cockpit host with an arbitrary username and hostname during the login flow.

The resulting ssh invocation looks like (simplified; additional options
like -o NumberOfPasswordPrompts=1 are omitted for clarity):

    arg0: ssh
    arg1: -l
    arg2: <username>
    arg3: <hostname>
    arg4: python3 -ic '# cockpit-bridge'

An attacker has full control over <username> and near-full control over
<hostname> (some characters like whitespace and slashes cannot be used
because the hostname is extracted from the URL path). Notably, there is
no "--" separator between the ssh options and the destination argument,
which enables option injection via the hostname field.

This leads to the following two vulnerabilities:

(1) Injection of malicious remote username leading to RCE

SSH allows the use of the remote username as a variable in SSH
configuration files via the %r token. A potential SSH configuration
could be:

    Match exec "/usr/bin/test %r = blocked_user"
        ProxyCommand /bin/false

With this configuration, ssh executes the command "/usr/bin/test
<username> = blocked_user" during connection setup. Since %r is expanded
before the command is passed to the shell, an attacker can inject
arbitrary shell commands through the username. For example, using the
username "x; touch /tmp/flag; #" would cause ssh to execute:

    /usr/bin/test x; touch /tmp/flag; # = blocked_user

The command injection occurs before ssh validates the username format.
Although ssh ultimately terminates with "remote username containing
invalid characters", the injected command ("touch /tmp/flag") has
already been executed.

This means if the Cockpit host's ssh_config uses %r in a "Match exec"
directive, Cockpit is vulnerable to unauthenticated remote code execution.

I am in parallel in contact with the OpenSSH maintainers to get this
problem fixed in OpenSSH as well, though I believe it is also an issue
in Cockpit for passing unverified data to ssh.

(2) Injection of malicious hostname leading to RCE

Since the hostname is passed as a positional argument to ssh without a
preceding "--" separator (see via_ssh() in beiboot.py), an attacker can
inject SSH options by supplying a hostname that starts with "-". For
example, the attacker can pass "-oProxyCommand=<malicious_command>" as
the hostname via the URL path
"/cockpit+=-oProxyCommand=<malicious_command>/login".

ProxyCommand is an SSH client option that executes a specified program
whenever SSH connects to a remote host. When the original hostname field
is consumed as an option instead of a hostname, ssh interprets the next
positional argument, which is "python3 -ic '# cockpit-bridge'" (the
remote command), as the actual hostname. This means the malicious
ProxyCommand is executed as ssh attempts to "connect" to this
misinterpreted hostname.

Fortunately, OpenSSH version 9.6 introduced early hostname validation
that bans shell metacharacters in command-line hostnames and usernames
(https://github.com/openssh/openssh-portable/commit/7ef3787) before
establishing an SSH connection. With OpenSSH >= 9.6, the command
injection via "-oProxyCommand=<malicious_command>" fails because
"python3 -ic '# cockpit-bridge'" contains invalid hostname characters
(spaces, quotes, etc.), and ssh aborts before executing the
ProxyCommand. Nevertheless, on older OpenSSH versions, this check is not
available and <malicious_command> will be executed by OpenSSH in an
attempt to connect to "python3 -ic '# cockpit-bridge'".

The probability that a Cockpit host is vulnerable to this depends on the
OpenSSH version installed. Cockpit migrated to the beiboot/OpenSSH path
in version 327 (released 2024-10-23), while OpenSSH 9.6 was released on
2023-12-18, which is roughly 10 months earlier. Systems that upgraded
Cockpit to >= 327 but did not update OpenSSH to >= 9.6 would be
vulnerable to unauthenticated remote code execution via hostname injection.

Potential fix:

1. Add a "--" separator before the destination argument in via_ssh()
(beiboot.py) to prevent option injection via the hostname. This directly
mitigates vulnerability (2).
2. Validate both username and hostname using a character allowlist
before passing them to ssh. Examples for such validation can be found in
OpenSSH's source code: valid_hostname() and valid_ruser() in ssh.c. This
mitigates both vulnerabilities (1) and (2).

Summary:

While the preconditions for these vulnerabilities will likely affect
only a subset of Cockpit installations, the impact is critical:
unauthenticated remote code execution on the Cockpit host. An attacker
with network access to the Cockpit webservice can trigger the exploit
with a single crafted HTTP request to the login endpoint. No valid
credentials are required, as the injection occurs during the
authentication flow before SSH authentication completes.

[1] https://bugzilla.redhat.com/show_bug.cgi?id=2450246
[2] https://github.com/cockpit-project/cockpit/security/advisories/GHSA-m4gv-x78h-3427


Current thread: