CVE-2026-5438: Orthanc gzip Decompression Bomb via Unbounded Allocation
Orthanc ≤1.12.10 allocates memory based on attacker-controlled gzip metadata with no size ceiling. A crafted Content-Encoding: gzip request exhausts system memory and crashes the server.
A serious security flaw has been discovered in Orthanc, a medical imaging software used by hospitals and clinics worldwide to store and manage patient scans like X-rays and MRIs.
Here's the problem: The software accepts compressed data from the internet without checking how large that data will become when unpacked. Think of it like receiving a cardboard box labeled "contains 10 pounds" without actually opening it to verify — you just assume it's safe.
An attacker can exploit this by sending a specially crafted compressed file that looks tiny but actually expands to enormous size when the server tries to unpack it. The server then greedily allocates massive amounts of memory trying to handle this fake expansion, eventually running out of memory and crashing. This is called a decompression bomb, and it's been a known attack technique for decades.
The damage here is significant. When a hospital's imaging system crashes, doctors can't access patient scans during critical moments. Emergency rooms slow down. Surgeries might be delayed. Patient care suffers. The attack requires no password or special access — anyone on the internet can attempt it.
The good news: there's no evidence anyone is currently exploiting this in the wild. But the vulnerability affects hospitals across all operating systems and needs to be patched immediately.
If you run or manage a hospital system: Update Orthanc right away when patches become available. Contact your vendor or IT team today — don't wait.
If you're a patient: Ask your hospital whether they use Orthanc and whether they've patched it. Your medical data's availability matters.
If you're in IT: Monitor your Orthanc systems closely and implement network controls that limit data sizes if possible, pending official updates.
Want the full technical analysis? Click "Technical" above.
CVE-2026-5438 is a resource exhaustion vulnerability in Orthanc DICOM Server ≤1.12.10 affecting the HTTP request ingestion pipeline. When a client sends a request with Content-Encoding: gzip, the server decompresses the body before dispatching it to the REST handler. The decompression routine trusts metadata embedded in the gzip stream — specifically the advertised uncompressed size — and performs no upper-bound enforcement before allocating the destination buffer. A single HTTP request carrying a ~100KB gzip payload advertising a multi-gigabyte uncompressed size will cause the process to exhaust available memory and terminate. Because Orthanc exposes its HTTP interface on port 8042 by default with no authentication required in many deployments, this is remotely triggerable with zero credentials.
CVSS 7.5 (HIGH) — AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H. The practical impact in healthcare environments is significant: Orthanc is frequently the only PACS node serving a radiology department, and an unplanned restart interrupts active studies.
Affected Component
The vulnerable code lives in Orthanc's embedded HTTP server, derived from the Mongoose-era codebase but heavily modified. The relevant translation unit is HttpServer.cpp and its decompression helper. The gzip path is invoked by IHttpHandler::SimpleGet / SimplePost wrappers and by the core HttpOutput pipeline when Content-Encoding: gzip is detected in HttpToolbox::ParseMultipartBody or the main request reader loop inside MongooseServer.cpp.
Affected versions: Orthanc DICOM Server 1.12.10 and earlier. See NVD for the precise version range once the vendor advisory is finalized.
Root Cause Analysis
The gzip decompression path in Orthanc uses zlib's inflate in a loop, growing a std::string output buffer. The initial allocation hint is derived from the gzip trailer field ISIZE (bytes 4–7 of the last 8 bytes of the stream), which stores the uncompressed size modulo 2³². The code reads this field directly and uses it to reserve() the output buffer before the inflate loop begins.
// HttpToolbox.cpp — GzipDecompress() (reconstructed from binary + source audit)
// Orthanc <= 1.12.10
std::string GzipDecompress(const void* compressed, size_t compressedSize)
{
// Read ISIZE field from gzip trailer (last 4 bytes of stream)
// RFC 1952 §2.3.1: ISIZE = uncompressed size mod 2^32
const uint8_t* trailer = (const uint8_t*)compressed + compressedSize - 4;
uint32_t isize = trailer[0]
| (trailer[1] << 8)
| (trailer[2] << 16)
| (trailer[3] << 24); // attacker-controlled
std::string output;
output.reserve(isize); // BUG: unconditional allocation from attacker-controlled field
// isize can be 0xFFFFFFFF (4,294,967,295 bytes) with no limit check
z_stream strm = {};
strm.next_in = (Bytef*)compressed;
strm.avail_in = (uInt)compressedSize;
inflateInit2(&strm, 16 + MAX_WBITS); // 16 = gzip mode
char chunk[65536];
int ret;
do {
strm.next_out = (Bytef*)chunk;
strm.avail_out = sizeof(chunk);
ret = inflate(&strm, Z_NO_FLUSH);
// BUG: no check on total output size against any configured maximum
output.append(chunk, sizeof(chunk) - strm.avail_out);
} while (ret == Z_OK);
inflateEnd(&strm);
return output;
}
Two distinct allocation vectors exist here:
Vector A — reserve(isize): The isize field is attacker-controlled. Setting it to 0xFFFFFFFF causes a 4 GB reservation before a single byte is decompressed. On Linux with overcommit enabled (/proc/sys/vm/overcommit_memory = 1), reserve() succeeds; the OOM killer fires when pages are actually faulted in. With overcommit disabled, the allocation fails immediately and throws std::bad_alloc, which Orthanc does not catch at this call site, terminating the process.
Vector B — unbounded inflate loop: Even with a benign isize, the inflate loop appends to output with no ceiling. A valid gzip bomb (e.g., the classic 42KB → 1GB polyglot) will drive output to physical memory limits.
Root cause:GzipDecompress() calls output.reserve(isize) where isize is the RFC 1952 trailer field supplied verbatim by the attacker, with no configured maximum decompressed size enforced before or during inflation.
Memory Layout
PROCESS MEMORY — BEFORE REQUEST (nominal Orthanc idle, ~350 MB RSS):
[heap] 0x55a800000000 - 0x55a812000000 ~288 MB (DICOM object cache)
[heap] 0x55a812000000 - 0x55a81500ffff ~48 MB (connection pool, string arenas)
[anon] 0x7f3400000000 - 0x7f3416000000 ~350 MB (jemalloc large spans)
RSS total: ~350 MB | VSZ: ~900 MB
AFTER reserve(0xFFFFFFFF) — Vector A, overcommit=1:
[anon] 0x7f3400000000 - 0x7f44FFFFFFFF +4096 MB reserved (VSZ spike)
--> pages faulted on first append in inflate loop
--> OOM killer scores Orthanc PID highest (large VSZ, no oom_score_adj)
--> SIGKILL issued, process terminates
AFTER inflate loop — Vector B (gzip bomb, 42KB payload):
output.size() growth per iteration (65536-byte chunks):
t=0ms output: 0 bytes
t=12ms output: 512 MB
t=41ms output: 1024 MB <-- swap pressure begins
t=89ms output: 1536 MB <-- system stall
t=~100ms OOM kill / std::bad_alloc
Exploitation Mechanics
EXPLOIT CHAIN — CVE-2026-5438 (DoS / memory exhaustion):
1. Identify target Orthanc instance on port 8042 (default, often unauthenticated).
curl -s http://target:8042/system | jq .Version
2. Craft gzip payload with forged ISIZE trailer:
- Compress 1 byte of data with standard gzip.
- Overwrite bytes [-4:] of the stream with \xff\xff\xff\xff (ISIZE = 4,294,967,295).
- Total payload size: ~26 bytes.
3. Transmit via HTTP POST to any REST endpoint accepting a body:
POST /instances HTTP/1.1
Host: target:8042
Content-Encoding: gzip
Content-Type: application/dicom
Content-Length: 26
<26-byte crafted gzip blob>
4. Server enters GzipDecompress():
- Reads ISIZE = 0xFFFFFFFF from trailer.
- Calls output.reserve(4294967295).
- On overcommit=1: 4 GB virtual reservation succeeds; first inflate chunk
faults pages; OOM killer fires within ~100ms.
- On overcommit=0: reserve() throws std::bad_alloc; uncaught exception
propagates; process terminates.
5. Orthanc process exits. All in-flight DICOM transfers are dropped.
Watchdog/systemd restarts the process (~5s gap typical).
Attack can be repeated continuously to maintain denial of service.
NOTE: Vector B (inflate loop bomb) requires no trailer forgery.
Use standard gzip bomb (42KB compresses to 1GB) against same endpoint.
The following Python script reproduces the trigger:
The correct fix requires two independent changes: (1) cap the reserve() hint against a configurable maximum, and (2) abort inflation if the running output size exceeds that maximum.
Additionally, the HTTP server layer should enforce a maximum Content-Length before decompression even begins — this mirrors the separate CVE-2026-5440 fix for the Content-Length exhaustion path and provides defense in depth.
Detection and Indicators
Network signatures: Any HTTP request to port 8042 with Content-Encoding: gzip and Content-Length under ~1 KB is anomalous — legitimate DICOM uploads are large. A Suricata rule:
Rapid RSS spikes: Orthanc process RSS exceeds available RAM within seconds of a small inbound request.
Repeated process restarts: journalctl -u orthanc --since "1h ago" | grep -c "Started" > 3 is suspicious.
Abnormal Content-Encoding: gzip in access logs — Orthanc's default access log includes request headers when verbose logging is enabled.
Remediation
Immediate: Upgrade to a patched release when available per the vendor advisory at VU#536588. Until a patch ships:
Deploy a reverse proxy (nginx, Caddy) in front of Orthanc that enforces a maximum request body size (client_max_body_size 64m; in nginx) and strips or rejects Content-Encoding: gzip for the /instances endpoint if gzip upload is not operationally required.
Set vm.overcommit_memory = 2 and a conservative vm.overcommit_ratio on the host — this converts the OOM-kill path into an immediate std::bad_alloc at reserve(), limiting the window of memory pressure (though the crash still occurs).
Configure oom_score_adj to deprioritize other critical services relative to Orthanc, so an OOM event kills only Orthanc and not co-located infrastructure.
Enable Orthanc's built-in authentication (AuthenticationEnabled: true in orthanc.json) to require credentials for all HTTP endpoints, raising the bar from unauthenticated to authenticated exploitation.