
Go proof-of-concept demonstrating CVE-2026-46595 in golang.org/x/crypto/ssh, using symbol inspection of stripped binaries and image scans to verify vulnerable NewServerConn usage.
https://www.cve.org/CVERecord?id=CVE-2026-46595 is https://pkg.go.dev/vuln/GO-2026-5023 a vulnerability in golang.org/x/crypto/ssh NewServerConn symbol as noted in the seocnd link.
This repo contains a simple program which demonstrates a positive vulnerability check.
A minimal golang.org/x/crypto/ssh server pinned to x/crypto v0.51.0 (< v0.52.0),
used to demonstrate that go tool nm on unstripped binaries provides both positive and
negative check
go mod tidy
go build ./...
Since we know the vulnerable symbol check for it. Right now it's unstripped binary but we'll strip it to remove symbol tables and prove that the vulnerable symbol remains in the output.
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
Now, lets check binaries shipped in an OpenShift payload image.
Extract /usr/bin out of the image. --only-files is required, otherwise oc image extract
aborts partway through the layer when it hits a symlink such as /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
For this digest that is the 4.21 cluster-config-operator image:
-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
See which version of x/crypto it was built against:
$ 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 is below every fixed version, so the module-version scanners flag it. Now apply the symbol check. Besides GO-2026-5023 there are other golang.org/x/crypto vulnerabilities fixed in v0.55.0 and v0.56.0, so check all of their vulnerable symbols at once:
GO-2026-5023 => ssh.NewServerConn GO-2026-6303 => ssh.NewServerConn again GO-2026-6354 => ssh.Dial ssh.NewClientConn ssh.NewControlClientConn ssh.NewServerConn GO-2026-6355 => ssh.Dial ssh.NewClientConn ssh.NewControlClientConn ssh.NewServerConn
The tests-ext binary ships gzipped, so decompress it first and check both:
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)'
No output, yay no vulnerability! Sanity check that the grep is not silent for the boring reason of there being no symbols to match against:
$ 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
So x/crypto is genuinely linked in, the linker just dropped all of x/crypto/ssh because
nothing reaches it.
This should be repeatable across the other payload images; the only per-image variation is
where the binaries live and whether they are compressed like the -tests-ext.gz above.
Note that test binaries such as *-tests-ext are shipped in the images even though they are
not on any execution path. Where they do pull in x/crypto/ssh (the kubernetes k8s-tests-ext
links ssh.NewClientConn for its e2e SSH helpers) it is still off the execution path of the
running component. Regardless we will work to remove test binaries from the images because they
make them larger and slow things like image pulls down.
The following was generated by Claude when creating the positive test case binaries, not necessary for the proof but I've left it in.
This is not necessary for proving the vulnerability exists, the above already demonstrate that but feel free to do this as well.
go run . -addr 127.0.0.1:2222 -user demo -password demo
Then, in another terminal:
ssh -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null [email protected]
Password is demo. The session prints a greeting and echoes input back.
A fresh ed25519 host key is generated on every start, so nothing is written to disk.
At v0.51.0, govulncheck reports 10 reachable vulnerabilities in
golang.org/x/crypto/ssh, all traced through main.go →
ssh.NewServerConn (GO-2026-5018 also traces through ssh.NewSignerFromKey).
go get golang.org/x/[email protected]
go mod tidy
govulncheck ./...
v0.52.0 clears most of them, but three need newer releases: GO-2026-6303 is fixed in v0.55.0, and GO-2026-6354 / GO-2026-6355 in v0.56.0. Scanning at v0.56.0 should come back clean.