Sep 23, 2026·6 min read·23 visits
Unauthenticated remote attackers can exhaust Erlang VM schedulers and cause a Denial of Service via deeply nested parameter keys in URL-encoded requests.
An algorithmic complexity vulnerability (CWE-407) in the query decoder of the Elixir Plug library (CVE-2026-54892) allows unauthenticated remote attackers to trigger scheduler starvation and denial of service by transmitting deeply nested brackets in query parameters or URL-encoded post bodies.
The vulnerability CVE-2026-54892 represents an algorithmic complexity flaw within the Plug library, the fundamental middleware stack powering web applications built in Elixir, including those on the Phoenix Framework. Within the Elixir ecosystem, Plug defines the specification for web application connection pipelines, managing everything from request parsing to route dispatching.
At the core of the issue is the processing of nested request parameters inside Plug.Conn.Query.decode/4 and Plug.Conn.Query.decode_each/2. These components are responsible for parsing incoming query strings from URLs as well as processing application/x-www-form-urlencoded payloads commonly sent via HTTP POST requests.
Because this parsing stage executes during the initial phases of the Plug connection lifecycle, it acts as a critical entry point on the application's attack surface. An attacker does not require authentication or route authorization to trigger the vulnerable code pathways, exposing the application to unauthenticated remote exploitation.
This security advisory details how a lack of depth limits in the parameter decoder leads to a quadratic-time $O(N^2)$ workload, which can be leveraged by a remote adversary to completely saturate Erlang VM schedulers, thereby inducing a persistent and resource-efficient Denial of Service (DoS).
The root cause of CVE-2026-54892 lies in the key splitting and nested dictionary construction algorithms implemented within lib/plug/conn/query.ex. The library employs a recursive parser to handle bracketed query parameter hierarchies such as user[profile][address][city]=value.
When Plug encounters a nested parameter structure, it recursively splits the key binary at bracket boundaries. To optimize performance, a previous patch utilized binary slicing to track path hierarchies. However, as the nested depth $N$ grows, the algorithm recursively extracts prefixes of the key binary to lookup and insert indices in the BEAM virtual machine's map structures.
Specifically, for each nested depth step $i$, the algorithm computes the binary prefix of length $L_i \approx 3i$. When inserting these keys into the tracking map, the BEAM VM must compute the hash of this growing binary prefix. Hashing a binary key requires a linear scan of its content, translating to an $O(L_i)$ computational cost. Summing this cost over $N$ nested levels yields an overall time complexity of $O(N^2)$ relative to the nesting depth.
Under default configurations, Plug.Parsers.URLENCODED allows up to 1,000,000 bytes of data. An attacker can construct a payload containing 333,000 levels of nesting. The BEAM VM attempting to parse this payload will undergo a massive hashing workload of approximately $166$ gigabytes of processed binary data, completely locking the corresponding Erlang process and its OS thread.
Below is a comparison of the vulnerable implementation versus the mitigation implemented in the security patch. The original, optimized code did not track recursion depth, allowing unbounded recursion and nested key allocation.
In the vulnerable version of lib/plug/conn/query.ex, the split_keys/6 function recursively parses brackets without keeping track of recursion depth limit:
defp split_keys(<<?], ?[, rest::binary>>, binary, current_pos, start_pos, level, acc) do
value = split_key(binary, current_pos, start_pos)
next_level = binary_part(binary, 0, current_pos + 1)
split_keys(rest, binary, current_pos + 2, current_pos + 2, next_level, [{level, value} | acc])
endThe security patch restricts recursion nesting to a maximum of 32 depth steps. A counter parameter is added to split_keys/7, which triggers an exception when exceeded:
@max_nesting 32
defp split_keys(<<?], ?[, rest::binary>>, binary, current_pos, start_pos, level, acc, count) do
count = count + 1
check_nesting!(count)
value = split_key(binary, current_pos, start_pos)
next_level = binary_part(binary, 0, current_pos + 1)
current_pos = current_pos + 2
acc = [{level, value} | acc]
split_keys(rest, binary, current_pos, current_pos, next_level, acc, count)
end
defp check_nesting!(count) when count > @max_nesting do
message = "maximum query nesting is #{@max_nesting}, got a query with #{count} keys"
raise Plug.Conn.InvalidQueryError, message: message
endThis counter limits the worst-case time complexity of the parser by throwing a Plug.Conn.InvalidQueryError if the parameter depth exceeds the safe threshold of 32 nesting levels. This ensures the parsing load remains linear and safe.
Exploitation is accomplished by transmitting an HTTP request containing a single key with an extreme nesting depth to the target application. This attack targets the initial request ingestion stage, which executes before application routing occurs.
An attacker can construct a payload consisting of a variable name followed by nested sub-keys: x[x][x]...[x]=1. Because the default body limit for Plug.Parsers.URLENCODED is 1,000,000 bytes, a payload can reach over 330,000 nesting levels within a single request. When parsed, this structure causes the BEAM virtual machine to exhaustively perform binary partitioning and map hashing, consuming billions of processor cycles.
Because the Erlang VM runs cooperative schedulers pinned to individual CPU cores, processing such a payload freezes that scheduler thread. Flooding the target application with a small quantity of such requests saturates all available schedulers, stopping the handling of other network traffic and leading to a complete Denial of Service. The exploit does not require active socket monitoring or established sessions; it functions entirely statelessly.
The security impact of CVE-2026-54892 is scored as High with a CVSS v4.0 base score of 8.7. The primary threat vector is the complete loss of service availability across the affected application node.
Since body parsers generally run at the entry point of the middleware stack, the payload is parsed before any routing or authentication logic is evaluated. Consequently, even requests targeting nonexistent paths or static asset directories can trigger the resource exhaustion bug. This exposes all public-facing HTTP endpoints as potential target vectors.
No confidentiality or integrity impacts are present, but the ease of execution and low network bandwidth requirements make this vulnerability a highly reliable Denial of Service vector against unpatched Elixir and Phoenix systems. Systems deployed without external load balancing, rate limiting, or reverse-proxy request buffering are particularly vulnerable to immediate starvation of CPU resources.
The recommended remediation is to upgrade the plug library in the application's dependencies to one of the patched releases:
plug version 1.15.5plug version 1.16.4plug version 1.17.2plug version 1.18.3plug version 1.19.3If upgrading immediately is not possible, you can mitigate the vulnerability by restricting incoming request body size limits in endpoint.ex:
plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Phoenix.json_library(),
length: [urlencoded: 50000]Additionally, Web Application Firewalls (WAF) can be configured to block request strings or post bodies that match a nested bracket regex pattern of more than 32 nested components, e.g., (\\[[^\\]]*\\]){32,}. These protective steps shield the underlying application from handling the malicious, oversized queries.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
plug elixir-plug | >= 1.15.0, < 1.15.5 | 1.15.5 |
plug elixir-plug | >= 1.16.0, < 1.16.4 | 1.16.4 |
plug elixir-plug | >= 1.17.0, < 1.17.2 | 1.17.2 |
plug elixir-plug | >= 1.18.0, < 1.18.3 | 1.18.3 |
plug elixir-plug | >= 1.19.0, < 1.19.3 | 1.19.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-407 |
| Attack Vector | Network |
| CVSS v4.0 Score | 8.7 |
| Affected Component | Plug.Conn.Query.decode/4 |
| Exploit Status | poc |
| Remediation Status | Patched |
The execution time of the query decoder increases quadratically relative to the depth of bracket-nested query parameter keys, enabling resource exhaustion attacks.
CVE-2026-16454 is an object-level authorization bypass vulnerability (CWE-284, CWE-862) in Eclipse hawkBit versions 1.0.3 and prior. The flaw exists within the Direct Device Integration (DDI) REST controller (hawkbit-ddi-resource), where endpoints responsible for enumerating software module metadata and serving firmware binaries fail to verify whether the requested software module is assigned to the authenticated device. Consequently, any device with valid credentials (such as a TargetToken or GatewayToken) can enumerate software module identifiers and download arbitrary firmware artifacts across the entire tenant namespace.
CVE-2026-62284 (GHSA-5w5g-j6hw-6p8f) is a code injection and server-side request forgery vulnerability in the console-proxy component of the OSAC Operator. The RemoteConfigResolver parses Kubernetes Secrets containing kubeconfig files via clientcmd.RESTConfigFromKubeConfig without sanitizing execution-related fields. An attacker with RBAC permissions to create or modify labeled Secrets in monitored namespaces can supply a malicious exec credential plugin, resulting in arbitrary command execution within the console-proxy pod.
CVE-2026-62361 (GHSA-xgjr-7j9q-2h4r) is an authenticated SQL injection and authorization bypass vulnerability in listmonk, an open-source, self-hosted newsletter and mailing list manager written in Go. In versions prior to 6.2.0, the subscriber export endpoint (GET /api/subscribers/export) directly interpolates user-supplied SQL filter expressions into a PostgreSQL query without invoking the table allowlist validation function (validateQueryTables). Consequently, authenticated users possessing the subscribers:sql_query and subscribers:get_all permissions can bypass relational access controls, execute arbitrary subqueries against restricted tables such as users and settings, and modify database state via writable Common Table Expressions (CTEs).
CVE-2026-28385 (GHSA-3gq2-x4qg-p4g6) is a Server-Side Request Forgery (SSRF) vulnerability in Canonical LXD versions 4.12 through 6.9. An authenticated user possessing the can_create_images entitlement can force the LXD daemon to execute arbitrary outbound HTTP and HTTPS requests via the /1.0/images API endpoint. Due to the absence of egress destination validation, an attacker can probe host loopback services, RFC 1918 private network segments, and cloud Instance Metadata Services (IMDS) from the network position of the privileged LXD daemon.
Canonical LXD and LXC Incus contain an arbitrary file write vulnerability (CWE-73 / CWE-59) in the `/1.0/instances/<name>/exec` REST API endpoint. When an instance is unpacked from an image archive, top-level symbolic links are extracted directly onto the host filesystem within the instance storage directory. If a crafted image includes a symbolic link named `exec-output` pointing to an arbitrary host path, invoking the execution endpoint with `record-output: true` causes the privileged host daemon to follow the symlink and write command output files directly to the target host directory. An authenticated attacker with permissions to import images and execute commands can exploit this flaw to write controlled content to sensitive host directories such as `/etc/cron.d`, resulting in arbitrary code execution on the host as `root`.
CVE-2026-48749 (tracked as GHSA-vghh-5rfx-xhq8 for LXD and GHSA-2q3f-q5pq-g8wv for Incus) is a critical container escape vulnerability in the Canonical LXD and Linux Containers Incus system container managers. By importing a specially crafted container image archive containing duplicate tar member headers, an authenticated attacker can replace the instance rootfs directory with a symbolic link pointing to the underlying host root filesystem. When performing file operations on a stopped container, the privileged forkfile helper binary resolves the symbolic link without confinement and chroots directly into the host filesystem, granting arbitrary root-level file read and write access.