GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GHSA-655F-MP8P-96GV

Vulnerability from github – Published: 2026-07-29 15:23 – Updated: 2026-07-29 15:23
VLAI
Summary
Req vulnerable to unbounded archive/compression extraction triggered by response content-type
Details

Summary

Req's default response pipeline auto-decodes archive and compressed bodies based on the server-supplied content-type (or URL extension) and materialises the full decompressed contents in memory with no size cap. An attacker who controls (or can redirect a victim into) an HTTP endpoint reached by Req.get!/1 can return a tiny "decompression bomb" that expands to many gigabytes on the client and exhausts the BEAM's memory.

Details

1. Archive auto-decoding. Req.Steps.decode_body/1 in lib/req/steps.ex dispatches on the response content-type (or URL extension) and calls Erlang's archive libraries with :memory, returning a [{name, bytes}] list of every entry fully decompressed in RAM: application/zip:zip.extract(body, [:memory]), application/x-tar:erl_tar.extract({:binary, body}, [:memory]), application/gzip / .tgz:erl_tar.extract({:binary, body}, [:memory, :compressed]). No byte cap is enforced before decoding and no per-entry size limit is passed to :zip / :erl_tar.

2. content-encoding chaining. Req.Steps.decompress_body/1 walks the content-encoding header and chains :zlib / :brotli / :ezstd decoders, so a response advertising content-encoding: gzip, gzip, gzip, … inflates through multiple layers without bound.

3. Default-on, attacker-chosen decoder. Both steps are part of Req's default pipeline. The caller does not need to opt in, and the attacker chooses which decoder fires by setting content-type and content-encoding on their own server (or on any host reached via Req's automatic redirect following).

PoC

  1. Run an HTTP server that responds 200 with content-type: application/zip and a body that is a zip archive whose single entry is ~400 MB of zero bytes (compressed wire payload: a few hundred KB).
  2. From the victim process, call Req.get!(url) against that server (no special options, no opt-in to archive decoding).
  3. decode_body/1 dispatches on content-type, invokes :zip.extract(body, [:memory]), and the response body becomes [{~c"bomb.bin", <<400 MB of zero bytes>>}]. A sub-MB request produces hundreds of MB resident memory; layering gzip on the content-encoding path or increasing entry size scales arbitrarily.

Impact

Memory-exhaustion denial of service against any Elixir application that uses Req with its default step pipeline to fetch URLs influenced by an untrusted party, including webhook senders, link previews, OAuth/OIDC discovery clients, package mirrors, image proxies, and any Req.get!/1 call that may follow redirects to attacker-controlled hosts. No authentication is required; a single response can crash the BEAM and take down unrelated workloads on the same VM.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Hex",
        "name": "req"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.1.0"
            },
            {
              "fixed": "0.6.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-49755"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-409"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-29T15:23:16Z",
    "nvd_published_at": "2026-06-08T16:16:43Z",
    "severity": "HIGH"
  },
  "details": "### Summary\n\nReq\u0027s default response pipeline auto-decodes archive and compressed bodies based on the server-supplied `content-type` (or URL extension) and materialises the full decompressed contents in memory with no size cap. An attacker who controls (or can redirect a victim into) an HTTP endpoint reached by `Req.get!/1` can return a tiny \"decompression bomb\" that expands to many gigabytes on the client and exhausts the BEAM\u0027s memory.\n\n### Details\n\n**1. Archive auto-decoding.** `Req.Steps.decode_body/1` in `lib/req/steps.ex` dispatches on the response `content-type` (or URL extension) and calls Erlang\u0027s archive libraries with `:memory`, returning a `[{name, bytes}]` list of every entry fully decompressed in RAM: `application/zip` \u2192 `:zip.extract(body, [:memory])`, `application/x-tar` \u2192 `:erl_tar.extract({:binary, body}, [:memory])`, `application/gzip` / `.tgz` \u2192 `:erl_tar.extract({:binary, body}, [:memory, :compressed])`. No byte cap is enforced before decoding and no per-entry size limit is passed to `:zip` / `:erl_tar`.\n\n**2. content-encoding chaining.** `Req.Steps.decompress_body/1` walks the `content-encoding` header and chains `:zlib` / `:brotli` / `:ezstd` decoders, so a response advertising `content-encoding: gzip, gzip, gzip, \u2026` inflates through multiple layers without bound.\n\n**3. Default-on, attacker-chosen decoder.** Both steps are part of Req\u0027s default pipeline. The caller does not need to opt in, and the attacker chooses which decoder fires by setting `content-type` and `content-encoding` on their own server (or on any host reached via Req\u0027s automatic redirect following).\n\n### PoC\n\n1. Run an HTTP server that responds 200 with `content-type: application/zip` and a body that is a zip archive whose single entry is ~400 MB of zero bytes (compressed wire payload: a few hundred KB).\n2. From the victim process, call `Req.get!(url)` against that server (no special options, no opt-in to archive decoding).\n3. `decode_body/1` dispatches on `content-type`, invokes `:zip.extract(body, [:memory])`, and the response body becomes `[{~c\"bomb.bin\", \u003c\u003c400 MB of zero bytes\u003e\u003e}]`. A sub-MB request produces hundreds of MB resident memory; layering gzip on the `content-encoding` path or increasing entry size scales arbitrarily.\n\n### Impact\n\nMemory-exhaustion denial of service against any Elixir application that uses Req with its default step pipeline to fetch URLs influenced by an untrusted party, including webhook senders, link previews, OAuth/OIDC discovery clients, package mirrors, image proxies, and any `Req.get!/1` call that may follow redirects to attacker-controlled hosts. No authentication is required; a single response can crash the BEAM and take down unrelated workloads on the same VM.",
  "id": "GHSA-655f-mp8p-96gv",
  "modified": "2026-07-29T15:23:16Z",
  "published": "2026-07-29T15:23:16Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/wojtekmach/req/security/advisories/GHSA-655f-mp8p-96gv"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-49755"
    },
    {
      "type": "WEB",
      "url": "https://github.com/wojtekmach/req/commit/84977e5b1a83f26e749d55ad06e3625464af4e8d"
    },
    {
      "type": "WEB",
      "url": "https://cna.erlef.org/cves/CVE-2026-49755.html"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/wojtekmach/req"
    },
    {
      "type": "WEB",
      "url": "https://osv.dev/vulnerability/EEF-CVE-2026-49755"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Req vulnerable to unbounded archive/compression extraction triggered by response content-type"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…