Am I affected?
Description
Allocation of Resources Without Limits or Throttling vulnerability in hackney_conn in benoitc hackney allows a malicious HTTP server to exhaust client memory via an oversized or highly compressed response body. hackney:request/5 and hackney:body/1 return the whole response body, and nothing bounds how much the connection process buffers. Any server the client requests can trigger it.
hackney_conn:read_full_body/2 and the HTTP/2 and HTTP/3 accumulators append every chunk until the body ends. With {auto_decompress, true} the buffered body is then inflated in one step with zlib:gunzip/1 or zlib:inflate/2, which return the whole output at once.
This issue affects hackney: from 2.0.0-beta.1 before 4.9.0.
Technical analysis
1. Unbounded accumulation. hackney:request/5 with a body, and hackney:body/1 after start_response/1, read the response in src/hackney_conn.erl through read_full_body/2, which appends each chunk to a binary accumulator until the body ends. The HTTP/2 and HTTP/3 synchronous paths accumulate stream data the same way. No option caps the size.
2. One-step decompression. With {auto_decompress, true} hackney sends Accept-Encoding: gzip, deflate. Once the body is complete, maybe_decompress_body/2 dispatches on Content-Encoding to decompress_gzip/1 (zlib:gunzip/1) or decompress_deflate/1 (zlib:inflate/2). Both return the whole output at once, so the inflated size is bounded only by the compression ratio. The report measured a 1 MB gzip body inflating to 1 GB on hackney 4.8.4.
Proof of concept
- Build a gzip stream whose inflated size is 1 GB from a reused 64 KB chunk of zero bytes (deflate with window bits 31, best compression). The stream is about 1 MB on the wire.
- Start a TCP listener on loopback that accepts one connection, reads the request, and answers
HTTP/1.1 200 OKwithContent-Encoding: gzip, aContent-Lengthmatching the stream,Connection: close, and the stream as body. - From a node running a vulnerable hackney, call
hackney:request(get, Url, [], <<>>, [{auto_decompress, true}, with_body])against that listener. - Observe the returned body of 1,048,576,000 bytes and the VM memory growing by about 1.8 GB for a 1,019,197 byte response. A larger stream, or an endless body without
auto_decompress, drives the node out of memory.
Weaknesses & attack patterns
Weakness
CWE-409
·
Improper Handling of Highly Compressed Data (Data Amplification)
in catalog →
MITRE ↗
CWE-770
·
Allocation of Resources Without Limits or Throttling
in catalog →
MITRE ↗
Attack patterns
CAPEC-130
·
Excessive Allocation
MITRE ↗
A server the client requests can make the client allocate memory proportional to the body it sends, or to its decompressed size, until the node runs out of memory.
Affected — Hex / hackney Hex.pm ↗ Repository ↗
modules · source files · routines
Affected — GitHub / benoitc/hackney Repository ↗
modules · source files · routines
Workarounds
Leave auto_decompress disabled (the default). Without it hackney_conn:maybe_decompress_body/2 never inflates the body, which closes the amplification half. The uncapped raw body read has no workaround.
References
Credits
CVSS breakdown
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