Mar 1, 2026·5 min read·9 visits
Vulnerable Erlang/Elixir build tools deserialize untrusted API responses using unsafe methods. An attacker controlling a package mirror or network position can inject malicious Erlang terms, causing VM crashes (DoS) or executing arbitrary code.
A critical unsafe deserialization vulnerability exists in `hex_core`, the reference implementation for the Hex package manager API, affecting downstream tools like `hex` and `rebar3`. The flaw stems from the use of the unsafe `binary_to_term/1` function when processing HTTP response bodies, allowing attackers to trigger Denial of Service via atom table exhaustion or potentially achieve Remote Code Execution through object injection.
The vulnerability resides in hex_core, a foundational library used by the Erlang and Elixir ecosystems to interact with the Hex package registry. Specifically, the library failed to validate serialized Erlang terms received from the Hex API before decoding them. This affects hex (the Mix package manager) and rebar3 (the Erlang build tool), meaning the vulnerability extends to the majority of build pipelines in this ecosystem.
When a client requests package information, the Hex API often returns data encoded in the Erlang External Term Format. The vulnerable code used the native binary_to_term/1 function to decode this data. This function is inherently unsafe when applied to untrusted input because it deserializes data into memory without restriction, creating atoms and other complex structures directly within the Erlang VM.
By spoofing a Hex API response—either through a compromised mirror, a Man-in-the-Middle (MitM) attack, or a malicious proxy—an attacker can supply a crafted binary payload. This payload is processed automatically by the build tool, leading to immediate resource exhaustion or the instantiation of malicious objects.
The root cause is the misuse of binary_to_term/1 for processing external data (CWE-502). In the Erlang Beam VM, atoms (literals used as identifiers) are stored in a non-garbage-collected global table with a fixed limit (default 1,048,576). Once created, an atom persists until the VM terminates. The binary_to_term/1 function creates new atoms if they do not already exist in the table.
Mechanism 1: Atom Table Exhaustion (DoS) An attacker can construct a payload containing hundreds of thousands of unique, random atoms. When the client deserializes this payload, the VM attempts to register all new atoms. This rapidly fills the atom table. Once the limit is reached, the Erlang VM allocates no further memory for atoms and crashes instantly, terminating the build process.
Mechanism 2: Object Injection (RCE)
The Erlang External Term Format supports the serialization of anonymous functions (funs). If an attacker injects a serialized fun into the response, and the application logic subsequently invokes that term (or passes it to a function expecting a callback), the attacker's code executes within the context of the build tool. This allows for arbitrary system command execution via os:cmd/1 wrapped inside the injected function.
The vulnerability existed in the HTTP response handling logic within src/hex_api.erl. The code implicitly trusted the Content-Type: application/vnd.hex+erlang header and passed the body directly to the deserializer.
Vulnerable Code (Pre-Patch):
%% src/hex_api.erl
case binary:match(ContentType, ?ERL_CONTENT_TYPE) of
{_, _} ->
%% CRITICAL: Unsafe deserialization of untrusted binary data
%% This permits atom creation and function instantiation
{ok, {Status, RespHeaders, binary_to_term(RespBody)}};
nomatch ->
{ok, {Status, RespHeaders, nil}}
end.Patched Code (Post-Patch):
The fix introduces two layers of defense. First, it switches to binary_to_term/2 with the safe option, which forbids the creation of new atoms. Second, it implements a recursive validator that ensures the deserialized term only contains inert data types (lists, tuples, maps, numbers, binaries) and explicitly rejects dangerous types like fun or port.
%% src/hex_api.erl
case binary:match(ContentType, ?ERL_CONTENT_TYPE) of
{_, _} ->
%% FIX 1: Use the [safe] flag to prevent atom creation
case binary_to_term(RespBody, [safe]) of
Term ->
%% FIX 2: Recursively validate the structure against a whitelist
case hex_safe_binary_to_term:validate(Term) of
ok ->
{ok, {Status, RespHeaders, Term}};
{error, Reason} ->
{error, Reason}
end;
Error ->
{error, Error}
end;
nomatch ->
...
end.To exploit this vulnerability, an attacker requires a position in the network to modify traffic or control over a repository mirror configured by the victim. The attack does not require authentication to the victim's machine, but it does require the victim to initiate a build command (User Interaction).
mix deps.get). If TLS validation is disabled or compromised, this is trivial. Alternatively, the attacker sets up a rogue mirror.%% DoS Payload: List of 1M unique atoms
Payload = term_to_binary([list_to_atom(integer_to_list(I)) || I <- lists:seq(1, 1000000)]).
%% RCE Payload: Embedded fun
Payload = term_to_binary(fun() -> os:cmd("rm -rf /") end).Content-Type: application/vnd.hex+erlang.fun and the client logic executes it (e.g., expecting a callback), the payload triggers.Despite the CVSS v4.0 score of 2.0 (which heavily penalizes the Attack Requirements and User Interaction), the technical impact is critical for affected environments. Development and CI/CD pipelines are the primary targets.
rebar3 or mix halts deployment pipelines, potentially blocking releases or causing operational downtime in automated environments.CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:A/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
hex_core hexpm | < 0.12.1 | 0.12.1 |
hex hexpm | < 2.3.2 | 2.3.2 |
rebar3 erlang | < 3.27.0 | 3.27.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-502 (Deserialization of Untrusted Data) |
| Secondary CWE | CWE-400 (Uncontrolled Resource Consumption) |
| Attack Vector | Network |
| CVSS v4.0 | 2.0 (Low) |
| Impact | DoS / RCE |
| Exploit Status | PoC Available |
The application deserializes untrusted data without sufficiently verifying that the resulting data will be valid.