Go proof-of-concept, демонстрирующий CVE-2026-46595 в golang.org/x/crypto/ssh, использующий инспекцию символов stripped-бинарников и сканирование образов для проверки уязвимого использования NewServerConn.
https://www.cve.org/CVERecord?id=CVE-2026-46595 — это https://pkg.go.dev/vuln/GO-2026-5023 уязвимость в символе golang.org/x/crypto/ssh NewServerConn, как указано во второй ссылке.
Этот репозиторий содержит простую программу, демонстрирующую положительную проверку уязвимости.
Минимальный сервер на golang.org/x/crypto/ssh, зафиксированный на x/crypto v0.51.0 (< v0.52.0),
используется для демонстрации того, что go tool nm на нестрипнутых бинарниках даёт как положительную, так и
отрицательную проверку
go mod tidy
go build ./...
Поскольку мы знаем уязвимый символ, проверяем его. Сейчас это нестрипнутый бинарник, но мы его стрипнем, чтобы удалить таблицы символов и доказать, что уязвимый символ остаётся в выводе.
file cve-2026-46595-proof
cve-2026-46595-proof: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, Go BuildID=kUaZmGWICI6Buw0K_I7t/L4jXRd3fy2BJy8zNlSYd/lB1_6wYHZLaOLsG0tE26/-FieQlhThdl7AkRj7rl0, BuildID[sha1]=c7ac1dd881dfcaef88a2dd180a8ccb3c5e751e32, with debug_info, not stripped
strings cve-2026-46595-proof | grep golang.org/x/crypto/ssh.NewServerConn
golang.org/x/crypto/ssh.NewServerConn
# ok, now strip it and repeat
strip cve-2026-46595-proof
file cve-2026-46595-proof
cve-2026-46595-proof: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, Go BuildID=kUaZmGWICI6Buw0K_I7t/L4jXRd3fy2BJy8zNlSYd/lB1_6wYHZLaOLsG0tE26/-FieQlhThdl7AkRj7rl0, BuildID[sha1]=c7ac1dd881dfcaef88a2dd180a8ccb3c5e751e32, stripped
strings cve-2026-46595-proof | grep -E 'ssh\.(Dial|NewServerConn|NewClientConn|NewControlClientConn)'
golang.org/x/crypto/ssh.NewServerConn
Теперь проверим бинарники, поставляемые в образе payload OpenShift.
Извлечём /usr/bin из образа. --only-files обязателен, иначе oc image extract
прерывается на середине слоя, когда встречает символическую ссылку, такую как /usr/bin/ld.so.
IMG=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:b4dde8227fb99134f71b50f0707eb7f695cfade43b017da3cbc486c2db854fe1
mkdir -p /tmp/cveimg
oc image extract "$IMG" --path /usr/bin/:/tmp/cveimg/ --only-files
ls -laS /tmp/cveimg | head
Для этого дайджеста это образ 4.21 cluster-config-operator:
-rw-r-----. 1 sdodson sdodson 117899168 Feb 24 2026 cluster-config-operator
-rw-r-----. 1 sdodson sdodson 10639741 Feb 24 2026 cluster-config-operator-tests-ext.gz
Посмотрим, против какой версии x/crypto он был собран:
$ go version -m /tmp/cveimg/cluster-config-operator | grep -E 'go1|x/crypto'
/tmp/cveimg/cluster-config-operator: go1.24.13 (Red Hat 1.24.13-1.el9_6) X:strictfipsruntime
dep golang.org/x/crypto v0.40.0
v0.40.0 ниже любой исправленной версии, поэтому сканеры версий модулей её отмечают. Теперь применим проверку символов. Помимо GO-2026-5023 есть другие уязвимости golang.org/x/crypto, исправленные в v0.55.0 и v0.56.0, поэтому проверим все их уязвимые символы сразу:
GO-2026-5023 => ssh.NewServerConn GO-2026-6303 => ssh.NewServerConn снова GO-2026-6354 => ssh.Dial ssh.NewClientConn ssh.NewControlClientConn ssh.NewServerConn GO-2026-6355 => ssh.Dial ssh.NewClientConn ssh.NewControlClientConn ssh.NewServerConn
Бинарник tests-ext поставляется в gzip-сжатом виде, поэтому сначала распакуем его и проверим оба:
gzip -dc /tmp/cveimg/cluster-config-operator-tests-ext.gz > /tmp/cveimg/cluster-config-operator-tests-ext
strings /tmp/cveimg/cluster-config-operator{,-tests-ext} \
| grep -E 'ssh\.(Dial|NewServerConn|NewClientConn|NewControlClientConn)'
Вывода нет, ура, уязвимости нет! Проверим, что grep молчит не по скучной причине отсутствия символов для сопоставления:
$ go tool nm /tmp/cveimg/cluster-config-operator | wc -l
150240
$ go tool nm /tmp/cveimg/cluster-config-operator | grep -c golang.org/x/crypto/ssh
0
$ go tool nm /tmp/cveimg/cluster-config-operator-tests-ext | wc -l
24607
$ go tool nm /tmp/cveimg/cluster-config-operator-tests-ext | grep -c golang.org/x/crypto
39
Итак, x/crypto действительно слинкован, просто линкер выбросил весь x/crypto/ssh, потому что
ничто до него не доходит.
Это должно воспроизводиться и на других payload-образах; единственное различие между образами —
где лежат бинарники и сжаты ли они, как -tests-ext.gz выше.
Обратите внимание, что тестовые бинарники, такие как *-tests-ext, поставляются в образах, хотя они
не находятся ни на каком пути исполнения. Там, где они всё же подтягивают x/crypto/ssh (kubernetes k8s-tests-ext
линкует ssh.NewClientConn для своих e2e SSH-хелперов), это всё равно вне пути исполнения
работающего компонента. Тем не менее мы будем работать над удалением тестовых бинарников из образов, потому что они
увеличивают их размер и замедляют такие вещи, как загрузка образов.
Следующее было сгенерировано Claude при создании бинарников положительного тест-кейса, не обязательно для доказательства, но я оставил это здесь.
Это не обязательно для доказательства существования уязвимости, выше уже показано это, но можете сделать и это.
go run . -addr 127.0.0.1:2222 -user demo -password demo
Затем в другом терминале:
ssh -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null [email protected]
Пароль — demo. Сессия выводит приветствие и эхо-возвращает ввод.
Свежий ключ хоста ed25519 генерируется при каждом запуске, поэтому на диск ничего не записывается.
На v0.51.0 govulncheck сообщает о 10 достижимых уязвимостях в
golang.org/x/crypto/ssh, все прослеживаются через main.go →
ssh.NewServerConn (GO-2026-5018 также прослеживается через ssh.NewSignerFromKey).
go get golang.org/x/[email protected]
go mod tidy
govulncheck ./...
v0.52.0 устраняет большинство из них, но три требуют более новых релизов: GO-2026-6303 исправлена в v0.55.0, а GO-2026-6354 / GO-2026-6355 в v0.56.0. Сканирование на v0.56.0 должно вернуться чистым.