Prova de conceito em Go que demonstra a CVE-2026-46595 em golang.org/x/crypto/ssh, utilizando inspeção de símbolos de binários removidos e varreduras de imagens para verificar o uso vulnerável de NewServerConn.
https://www.cve.org/CVERecord?id=CVE-2026-46595 é https://pkg.go.dev/vuln/GO-2026-5023 uma vulnerabilidade no símbolo NewServerConn de golang.org/x/crypto/ssh, conforme observado no segundo link.
Este repositório contém um programa simples que demonstra uma verificação positiva de vulnerabilidade.
Um servidor mínimo de golang.org/x/crypto/ssh fixado em x/crypto v0.51.0 (< v0.52.0),
usado para demonstrar que go tool nm em binários não removidos (unstripped) fornece tanto verificação
positiva quanto negativa
go mod tidy
go build ./...
Como conhecemos o símbolo vulnerável, verificamos por ele. No momento é um binário não removido (unstripped) mas vamos removê-lo (strip) para eliminar as tabelas de símbolos e provar que o símbolo vulnerável permanece na saída.
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, agora remova os símbolos e repita
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
Agora, vamos verificar os binários enviados em uma imagem de payload do OpenShift.
Extraia /usr/bin da imagem. --only-files é obrigatório, caso contrário oc image extract
aborta no meio da camada quando encontra um link simbólico como /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
Para este digest, essa é a imagem cluster-config-operator da versão 4.21:
-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
Veja contra qual versão de x/crypto ele foi compilado:
$ 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 está abaixo de todas as versões corrigidas, então os scanners de versão de módulo o sinalizam. Agora aplique a verificação de símbolos. Além do GO-2026-5023, há outras vulnerabilidades de golang.org/x/crypto corrigidas em v0.55.0 e v0.56.0, então verifique todos os seus símbolos vulneráveis de uma vez:
GO-2026-5023 => ssh.NewServerConn GO-2026-6303 => ssh.NewServerConn novamente GO-2026-6354 => ssh.Dial ssh.NewClientConn ssh.NewControlClientConn ssh.NewServerConn GO-2026-6355 => ssh.Dial ssh.NewClientConn ssh.NewControlClientConn ssh.NewServerConn
O binário tests-ext é enviado comprimido com gzip, então descomprima-o primeiro e verifique ambos:
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)'
Sem saída, eba, sem vulnerabilidade! Verifique que o grep não está silencioso pelo motivo trivial de não haver símbolos para corresponder:
$ 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
Então x/crypto está genuinamente vinculado, o linker apenas descartou todo o x/crypto/ssh porque
nada o alcança.
Isso deve ser reproduzível nas outras imagens de payload; a única variação por imagem é
onde os binários residem e se eles estão comprimidos como o -tests-ext.gz acima.
Observe que binários de teste como *-tests-ext são enviados nas imagens mesmo que não estejam
em nenhum caminho de execução. Onde eles trazem x/crypto/ssh (o k8s-tests-ext do kubernetes
vincula ssh.NewClientConn para seus auxiliares de SSH e2e) isso ainda está fora do
caminho de execução do componente em execução. Independentemente disso, trabalharemos para remover binários de teste das imagens porque eles
as tornam maiores e tornam coisas como pulls de imagem mais lentas.
O seguinte foi gerado pelo Claude ao criar os binários de caso de teste positivo, não é necessário para a prova, mas eu o deixei aqui.
Isso não é necessário para provar que a vulnerabilidade existe, o acima já demonstra isso, mas sinta-se à vontade para fazer isso também.
go run . -addr 127.0.0.1:2222 -user demo -password demo
Então, em outro terminal:
ssh -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null [email protected]
A senha é demo. A sessão imprime uma saudação e ecoa a entrada de volta.
Uma nova chave de host ed25519 é gerada a cada inicialização, então nada é escrito no disco.
Na v0.51.0, o govulncheck reporta 10 vulnerabilidades alcançáveis em
golang.org/x/crypto/ssh, todas rastreadas através de main.go →
ssh.NewServerConn (GO-2026-5018 também rastreia através de ssh.NewSignerFromKey).
go get golang.org/x/[email protected]
go mod tidy
govulncheck ./...
A v0.52.0 elimina a maioria delas, mas três precisam de versões mais recentes: GO-2026-6303 é corrigida na v0.55.0, e GO-2026-6354 / GO-2026-6355 na v0.56.0. Escanear na v0.56.0 deve retornar limpo.