CVE-2024-45337

CVE-2024-45337

December 13, 2024· Vince Parker
Vince Parker
·Reading time: 6 minutes
This article was originally published on the Platform.sh blog.

Overview

On September 5th 2024, the engineering team at Platform.sh discovered a security vulnerability in the golang.org/x/crypto/ssh package. Upon investigating an unexpected Panic: runtime error: invalid memory address or nil pointer dereference message in our edge proxy, the engineers discovered our own misimplementation and common misuse of the PublicKeyCallback function. We quickly remediated and found no reason to believe this vulnerability was exploited before we patched.

Systems that implement this callback function incorrectly, end up with a vulnerability that allows an authorization bypass in Go’s x/crypto/ssh. Our analysis suggests that this issue is prevalent across multiple projects utilizing this (golang.org/x/crypto/ssh) package, leading to potentially severe security implications.

At the time of writing there are ~19k known importers of this ssh package.

While this is ultimately a bug in the usage of the package by end-users, we believe that the design of the PublicKeyCallback function and its documentation is contributing to the vulnerability, which has the potential to affect a broad spectrum of users in the wild. Given the broad impact potential we chose to disclose the vulnerability to the Go Security team.

Timeline

  • September 5th, 2024: Engineering discovered security auth bypass in x/crypto/ssh.
  • September 12th 2024: Reported findings to Go Security team and implemented mitigations.
  • October 2nd, 2024: Go security team reviewed our report and confirmed our findings.
  • December 6, 2024: Go security team made pre-announcement.
  • December 11, 2024: Go security team made public disclosure and released v0.31.0 of golang.org/x/crypto.

Context

The SSH-2 protocol, specified in RFC 4253, was designed in its current form back in 2006 as an evolution of the original SSH-1 protocol going back to 1995. Back in SSH-1 times, the client authentication was done as a challenge/response protocol outlined under SSH_AUTH_RSA on pages 11-12 here. This allowed clients to ask the server where a particular key had the potential to be accepted:

“The client sends SSH_CMSG_AUTH_RSA with public key modulus (n) as an argument. The server may respond immediately with SSH_SMSG_FAILURE if it does not permit authentication with this key. Otherwise it generates a challenge.” 1

The SSH-2 protocol changed this to a static signature based on data that is already known by the two parties, but kept a query mechanism in place as a way to improve performance and usability. Section-7 of RFC 4252:

“Private keys are often stored in an encrypted form at the client host, and the user must supply a passphrase before the signature can be generated. Even if they are not, the signing operation involves some expensive computation. To avoid unnecessary processing and user interaction, the following message is provided for querying whether authentication using the “publickey” method would be acceptable.2

Details

The golang.org/x/crypto/ssh package is an implementation of the SSH-2 protocol. The golang implementation hides the complexity of the “query that a key might be accepted / actually authenticate” dance under a single API.

PublicKeyCallback is called only once for a particular key, before the client has proven possession of the private key, via a cache implemented in server.go#L621. The comments in PublicKeyCallBack function below have critical implementation details that are easily overlooked.

server.go
// PublicKeyCallback, if non-nil, is called when a client
// offers a public key for authentication. It must return a nil error
// if the given public key can be used to authenticate the
// given user. For example, see CertChecker.Authenticate. A
// call to this function does not guarantee that the key
// offered is in fact used to authenticate. To record any data
// depending on the public key, store it inside a
// Permissions.Extensions entry.
PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)

The ease of misimplementation for this function was previously mentioned in issue #20094 from April 2017, and clarifying code comments were subsequently added.

Despite the description hinting that PublicKeyCallBack func needs to be implemented in a stateless way, what can happen, if the func is stateful, is a client can successfully query a public key that it doesn’t use. Where doing so only requires knowledge of the public key which, by nature of being public, is very know-able. For example, the public keys of all github users are public if you append .keys to the user url.

The flaw in this process occurs because the PublicKeyCallback is not called again after the client presents the key, allowing the attacker to gain access without possessing the private key for the queried key.

Relevance to Upsun & Platform.sh

Our SSH gateways allow our customers to access containers running on our infrastructure using standard SSH tools and are based on the open source golang.org/x/crypto/ssh package.

We favour open source technologies and enjoy contributing and supporting open source projects we love. Catching and addressing an issue like this is something our teams take great pride in. It genuinely feels awesome when you can make the tools you use better for everyone else.

Given the reach of this issue (19k importers) we believed the upstream Go team was best suited to handle the disclosure. We encourage others to also disclose responsibly and have confidence in their upstream maintainers. As developers ourselves we understand it can often be a thankless job, and unwarranted complications are not welcome. Eliminating unnecessary complexities for developers is the core our products are built on.

Before this devolves too much into a “puff piece,” if you relate with any of these sentiments please reach out!

Our recommendations

If you have a Go project depending on crypto/ssh, update the package to the newest version with:

go get -u golang.org/x/crypto/ssh

Then, if your code has a PublicKeyCallback method (implementing the ssh.ServerConfig interface), you will need to ensure that:

  • The callback must be stateless, or in other words, it must not have any side effects. As the comments on the interface suggest, return any state directly in the function as part of the ssh.Permissions object.
  • Do not assume that the public key is authenticated at this stage. Use the PostSuccessAuthCallback if you need to run code after the client has proved possession of the private key. This will be passed the same ssh.Permissions object.

Even if the PublicKeyCallback is not part of your own code, remember to check that you are not using any other packages that misimplement the same callback. If so, they will also need to be updated, fixed or removed.

The package maintainers were very understanding and professional as we communicated that users of the x/crypto/ssh package expect that the key used by the client for authentication (if successful) matches the one passed to the most recent call to PublicKeyCallback.

The maintainers’ cache fix should mitigate this vulnerability for the majority of implementations, unfortunately it cannot fix all.

Last updated on