Hex package registry vulnerability
Posted 2019-01-29 14:13:52.780049
Recently I came across a vulnerability in the Hex package manager that would let a malicious or compromised mirror host modified versions of popular packages without detection by the client.
The issue, which affected both the Hex plugin for Mix and the Hex client built into Rebar3, has since been fixed. I hope everyone upgraded to Hex v0.19.0 and Rebar3 v3.8.0 by now.
In this post I will explain the background, the vulnerability, the potential impact and the fix.
Any use of third party software requires trust and due diligence. Adding a dependency to a project requires trusting not only the author of that code, but also the infrastructure involved in making the code available: the source code repository, the package manager, the package manager’s hosting and CDN providers, package repository mirrors, etc. Not only do we have to assume that these are not bad actors themselves, we also need to have trust in their ability to protect their systems from attackers.
Some elements in the chain above should never be modifying the content they are hosting or relaying. By adding a mechanism to detect unauthorized modifications we can reduce the level of trust we need to place in them, and reduce the risk of a supply chain attack. We can implement such mechanisms using checksums and digital signatures.
Hex package registry
For each package Hex maintains a ‘registry’, listing all releases (versions) and their properties. Among the properties is a checksum (a SHA256 hash, to be precise) that is calculated over the release contents. The registry itself is protected by a signature, generated with the Hex server’s private key.
When fetching a package, the Hex client first retrieves that package’s registry file from the Hex repository server or one of its mirrors, then verifies the signature using the server’s public key. If the signature is valid, the desired release is selected through dependency resolution and the associated file is retrieved. Before extracting the package, the downloaded file is checked against the expected checksum from the registry file.
If either the registry file or the package contents are corrupted or have been tampered with, these checks will fail and the installation is aborted. As long as the Hex server’s signing key is kept safe we can be sure the package we are installing is the same package that was uploaded to the server by the author. At least that’s the idea.
Now here’s the catch: until recently, an attacker did not necessarily need the Hex server’s private key to produce a package registry file with a valid signature. It turns out the attacker could trick the server into producing it.
Suppose someone wanted to publish a modified version of Certifi, the most popular package on hex.pm right now, and obviously very important to the security of applications and packages that depend on it. The attacker could just fetch the source code for Certifi from GitHub, make the desired changes and publish their version under a different name. The Hex server would generate and sign a registry file, but crucially this registry file would be in no way tied to the (modified) package name!
Now if the attacker had control over a Hex mirror, for example because they had previously set one up themselves and gotten people to use it, they could replace the real Certifi package files and registry with their own variants. Clients would try to retrieve the Certifi package from the mirror, but get the modified files. When verifying the signature and checksum all would seem well, but the attacker’s modifications could trigger arbitrary code to execute at compile time (
mix deps.compile) or runtime.
To avoid scrutiny by people noticing the modified package, e.g. on the Elixir Packages Twitter feed, the attacker could publish to a private package repository (‘organization’) instead. The package signature was also not tied to the organization name.
Essentially the Hex server was acting as a ‘signing oracle’: accepting user input (the attacker’s modified package) and signing it without constraining the context in which that signed data could be used.
The solution was to include the package and repository (organization) names in the package registry data structure and verifying the values in the client. Since these parameters are set by the server, signed registry files are tied to a specific package and organization, and malicious substitutions are detected.
If you are using digital signatures in your own applications, remember to be very careful when including user input in the data structures to be signed: constrain and validate the user input as much as possible, and ensure the user input is accompanied by fields under your control that define the scope in which the signature is valid.
The following CVEs were assigned (they do not appear to be included in the Mitre CVE DB yet):
- Hex client: CVE-2019-1000012
- Rebar3: CVE-2019-1000014