Перейти к содержимому
НовостиGitHubСкачать

Безопасность

Не открывайте публичный issue. В репозитории включены приватные сообщения об уязвимостях GitHub: откройте новое сообщение на вкладке Security. Его видят только мейнтейнеры, а обсуждение и исправление идут в закрытом advisory.

Чтобы мы быстрее разобрались, укажите:

  • версию или коммит Citrus и платформу;
  • затронутый компонент (протокол, листенер, контроллер, обновление и так далее);
  • минимальный конфиг и шаги воспроизведения;
  • что может сделать атакующий и какой доступ ему нужен.

Мы подтверждаем получение, исправляем в закрытой ветке, публикуем исправление и advisory и указываем автора, если он не против. Правила те же, что в SECURITY.md.

Уязвимости в форках getmikan/utls, getmikan/tls и getmikan/http присылайте сюда же. Если проблема есть и в mihomo, отметьте это в сообщении.

Исправления выходят в ветке dev, затем в main и в релизах. Отдельных веток поддержки для старых версий нет: обновляйтесь до последней сборки.

Исправления по ревью ядра, форков и статическим аудитам перечислены в журнале изменений, вместе с PR и проверками. Каждый новый отчёт сверяется с текущим кодом: наличие старого исправления в журнале само по себе не подтверждает закрытие нового пункта. Ограничения совместимости описаны ниже.

  • Без secret контроллер не слушает сетевой адрес; без secret на loopback он отклоняет запросы браузера с чужих сайтов и DNS rebinding.
  • Unix-сокет и именованный канал доступны только владельцу; secret проверяется на всех листенерах, включая /debug.
  • Смена secret действует сразу и закрывает открытые потоки; соединения ограничены по времени заголовков и простоя.
  • API не отдаёт пароли и ключи входящих, а external-ui не может указывать на каталог с конфигом и ключами.
  • HTTP-прокси авторизует каждый запрос, а не только первый на соединении.
  • UDP через SOCKS5 работает только при открытой ассоциации и привязан к пользователю и порту.
  • Смена пароля или удаление пользователя закрывает его сессии; пустые имена запрещены.
  • UDP-сессии разделены по входящему и пользователю, так что правила и лимиты одного пользователя не достаются другому.
  • Адрес клиента из заголовков прокси принимается только от перечисленных trusted-proxies.
  • Пользователи серверных входящих не могут через DIRECT попасть на loopback, в частные сети, к metadata облака и на этот хост, какие бы ни были правила: защита исходящего трафика.
  • Общая TCP-обёртка REALITY, ShadowTLS, JLS и ResTLS допускает до 256 незавершённых рукопожатий на listener и закрывает их через 15 секунд. После решения «клиент или fallback» этот срок снимается; ограничения fallback настраиваются отдельно от рукопожатия.
  • Состояние, которое клиент может создать до проверки учётных данных (сессии XHTTP, тикеты 0-RTT, потоки QUIC, мультиплексированные потоки, сборка фрагментов), ограничено по числу, объёму и времени.
  • Длины, которые объявляет собеседник, проверяются до выделения памяти.
  • Ответы провайдеров, geodata, архивы UI и пакеты обновлений ограничены по размеру, архивы — ещё и по числу записей.
  • Пути проверяются с учётом символических ссылок, файлы провайдеров записываются атомарно.
  • Публичный URL не может перенаправить загрузку во внутреннюю сеть.
  • Новые файлы конфигурации, провайдеров и кэша создаются с правами только для владельца.
  • Минимум TLS 1.2 на всех TLS-входящих и на контроллере.
  • В форки getmikan/tls и getmikan/utls перенесены исправления безопасности crypto/tls из Go 1.26 (проверка ссылок на внешние расширения ECH, CVE-2026-97031, и ограничение KeyUpdate).
  • Самообновление требует подписанный Ed25519 manifest со сроком действия и привязкой к каналу, sequence сборки, платформе, имени, размеру и SHA-256. Два встроенных release-ключа позволяют overlap-ротацию; отозванные ID отклоняются, sequence по каналу блокирует rollback.
  • Удалённым GEO-базам можно задать SHA-256 для каждого типа (geox-sha256): база с другим хешем отклоняется. Без пина база принимается как есть: издатели баз их не подписывают.
  • Набор корневых сертификатов Mozilla хранится в репозитории, а не копируется с раннера CI, и государственные CA для перехвата TLS по умолчанию не считаются доверенными; см. Корневые сертификаты.
  • Все GitHub Actions закреплены по SHA коммита, базовые образы Docker и образы тестов — по digest; обновления приходят через Dependabot.
  • Токены CI получают минимальные права: сборке — только чтение, публикации релиза и образа — только нужные scope.
  • На каждый PR работают CodeQL, govulncheck, dependency review, go vet и проверка go mod tidy.
  • Бинарники Xray и mihomo для интеграционных тестов сверяются с закреплённым SHA-256.

Речь о TLS-клиентах самого ядра: транспорты TLS, WebSocket и gRPC у VLESS и Trojan, HTTP и SOCKS поверх TLS, Hysteria2, TUIC, DoH, DoT и DoQ, загрузка провайдеров и geodata.

  • Встроенный набор. В бинарник встроено хранилище корневых сертификатов Mozilla в выгрузке curl (component/ca/ca-certificates.crt, источник — curl.se/ca/cacert.pem); дата данных Mozilla записана в заголовке файла. make ca-bundle обновляет его и сверяет SHA-256, который публикует curl.
  • Источники. tls.ca-roots задаёт, откуда берутся доверенные корни: both (по умолчанию) — системное хранилище плюс встроенный набор, bundle — только встроенный набор, установленные на устройство CA не учитываются, system — только системное хранилище. Переменные окружения DISABLE_SYSTEM_CA и DISABLE_EMBED_CA по-прежнему сужают both.
  • Чёрный список. Некоторые государственные CA существуют для перехвата TLS: если такой CA установлен на устройство, его владелец может читать проксируемый трафик. Ядро никогда не доверяет им, откуда бы они ни пришли, из системы или из набора: Russian Trusted Root CA и Russian Trusted Sub CA (ключи 2022 и 2024 годов), казахстанские Qaznet Trust Network и Information Security Certification Authority (2019–2021). Записи сверяются по SHA-256 открытого ключа, так что перевыпущенный сертификат с тем же ключом тоже отклоняется. Цепочка через такой CA завершается ошибкой certificate chain goes through the distrusted CA. Список и источники — в component/ca/denylist.go.
  • Отключение. tls.ca-denylist: false снова разрешает эти CA. Чтобы доверять только одному из них, добавьте его в tls.custom-certifactes: цепочки, которые заканчиваются на пользовательском сертификате, чёрный список не проверяет.
  • Пиннинг. fingerprint и skip-cert-verify — явный выбор для отдельного прокси, на них чёрный список не действует.
  • Исключение. Управляющий клиент Tailscale сам добавляет эти корни к системному пулу, поэтому bundle его не сужает и чёрный список там не действует.
tls:
ca-roots: bundle # both | bundle | system
ca-denylist: true # false снова разрешает государственные CA из списка

Браузерная панель сначала отправляет POST /auth/ws-ticket с Authorization: Bearer <secret>, Content-Type: application/json и телом {"path":"/events?types=auth"}. Успешный ответ — HTTP 201 и {"ticket":"...","expires_in":30} с Cache-Control: no-store. Затем панель подключается к /events?types=auth&ticket=... по WebSocket. Для каждого переподключения нужен новый билет.

Билет действует 30 секунд, погашается атомарно при одной попытке авторизации и разрешает только GET-upgrade на указанный поток с теми же query-параметрами. Если при выдаче был заголовок Origin, он должен совпасть при подключении. Поддерживаются /events, /logs, /traffic, /traffic/users, /memory и /connections. Билетом нельзя вызвать REST API или выпустить другой билет. На один обработчик контроллера допускается до 1024 непогашенных билетов; сверх лимита выдача отвечает HTTP 429, истёкшие билеты освобождают ёмкость. Пересоздание контроллера, включая смену секрета, аннулирует ранее выданные билеты.

Нативные клиенты могут продолжать передавать Bearer-заголовок при WebSocket-upgrade. Для старых браузерных панелей есть выключенная по умолчанию опция external-controller-allow-legacy-websocket-token: true. Она разрешает прежний ?token=<secret> на потоках WebSocket и выводит предупреждение: URL снова содержит основной многоразовый секрет, поэтому это режим совместимости, а не устранение риска. В обычном режиме в URL находится только короткоживущий одноразовый билет.

  • Sudoku: текущий handshake принимает знание настроенного key как секрет доступа и не доказывает владение отдельным приватным ключом. Храните это значение в секрете, даже если инструмент называет его публичным ключом. Добавление такого доказательства требует изменения протокола; текущая совместимость сохранена, этот пункт не считается исправленным.
  • KCPTun: предупреждение о стандартном ключе и лимиты ресурсов не заменяют уникальный секрет. Старый общий ключ остаётся доступен для совместимости; без явного key этот транспорт нельзя считать защищённым отдельным секретом сервера.

Подпись релизов и проверка подлинности загружаемых дашбордов и geodata — в дорожной карте.