Сжатие в SSH десятилетиями считалось безобидной опцией для медленных каналов: добавил флаг -C, и файлы пошли быстрее. Выпуск OpenSSH 10.6 от 6 октября 2026 года ломает эту привычку. Разработчики отключили в ssh и sshd словарный кодировщик LZ77, потому что общий словарь одного соединения позволял через один канал выяснять содержимое другого. Вместе с этим в версию вошли правки в SFTP и GSSAPI, более строгая проверка имён пользователей, новое имя гибридного алгоритма подписи и несколько смен поведения по умолчанию.
Для одного ноутбука всё это сводится к паре минут на обновление пакета. Для парка из десятков или сотен машин картина другая: часть клиентов живёт в контейнерах и CI-раннерах, часть серверов получает исправления из репозиториев дистрибутива с задержкой, а скрипты резервного копирования годами пользуются сжатием, которое теперь стало слабее. Ниже разобран порядок практического аудита с командами, которые можно запускать сразу.
Общий контекст сжатия в одном соединении и механика утечки через длину шифротекста
SSH-соединение похоже на одну трубу с несколькими жилами внутри. По одному TCP-сокету идут интерактивная сессия, передача файлов, обращения к другим службам, работа агента ключей. Каждая такая жила называется каналом, и все каналы делят общий транспорт: ключи шифрования, счётчики пакетов и, если включено сжатие, общий контекст сжатия.
Здесь и прячется проблема. Метод zlib, который использует OpenSSH, построен на алгоритме DEFLATE, а тот опирается на LZ77. Кодировщик помнит окно последних 32 килобайт отправленных данных и, встретив строку длиной от 3 до 258 байт, которая уже была в окне, заменяет её короткой ссылкой назад. Такая ссылка занимает порядка двух-трёх байт, поэтому повтор ужимается сильно, а новая строка почти не ужимается. Сжатие в современном OpenSSH включается после аутентификации (метод
Шифрование прячет содержимое пакетов, но не их длину. На этом строится атака с выбранным открытым текстом. Допустим, в одном канале течёт секрет, например токен сессии, а другой канал позволяет злоумышленнику подмешивать собственные байты. Он подставляет догадку о начале токена. Если догадка совпала, кодировщик заменяет её ссылкой, и пакет получается короче. Если нет, пакет остаётся длиннее. Наблюдатель на пути трафика видит длины зашифрованных пакетов и читает по ним ответ "да" или "нет". Тот же принцип когда-то лёг в основу известных атак на сжатие в веб-протоколах, которые называют CRIME и BREACH. Исследование под названием "Crossing the Streams" показало, как перенести его на мультиплексированные каналы SSH, где словарь общий для всех.
Масштаб проблемы хорошо видно на арифметике. Если токен состоит из 32 символов алфавита в 64 знака, то перебор целиком - это 64 в 32-й степени, то есть 2 в 192-й, и о нём можно забыть. Но когда оракул длины подтверждает каждый символ по отдельности, хватает не более 64 догадок на позицию, то есть не более 2048 попыток на весь токен и около тысячи в среднем. В реальности попыток нужно больше из-за шума сети и выравнивания пакетов, однако разница между экспоненциальным и линейным ростом всё равно решает дело.
Выравнивание, кстати, работает не только как помеха. Пакеты SSH дополняются до границы блока шифра, обычно 8 или 16 байт, поэтому выигрыш в пять байт от совпавшей догадки виден не всегда, а только когда пересекает границу блока. Атакующий подгоняет длину своей вставки так, чтобы размер стоял ровно на границе, и тогда один совпавший символ перебрасывает пакет в следующий блок. Мелочь превращается в сигнал.
Схема общего и раздельного контекста выглядит так:
ДО (общий словарь на всё соединение)
канал 1: интерактивная сессия с секретом ---+
канал 2: сетевой запрос с чужим вводом -------+--> один LZ77-буфер --> шифрование --> сеть
канал 3: передача файла ---------------------+
длина пакета зависит от совпадений между каналами
ПОСЛЕ (сжатие на уровне приложения)
канал 1 --> сжатие потока 1 --+
канал 2 --> сжатие потока 2 --+--> шифрование --> сеть
канал 3 --> сжатие потока 3 --+
у каждого потока свой словарь, чужие байты в него не попадают
Документация проекта давно советовала не включать сжатие там, где одно соединение несёт и доверенный, и недоверенный трафик. Но рекомендация жила на страницах руководства, а конфигурации жили собственной жизнью. У клиента ssh по умолчанию Compression no, зато у sshd по умолчанию yes, то есть сервер соглашается на сжатие, как только клиент его попросил. Попросить можно ключом -C, директивой Compression yes в конфиге, а ещё незаметнее через обёртки: scp -C и sftp -C передают сжатие вниз, в ssh.
Что изменилось в 10.6? Разработчики не стали вырезать опцию, а убрали из ssh и sshd поиск повторов. Compression остаётся в конфигурации, но выгода от неё падает, и проект прямо предупреждает об этом среди потенциально несовместимых изменений. Читать это изменение стоит как исправление, а не как потерю: уходит именно тот выигрыш, который держался на общем словаре.
Сжатие на уровне приложения изолирует потоки лучше, чем общий словарь соединения
Что предлагают разработчики взамен? Сжимать данные до того, как они попадут в SSH. Принцип простой: у каждого потока тогда свой словарь, и соседний канал не может подмешать в него пробные строки. Если сравнивать с бытовой картиной, общий словарь - это одна записная книжка на всех пассажиров купе, где каждый видит, какие слова уже вписал сосед. Сжатие приложения - это личные блокноты: у каждого свой, и сосед в него не заглядывает. Заодно прикладное сжатие обычно эффективнее, потому что специализированные инструменты умеют больше, чем zlib внутри SSH.
Самый частый сценарий, где сжатие нужно, - передача каталога или дампа на другую машину. Вот как он выглядит без сжатия на уровне SSH:
tar -cf - /var/www | zstd -3 | ssh -o Compression=no backup@storage 'zstd -d | tar -xf - -C /srv/restore'
Здесь tar собирает каталог в поток, zstd сжимает его на уровне 3 (это уровень по умолчанию), ssh только шифрует и везёт, а на приёмной стороне операции повторяются в обратном порядке. Для синхронизации каталогов подходит похожая связка:
rsync -az -e "ssh -o Compression=no" /srv/data/ backup@storage:/srv/data/
Ключ -z включает сжатие самого rsync, которое живёт внутри его собственного потока данных, а сжатие SSH явно выключено. Во многих инструментах нужное уже встроено: pg_dump с форматом -Fc сжимает дамп сам, а git хранит и передаёт объекты в упакованном виде. Для таких задач флаг -C в скрипте давно превратился в привычку без пользы.
Прежде чем менять привычки, полезно замерить, чем один способ отличается от другого на своих данных:
time tar -cf - /var/log | ssh -o Compression=yes backup@storage 'cat > /dev/null'
time tar -cf - /var/log | zstd -3 | ssh -o Compression=no backup@storage 'cat > /dev/null'
На клиенте до 10.6 первая строка покажет старую картину, на 10.6 уже новую, и разница видна сразу. Есть и два ориентира для оценки. Файлы, которые уже сжаты (архивы, фотографии, видео, образы дисков с компрессией), не выигрывали от сжатия SSH и раньше, поэтому для них изменение незаметно. А на быстрых каналах порядка гигабита сжатие в SSH нередко вредило: одно ядро с zlib выдаёт по порядку величины десятки мегабайт в секунду, а гигабитный канал пропускает около 125 мегабайт в секунду, так что узким местом становится процессор.
У изоляции есть честная оговорка. Она работает, пока внутри одного сжатого потока не смешаны секрет и ввод, который контролирует недоверенная сторона. Если приложение само склеивает их в общий поток, утечка возможна уже внутри него, и SSH тут ни при чём. Принцип остаётся прежним: секреты и чужой ввод под одним словарём несовместимы.
Инвентаризация версий и пакетов с перенесёнными исправлениями вместо доверия одному номеру версии
Номер версии - первый и самый ненадёжный показатель. Дистрибутивы с долгой поддержкой редко поднимают OpenSSH до свежей версии, они переносят отдельные исправления в уже вышедший пакет. Сервер может честно показывать девятую версию и при этом содержать нужную правку, а может показывать ту же строку и не содержать её. Поэтому инвентарь собирается из двух столбцов: что сообщает программа и что записано в журнале изменений пакета.
Для локальной машины хватает нескольких команд:
ssh -V
dpkg -l openssh-server openssh-client | grep '^ii'
rpm -q openssh-server openssh-clients
apt changelog openssh-server | head -n 40
rpm -q --changelog openssh-server | head -n 40
С момента выхода релиза прошло всего несколько дней, поэтому в стабильные ветки дистрибутивов исправления приходят волнами. В журнале изменений стоит искать упоминания сжатия, GSSAPI и номеров уязвимостей, а в карточке узла вести отдельную отметку "исправление получено" или "ожидается". Для удалённой проверки без входа на машину пригодится строка с отладочным выводом:
ssh -v -o BatchMode=yes -o ConnectTimeout=5 host.example true 2>&1 | grep 'remote software version'
Она покажет баннер сервера, а у Debian и Ubuntu в баннере виден и суффикс ревизии пакета. Для всего списка машин можно собрать версию и ключевые параметры одним проходом:
for h in $(cat hosts.txt); do
echo "== $h"
ssh -o BatchMode=yes -o ConnectTimeout=5 "$h" \
'ssh -V 2>&1; sudo -n sshd -T | grep -iE "^(compression|gssapiauthentication) "'
done
Команда sshd -T печатает эффективную конфигурацию с именами в нижнем регистре, поэтому шаблон grep написан именно так. Одна тонкость: без параметра -C блоки Match в этом выводе не оцениваются. Чтобы увидеть значения для конкретного подключения, нужен вызов вроде sshd -T -C user=deploy,host=10.0.0.5,addr=10.0.0.5. Без этого администратор рискует любоваться глобальными настройками и не заметить, что для группы пользователей действует переопределение.
Отдельная строка в инвентаре нужна клиентам. Они прячутся в образах контейнеров, где ssh стоит ради git и rsync, в раннерах сборки, в рабочих станциях разработчиков и в jump-хостах. Серверный парк обычно известен хорошо, а клиентский разбросан по сотне мест, и именно клиент решает, запросить ли сжатие.
Проверка настроек Compression, GSSAPI и блоков Match в конфигурации клиентов и серверов
Первым делом стоит выяснить, где сжатие включено явно:
grep -RniE '^\s*Compression' /etc/ssh/ssh_config /etc/ssh/ssh_config.d /etc/ssh/sshd_config /etc/ssh/sshd_config.d ~/.ssh/config 2>/dev/null
ssh -G prod-db | grep -i '^compression'
Вторая команда показывает итоговое значение для конкретного узла с учётом всех Host-блоков, и именно ей стоит доверять больше, чем беглому чтению конфига. Пока часть парка работает на версиях до 10.6, разумно закрепить выключенное сжатие там, где в одном соединении смешан разный трафик:
Host *
Compression no
На стороне сервера та же директива в sshd_config закрывает вопрос независимо от желаний клиента: если сервер говорит "нет", сжатие не включится, даже если клиент запросил его ключом -C. Заодно полезно пройтись по скриптам и найти вызовы с -C, понимая, что поиск даст и ложные срабатывания вроде git -C, поэтому результат читается глазами.
С GSSAPI в релизе исправлены две родственные вещи. Первая: учётные данные GSSAPI теперь сохраняются только после успешной аутентификации. Раньше данные из неудавшейся попытки могли остаться в системе и оказаться доступными, если следом успешно проходил другой способ входа. Вторая: состояние GSSAPIAuthentication сбрасывается перед аутентификацией, чтобы след одной попытки не путался с другой. Это история про остатки, которые забыли подмести, и такие остатки обычно находят не сразу.
Проверка занимает минуту:
sshd -T | grep -i gssapi
grep -rni gssapi /etc/ssh/ssh_config /etc/ssh/ssh_config.d
В ряде дистрибутивов поставляемый конфиг включает GSSAPIAuthentication, даже если Kerberos в инфраструктуре не используется. Если он не нужен, лучше задать значение no явно, а не рассчитывать на умолчание, которое зависит от сборщика пакета.
Заодно имеет смысл просмотреть значение none в блоках Match. Некоторые параметры, включая AuthorizedPrincipalsFile, документированно принимали none как способ отключения, но внутри Match это слово читалось как имя файла. Администратор считал, что опция отключена, а служба честно искала файл с названием "none". Поиск делается так:
grep -nB3 -A3 -i 'none' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf
Если такие строки найдены внутри Match, итог лучше сверить через sshd -T -C. А владельцам конфигураций на ветках 10.4 и 10.5 стоит помнить, что PAMServiceName внутри Match в этих версиях перестал работать из-за случайной регрессии, и в 10.6 она исправлена.
Сценарии SFTP, удалённое копирование через scp и имена пользователей в командной строке, которые могут сломаться или стать безопаснее
В клиенте sftp усилена проверка путей, которые возвращает сервер. Раньше в ряде случаев сервер мог вернуть такие пути, что рекурсивное копирование записывало файлы за пределами целевого каталога. Для скриптов, которые забирают данные у внешних партнёров, подрядчиков и сторонних хранилищ, это самый ощутимый пункт релиза, ведь клиент доверяет серверу, которым не управляет. Найти такие места можно поиском рекурсивных команд в пакетных файлах:
grep -rnE '(^|[[:space:]])(get|reget)[[:space:]]+-[A-Za-z]*r' /opt /usr/local /etc 2>/dev/null
Рядом с обновлением клиента нужна простая гигиена: забирать данные в отдельный пустой каталог, запускать задачу от непривилегированной учётной записи, а после загрузки проверять результат командой find с ограничением по путям. Среди новинок есть и приятная мелочь. Флаг -p у команд mkdir и lmkdir создаёт каталоги по цепочке и не считает ошибкой уже существующий каталог, поэтому строка mkdir -p /srv/in/2026/10 делает пакетный файл короче и повторяемым.
С scp -R другая история. Эта опция запускает scp на удалённой машине для копирования между двумя узлами. Проект называет её хрупкой оптимизацией: она требует учётных данных на удалённом хосте и создаёт риск, если правила экранирования оболочки там отличаются от ожиданий клиента. В 10.6 опция ещё работает, но пишет предупреждение в стандартный поток ошибок, а в будущей версии будет игнорироваться, и копирование пойдёт через машину, где запущен scp. Ловить такие места удобно так:
grep -rn 'scp' /usr/local/bin /opt /etc/cron.d 2>/dev/null | grep -E -- ' -[A-Za-z]*R'
Предупреждение само по себе безобидно, но у него есть побочный эффект: задания cron отправляют вывод ошибок по почте, а мониторинг иногда считает любой вывод в stderr сбоем. Заменой служит режим копирования через локальную машину. Цена вопроса - трафик проходит через узел, на котором запущен scp, и для больших объёмов выгоднее выделенное хранилище или rsync от одного хоста к другому под отдельным ключом.
Имена пользователей получили новую проверку. Клиент ssh отказывается принимать в командной строке имена со знаками доллара и обратной косой черты. Причина понятна: если имя приходит из недоверенного источника, оно может превратиться в инъекцию в оболочке через директивы, которые запускают команды оболочки, например Match exec. Параметр User из конфигурационного файла ограничению не подлежит. Проект честно оговаривается: меры такого рода не бывают абсолютными из-за разнообразия оболочек, и по-прежнему не стоит отдавать командную строку ssh под управление недоверенного ввода.
Для аудита важны два места. Первое - автоматизация, где имя пользователя берётся из внешних данных: заявки, CMDB, переменные CI, ответы API. Второе - инфраструктура с доменными учётными записями, где имя иногда содержит обратную косую черту. Такие подключения придётся переносить в конфигурационный файл через директиву User и проверять результат командой ssh -G, чтобы увидеть имя, которое клиент реально применит. Поиск Match exec и подстановок имени пользователя делается строкой:
grep -rnE 'Match +exec|%[ru]' /etc/ssh/ssh_config /etc/ssh/ssh_config.d ~/.ssh/config 2>/dev/null
Каждое найденное место стоит прочитать: подставляются ли в команду токены %r или %u и откуда берётся их содержимое.
Экспериментальные гибридные ключи постквантовой подписи, параметры закрытых ключей и сертификаты с абсолютными датами
В 10.6 включён гибридный алгоритм подписи ssh-mldsa44-ed25519, который сочетает классическую подпись Ed25519 с постквантовой ML-DSA-44. Главный сюрприз для администраторов в названии: теперь оно без суффикса @openssh.com, который носила предыдущая экспериментальная версия. Ключи, созданные по старой схеме, несовместимы с новой и должны быть перегенерированы или удалены. Скорее всего, таких ключей в парке немного, ведь вариант был экспериментальным, но проверка занимает минуты, а сюрприз при отказе входа стоит дороже:
grep -rIl 'mldsa' /etc/ssh /root/.ssh /home/*/.ssh 2>/dev/null
ssh-add -L | grep -c mldsa
Поиск по тексту находит открытые ключи, authorized_keys, known_hosts и конфигурации с перечнями алгоритмов (HostKeyAlgorithms, PubkeyAcceptedAlgorithms). Закрытые файлы в формате OpenSSH хранят имя типа внутри base64, поэтому искать надёжнее по парным открытым файлам. Для найденного составляется отдельный список с колонками "владелец", "где используется", "заменён ли", а новые ключи создаются уже версией ssh-keygen 10.6 и раздаются вместо старых.
Рядом лежит изменение, о котором можно узнать по журналам. В sshd появился параметр WarnWeakCrypto, который раньше был только у клиента. Он включён по умолчанию и пишет в журнал, когда клиент использует схему согласования ключей без постквантовой стойкости. Сразу после обновления журналы вырастут, и в этом есть польза: получается готовый список старых клиентов, которым нужно обновление. Тишину можно купить выключением параметра, но она будет стоить информации.
Следующий пункт касается защиты закрытых ключей. Число раундов функции выработки ключа по умолчанию выросло с 24 до 32, причём рост линейный, как напоминает проект, а не экспоненциальный, как у bcrypt. Простая арифметика: 32 разделить на 24 даёт около 1,33, то есть разблокировка нового ключа займёт примерно на треть больше времени. Для человека, который вводит парольную фразу раз в день, разница незаметна, а для автоматики с сотнями загрузок ключей стоит проверить, не накопилась ли секунда-другая. Ключи, созданные раньше, остаются со старым числом раундов, и пересоздать их можно командой:
ssh-keygen -p -a 32 -f ~/.ssh/id_ed25519
Ещё одна деталь: введён потолок в миллион раундов при записи и загрузке ключа, чтобы служба, получившая ключ с абсурдным числом, всё же завершила разбор.
Наконец, ssh-keygen поправил обработку летнего и зимнего времени при преобразовании дат. Прежняя логика могла давать ошибку до плюс-минус часа, а в часовом поясе Antarctica/Troll даже до двух, и это приводило к сертификатам с неверным временем окончания. Для центров, которые выдают сертификаты с абсолютными датами в параметре -V, проверка делается так:
ssh-keygen -L -f id_ed25519-cert.pub
В выводе стоит сравнить строки Valid с задуманным интервалом. Календарь добавляет интригу: ближайший перевод часов в Европе приходится на 25 октября 2026 года, а в США на 1 ноября, так что у проверки есть вполне осязаемый срок.
Порядок обновления парка и график проверок при более частых выпусках
Команда OpenSSH сообщила, что некоторое время будет выпускать версии чаще. Причина в потоке отчётов о безопасности, часть из которых найдена с помощью ИИ-инструментов. Наблюдение разработчиков звучит трезво: уязвимости, обнаруженные ИИ, затем независимо находили и другие исследователи, а значит, те, кто не сообщает об ошибках проектам, способны найти их тоже. Вывод для эксплуатации прост: ежегодный аудит SSH больше не работает, нужен спокойный ритм, при котором проверка занимает часы, а не недели.
Порядок работ для парка укладывается в шесть шагов:
- Собрать инвентарь: версии клиента и сервера, источник пакета, роль машины, а также образы контейнеров и раннеры CI, где ssh стоит внутри;
- Выписать эффективные значения Compression, GSSAPIAuthentication и параметров со значением none в блоках Match через sshd -T -C;
- Заменить сжатие SSH сжатием приложения в скриптах резервного копирования и передачи данных, а Compression no закрепить в конфигурации там, где в одном соединении смешан разный трафик;
- Просмотреть скрипты с sftp -r, scp -R и вызовы ssh, где имя пользователя приходит из внешних данных, и переписать их под новое поведение;
- Составить отдельный список экспериментальных гибридных ключей и сертификатов с абсолютными датами, а перевыпуск провести до перевода часов 25 октября;
- Обновить сначала тестовую группу, затем остальные, сверить журналы sshd с новыми предупреждениями WarnWeakCrypto и закрепить повторную проверку раз в месяц.
Обновление приносит и несколько полезных мелочей, не связанных с защитой напрямую. Параметр AgentSocketPath задаёт, где sshd создаёт сокет агента ключей для удалённой сессии: в каталоге пользователя (user:.ssh/agent) или в общем каталоге (shared:/tmp), причём в общем сокет появляется внутри подкаталога со случайным именем. Параметр PubkeyAuthOptions max-pk-ok:nnn по умолчанию разрешает 6 проверок вида "подойдёт ли этот ключ" без списания попыток из MaxAuthTries. Это спасает тех, у кого в агенте лежит много ключей и сервер раньше обрывал соединение.
Сжатие в SSH оказалось редким случаем, когда ускорение превратилось в долг, который рано или поздно приходится возвращать. Платить его будет проще тому, у кого есть список машин, понятные команды проверки и привычка заглядывать в журнал изменений раз в месяц. Остальное сделает свежий пакет.