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.