> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Security vulnerability uncovered and patched in the golang.org/x/crypto /ssh package

> Misimplementation of PublicKeyCallback leads to authorization bypass in Go's x/crypto/sshPlatform.

export const PostMeta = ({data = {}}) => {
  const {author, date, image} = data;
  const authors = Array.isArray(author) ? author : author ? [author] : [];
  const resolveAuthor = slug => {
    const entry = AUTHOR_MAP[slug] || ({});
    const name = entry.name || slug;
    const github = entry.github || null;
    const linkedin = entry.linkedin || null;
    const url = github ? `https://github.com/${github}` : linkedin || null;
    const avatarUrl = github ? `https://github.com/${github}.png?size=64` : null;
    return {
      name,
      url,
      avatarUrl
    };
  };
  const formattedDate = date ? new Date(date).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }) : null;
  if (!image && authors.length === 0 && !formattedDate) return null;
  const AUTHOR_MAP = {
    "aaron-collier": {
      "name": "Aaron Collier"
    },
    "aaron-dudenhofer": {
      "name": "Aaron Dudenhofer"
    },
    "aaron-porter": {
      "name": "Aaron Porter"
    },
    "adriaan-odendaal": {
      "name": "Adriaan Odendaal"
    },
    "ajmal": {
      "name": "Ajmal Siddiqui"
    },
    "akalipetis": {
      "name": "Antonis Kalipetis"
    },
    "alexander-varwijk": {
      "name": "Alexander Varwijk"
    },
    "alicia-bevilacqua": {
      "name": "Alicia Bevilacqua"
    },
    "amelie-deguerry": {
      "name": "Amelie Deguerry"
    },
    "anacidre": {
      "name": "Ana Cidre",
      "linkedin": "https://www.linkedin.com/in/ana-cidre"
    },
    "andoni": {
      "name": "Andoni Auzmendi"
    },
    "andrei-taranu": {
      "name": "Andrei (Alex) Taranu",
      "linkedin": "https://www.linkedin.com/in/andrei-alex-taranu/"
    },
    "andrew-baxter": {
      "name": "Andrew Baxter"
    },
    "andrew-melck": {
      "name": "Andrew Melck"
    },
    "antoine-crochet-damais": {
      "name": "Antoine Crochet Damais"
    },
    "augustin-delaporte": {
      "name": "Augustin Delaporte",
      "linkedin": "https://www.linkedin.com/in/augustindelaporte/"
    },
    "branislav-bujisic": {
      "name": "Branislav Bujisic"
    },
    "carl-smith": {
      "name": "Carl Smith"
    },
    "caroline-leroy": {
      "name": "Caroline Leroy"
    },
    "cati-mayer": {
      "name": "Cati Mayer"
    },
    "catplat": {
      "name": "C Trinkwon"
    },
    "ceelolulu": {
      "name": "Celeste van der Watt"
    },
    "chadwcarlson": {
      "name": "Chad Carlson",
      "github": "chadwcarlson",
      "linkedin": "https://www.linkedin.com/in/chadwcarlson"
    },
    "chris-ward": {
      "name": "Chris Ward"
    },
    "chris-yates": {
      "name": "Chris Yates"
    },
    "christian-sieber": {
      "name": "Christian Sieber"
    },
    "christopher-lockheardt": {
      "name": "Christopher Lockheardt"
    },
    "christopher-skene": {
      "name": "Christopher Skene"
    },
    "chuck-morgan": {
      "name": "Chuck Morgan"
    },
    "corey-dockendorf": {
      "name": "Corey Dockendorf"
    },
    "crell": {
      "name": "Crell"
    },
    "damz": {
      "name": "Damz"
    },
    "dan-morrison": {
      "name": "Dan Morrison"
    },
    "davidbonachera": {
      "name": "David Bonachera",
      "github": "davidbonachera",
      "linkedin": "https://www.linkedin.com/in/davidbonachera"
    },
    "dereliahmet1": {
      "name": "Ahmet Faruk Dereli"
    },
    "devicezero": {
      "name": "Jonas Kröger",
      "github": "devicezero",
      "linkedin": "https://www.linkedin.com/in/jonaskroeger/"
    },
    "doug-goldberg": {
      "name": "Doug Goldberg"
    },
    "duncan-naves": {
      "name": "Duncan Naves",
      "github": "duncannaves",
      "linkedin": "https://www.linkedin.com/in/duncan-naves-a94423aa"
    },
    "erika-bustamante": {
      "name": "Erika Bustamante"
    },
    "fabpot": {
      "name": "Fabien Potencier"
    },
    "flovntp": {
      "name": "Florent Huck",
      "github": "flovntp",
      "linkedin": "https://www.linkedin.com/in/florenthuck"
    },
    "fred-plais": {
      "name": "Fred Plais"
    },
    "gauthier-garnier": {
      "name": "Gauthier Garnier"
    },
    "gilzow": {
      "name": "Paul Gilzow"
    },
    "gmoigneu": {
      "name": "Guillaume Moigneu",
      "github": "gmoigneu",
      "linkedin": "https://www.linkedin.com/in/guillaumemoigneu/"
    },
    "gregqualls": {
      "name": "Greg Qualls"
    },
    "guguss": {
      "name": "Augustin Delaporte"
    },
    "haylee-millar": {
      "name": "Haylee Millar"
    },
    "ivana-kotur": {
      "name": "Ivana Kotur"
    },
    "jackrabbithanna": {
      "name": "Mark Hanna"
    },
    "jared-wright": {
      "name": "Jared Wright",
      "github": "jww-sh",
      "linkedin": "https://www.linkedin.com/in/jaredwaynewright"
    },
    "jessica-orozco": {
      "name": "Jessica Orozco"
    },
    "joey-stanford": {
      "name": "Joey Stanford"
    },
    "john-grubb": {
      "name": "John Grubb"
    },
    "jonas-kruger": {
      "name": "Jonas Kruger"
    },
    "kathryn-frazer": {
      "name": "Kathryn Frazer"
    },
    "kemiojo": {
      "name": "Kemi Elizabeth Ojogbede"
    },
    "kieronsambrook-smith": {
      "name": "Kieronsambrook Smith"
    },
    "laurent-arnoud": {
      "name": "Laurent Arnoud"
    },
    "letoya-boyne": {
      "name": "Letoya Boyne"
    },
    "lolautruche": {
      "name": "Jérôme Vieilledent"
    },
    "lyly-lepinay": {
      "name": "Lyly Lepinay"
    },
    "manauwar-alam": {
      "name": "Manauwar Alam"
    },
    "marc-antoine-porri": {
      "name": "Marc Antoine Porri"
    },
    "maria-antinkaapo": {
      "name": "Maria Antinkaapo"
    },
    "maria-de-anton": {
      "name": "Maria De Anton"
    },
    "mark-dorison": {
      "name": "Mark Dorison"
    },
    "markus-hausammann": {
      "name": "Markus Hausammann"
    },
    "mary-thomas": {
      "name": "Mary Thomas"
    },
    "mathias-bolt-lesniak": {
      "name": "Mathias Bolt Lesniak"
    },
    "mathieu-strauch": {
      "name": "Mathieu Strauch"
    },
    "matthias-van-woensel": {
      "name": "Matthias Van Woensel",
      "linkedin": "https://www.linkedin.com/in/matthias-van-woensel-267a069"
    },
    "michael-sharp": {
      "name": "Michael Sharp"
    },
    "mupsi": {
      "name": "Marine Gandy"
    },
    "natalie-harper": {
      "name": "Natalie Harper"
    },
    "ngommenginger": {
      "name": "Nicolas Gommenginger",
      "linkedin": "https://www.linkedin.com/in/nicolas-gommenginger"
    },
    "nicholas-bennison": {
      "name": "Nicholas Bennison"
    },
    "nicholas-vahalik": {
      "name": "Nicholas Vahalik"
    },
    "nick-hardiman": {
      "name": "Nick Hardiman"
    },
    "nickanderegg": {
      "name": "Nickanderegg"
    },
    "nicolas-grekas": {
      "name": "Nicolas Grekas",
      "github": "nicolas-grekas",
      "linkedin": "https://www.linkedin.com/in/nicolasgrekas/"
    },
    "niti-malwade": {
      "name": "Niti Malwade"
    },
    "opensocialteam": {
      "name": "Opensocialteam"
    },
    "ori-pekelman": {
      "name": "Ori Pekelman"
    },
    "otavio-santana": {
      "name": "Otavio Santana"
    },
    "palwandi": {
      "name": "Pawan Alwandi",
      "github": "pawpy",
      "linkedin": "https://www.linkedin.com/in/pawanalwandi"
    },
    "patrick-boest": {
      "name": "Patrick Boest"
    },
    "patrick-dawkins": {
      "name": "Patrick Dawkins",
      "github": "pjcdawkins",
      "linkedin": "https://www.linkedin.com/in/patrickdawkins"
    },
    "patrick-klima": {
      "name": "Patrick Klima"
    },
    "pjcdawkins": {
      "name": "Pjcdawkins"
    },
    "prineet-kaurbhurji": {
      "name": "Prineet Kaurbhurji"
    },
    "quentin-sinig": {
      "name": "Quentin Sinig"
    },
    "ralt": {
      "name": "Florian Margaine",
      "github": "ralt",
      "linkedin": "https://www.linkedin.com/in/florian-margaine-43971136"
    },
    "ramanathanramakrishnamurthy": {
      "name": "Ramanathanramakrishnamurthy"
    },
    "remi-lejeune": {
      "name": "Rémi Lejeune"
    },
    "ribel": {
      "name": "Taras Kruts"
    },
    "robert-douglass": {
      "name": "Robert Douglass"
    },
    "rudy-weber": {
      "name": "Rudy Weber"
    },
    "ryan-hicks": {
      "name": "Ryan Hicks"
    },
    "sabri-helal": {
      "name": "Sabri Helal"
    },
    "savannah-bergeron": {
      "name": "Savannah Bergeron"
    },
    "shannon-vettes": {
      "name": "Shannon Vettes"
    },
    "shawn-ogasawara": {
      "name": "Shawn Ogasawara",
      "linkedin": "https://www.linkedin.com/in/shawn-ogasawara-83a9a0/"
    },
    "shawna-spoor": {
      "name": "Shawna Spoor"
    },
    "shedrack-akintayo": {
      "name": "Shedrack Akintayo"
    },
    "simon-ruggier": {
      "name": "Simon Ruggier"
    },
    "sophie-van-der-kindere": {
      "name": "Sophie Van Der Kindere"
    },
    "stefanos-thampis": {
      "name": "Stefanos Thampis"
    },
    "stephen-weinberg": {
      "name": "Stephen Weinberg"
    },
    "sukhman-virk": {
      "name": "Sukhman Virk"
    },
    "sumaira-nazir": {
      "name": "Sumaira Nazir"
    },
    "sumer": {
      "name": "Sümer Cip"
    },
    "syed-raza": {
      "name": "Syed Raza"
    },
    "tamara-bacchia": {
      "name": "Tamara Bacchia"
    },
    "tara-arnold": {
      "name": "Tara Arnold"
    },
    "theosakamg": {
      "name": "Mickael Gaillard",
      "github": "theosakamg"
    },
    "thomasdiluccio": {
      "name": "Thomas di Luccio"
    },
    "tim-anderson": {
      "name": "Tim Anderson"
    },
    "tom-helmer-hansen": {
      "name": "Tom Helmer Hansen"
    },
    "tylermills": {
      "name": "Tyler Mills"
    },
    "upsun": {
      "name": "Upsun"
    },
    "veronika-tolkachova": {
      "name": "Veronika Tolkachova",
      "linkedin": "https://www.linkedin.com/in/veronika-tolkachova-169167a2"
    },
    "vince-parker": {
      "name": "Vince Parker"
    },
    "vinnie-russo": {
      "name": "Vincenzo Russo"
    },
    "vrobert78": {
      "name": "Vincent Robert",
      "github": "vrobert78",
      "linkedin": "https://www.linkedin.com/in/vincent-robert-498a883"
    },
    "yuriy-babenko": {
      "name": "Yuriy Babenko"
    },
    "yuriy-gerasimov": {
      "name": "Yuriy Gerasimov"
    }
  };
  return <div className="post-meta">
      {(authors.length > 0 || formattedDate) && <div className="post-meta-info">
          {authors.length > 0 && <div className="post-meta-authors">
              {authors.map(slug => {
    const {name, url, avatarUrl} = resolveAuthor(slug);
    const inner = <>
                    {avatarUrl && <img src={avatarUrl} alt={name} className="post-meta-avatar" />}
                    <span className="post-meta-author-name">{name}</span>
                  </>;
    return url ? <a key={slug} href={url} target="_blank" rel="noopener noreferrer" className="post-meta-author">
                    {inner}
                  </a> : <span key={slug} className="post-meta-author">{inner}</span>;
  })}
            </div>}
          {authors.length > 0 && formattedDate && <span className="post-meta-separator" aria-hidden="true">·</span>}
          {formattedDate && <span className="post-meta-date">{formattedDate}</span>}
        </div>}
      {image && <img src={image} alt="" className="post-meta-image" aria-hidden="true" />}
    </div>;
};

<PostMeta data={{ author: ["vince-parker"], date: "2024-12-13T10:36:25.747Z", image: "/images/posts/unknown/uncovered-and-patched-golang-vunerability/uncovered-and-patched-golang-vunerability.webp" }} />

<Tip>
  This post was originally published on the Platform.sh blog and reflects information from the time of publication.
</Tip>

### Misimplementation of PublicKeyCallback leads to authorization bypass in Go's x/crypto/ssh

[Platform.sh](http://platform.sh) teams are always striving to ensure a safe space for all developers within our product. And this consistent diligence led to the Platform.sh Engineering team discovering a security vulnerability in the [golang.org/x/crypto/ssh package](https://pkg.go.dev/golang.org/x/crypto/ssh) on 5 September 2024.

Upon investigating an unexpected *Panic: runtime error: invalid memory address or nil pointer dereference* message in our edge proxy, the engineers discovered a misimplementation of the *PublicKeyCallback* function. A quick remediation was carried out, and no evidence was found to believe this vulnerability had been exploited before the patch was implemented.

Systems that implement this callback function incorrectly end up with a vulnerability that allows an authorization bypass in [Go's](https://go.dev/) *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](https://pkg.go.dev/golang.org/x/crypto/ssh?tab=importedby).

While this is ultimately a bug in the usage of the package by end-users, the design of the *PublicKeyCallback* function and its documentation contribute to the vulnerability, potentially affecting a broad spectrum of users in the wild. Given the broad impact potential, the Platform.sh Engineering team disclosed the vulnerability to the [Go Security team](https://go.dev/doc/security/policy).

### How the event unfolded

* **5 September 2024:** Platform.sh Engineering team discovered security auth bypass in x/crypto/ssh.
* **12 September 2024:** Reported Findings to the Go Security team and implemented mitigations.
* **2 October 2024:** Go security team reviewed our report and confirmed our findings.
* **6 December 2024:** The Go Security team pre-announced the vulnerability as [CVE-2024-45337](https://nvd.nist.gov/vuln/detail/CVE-2024-45337).
* **12 December 2024:** Go security team [made public disclosure](https://groups.google.com/g/golang-announce/c/-nPEi39gI4Q) and released v0.31.0 of [golang.org/x/crypto](http://golang.org/x/crypto)

### Why this matters

The ssh-2 protocol, specified in[ RFC 4253](https://datatracker.ietf.org/doc/html/rfc4253), was designed in its current form back in 2006 as an evolution of the original ssh-1 protocol from 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](https://www.ietf.org/archive/id/draft-ylonen-ssh-protocol-00.txt). 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."*

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.[ Secion-7 of RFC 4252](https://datatracker.ietf.org/doc/html/rfc4252#section-7):

> *"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."*

### Let’s start at the beginning

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[ the server. Go #L621](https://github.com/golang/crypto/blob/c9da6b9a4008902aae7c754e8f01d42e2d2cf205/ssh/server.go#L621). The comments in [the PublicKeyCallBack func](https://github.com/golang/crypto/blob/c9da6b9a4008902aae7c754e8f01d42e2d2cf205/ssh/server.go#L98) below have critical implementation details that are easily overlooked.

```Javascript theme={null}
// 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 mentioned in[ issue #20094 from April 2017](https://github.com/golang/go/issues/20094), and clarifying code comments were subsequently[ added](https://go-review.googlesource.com/c/crypto/+/45311/7/ssh/server.go#69).

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 knowable. For example, the public keys of all GitHub users are public if you append *.keys* to the user URL.

> ***Here are Damien Tournoud, Platform.sh CTO's, public keys for your enjoyment:*** [***GitHub Public Keys***](https://github.com/damz.keys)

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.

### How this aligns with Platform.sh and Upsun

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.

Platform.sh favors open source technologies and enjoys [contributing to and supporting open source projects it loves](https://platform.sh/community/open-source/). We take great pride in catching and addressing an issue like this. It genuinely feels awesome when you can make the tools you use better for everyone else.

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

### What we recommend

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**.

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 must also 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.
