Bug 2450246 (CVE-2026-4631) - CVE-2026-4631 cockpit: Cockpit: Unauthenticated remote code execution due to SSH command-line argument injection
Summary: CVE-2026-4631 cockpit: Cockpit: Unauthenticated remote code execution due to ...
Keywords:
Status: NEW
Alias: CVE-2026-4631
Deadline: 2026-04-07
Product: Security Response
Classification: Other
Component: vulnerability
Version: unspecified
Hardware: All
OS: Linux
urgent
urgent
Target Milestone: ---
Assignee: Product Security DevOps Team
QA Contact:
URL:
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2026-03-23 08:25 UTC by OSIDB Bzimport
Modified: 2026-04-09 06:58 UTC (History)
2 users (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed:
Embargoed:


Attachments (Terms of Use)

Description OSIDB Bzimport 2026-03-23 08:25:05 UTC
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.


Note You need to log in before you can comment on or make changes to this bug.