All Advisories

Orthanc DICOM Server

Out-of-Bounds Read in DICOM Image Decoder (DecodeLookupTable)

Orthanc's PALETTE COLOR image decoder validates that the LUT arrays match the declared palette size but does not validate that the pixel values used as LUT indices stay within that range. Out-of-range pixel values read past the LUT into adjacent heap memory; the leaked bytes become RGB values in the rendered preview image and are returned to the caller.

Authored byVolker Schönefeld, Simon WeberDisclosed 2026-04-02Fully disclosed 2026-04-28
SeverityCriticalCVSS 9.1CVSS 3.1 VectorAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:HCWECWE-125 (Out-of-bounds Read)ProductOrthanc DICOM ServerAffected VersionsOrthanc <= 1.12.10Fixed In1.12.11CVECVE-2026-5445CERT/CCVU#536588.5

Description

DICOM PALETTE COLOR images carry per-channel Color Lookup Tables (Red, Green, Blue) and pixel data whose values index into those LUTs. Orthanc's DecodeLookupTable decodes these images by walking each pixel and reading three LUT entries (one per channel) into the output RGB image. The framework validates that the LUT arrays themselves match the declared paletteSize, but never checks that an individual pixel value is within [0, paletteSize).

OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp:491-502

for (unsigned int y = 0; y < height; y++)
{
uint8_t* p = reinterpret_cast<uint8_t*>(target->GetRow(y));
for (unsigned int x = 0; x < width; x++)
{
// OOB read if *source >= paletteSize
p[0] = lutRed[*source] >> offsetBits;
p[1] = lutGreen[*source] >> offsetBits;
p[2] = lutBlue[*source] >> offsetBits;
source++;
p += 3;
}
}

View source →

With a 16-entry LUT and pixel value 200, each LUT entry is uint16_t so the read lands 368 bytes past the LUT buffer. The three OOB reads per pixel target separate LUT allocations (red, green, blue), each providing an independent heap read primitive. The leaked bytes are written into the RGB pixel triplet and returned through the preview endpoint.

Only the PixelFormat_RGB24 path is affected. The PixelFormat_RGB48 path hardcodes paletteSize to 65536 and uses uint16_t source values, so the maximum index 65535 exactly fits the LUT.

An 8x8 PALETTE COLOR DICOM with paletteSize=16 and pixel values 16-79 was used in testing. The preview returned an 8x8 RGB image where over 50 of 64 pixels contained non-zero values — leaked heap data:

Preview output excerpt (Red channel, OOB read from lutRed heap region)

0000: 30 00 00 00 45 00 00 00 30 f9 0f 00 1e 99 bb f3 0...E...0.......
0010: 31 34 31 31 37 03 ff 00 08 18 66 62 70 03 ff 00 14117.....fbp...
0020: 40 00 00 00 45 00 00 00 20 00 00 00 63 5f 74 69 @...E... ...c_ti
0030: 67 00 54 00 21 00 00 00 90 00 ff 00 90 00 ff 00 g.T.!...........

The hex dump shows clear ASCII strings — 14117, fbp, c_tig — corresponding to internal Orthanc identifiers and DICOM tag data stored in adjacent heap chunks. Across 21 consecutive runs against a stock 1.12.10 Docker image, every run leaked between 41 and 53 of 64 pixels, with content varying between runs as the heap layout shifts. Representative leaks across runs included DICOM UID fragments (038485967947477029570663), internal class/method names (PrnPtet, PrnSre, RmtAT), DICOM tag fragments (mdl-ain, nsif), and recurring glibc chunk-header pointer patterns.

AddressSanitizer pinpoints the first OOB at DicomImageDecoder.cpp:497, immediately past a 32-byte LUT region allocated by DcmElement::newValueField():

AddressSanitizer trace

==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xffff95c34bf0
READ of size 2 at 0xffff95c34bf0 thread T17
#0 in DecodeLookupTable DicomImageDecoder.cpp:497
#1 in Orthanc::DicomImageDecoder::DecodeUncompressedImage() DicomImageDecoder.cpp:575
#2 in Orthanc::DicomImageDecoder::Decode() DicomImageDecoder.cpp:827
#3 in Orthanc::ParsedDicomFile::DecodeFrame() ParsedDicomFile.cpp:1823
#4 in Orthanc::ServerContext::DecodeDicomFrame() ServerContext.cpp:1921
0xffff95c34bf0 is located 0 bytes to the right of 32-byte region [0xffff95c34bd0,0xffff95c34bf0)
allocated by thread T17 here:
#0 in operator new[]
#1 in DcmElement::newValueField() dcelem.cc:788

The vulnerability is reachable via two distinct paths. The first is an authenticated HTTP upload: POST /instances followed by GET /instances/{id}/preview. The second is a stored attack via DICOM C-STORE on port 4242. Orthanc's default configuration (DicomAlwaysAllowStore: true) accepts C-STORE requests without authentication, so an attacker on the network can plant a malicious PALETTE COLOR DICOM unauthenticated. Any subsequent preview request — including by an attacker holding read-access credentials — triggers the OOB read and returns leaked heap data in the rendered image.

Impact

  • Information disclosure of arbitrary heap memory (internal Orthanc identifiers, DICOM tag data, glibc chunk metadata) via rendered preview images.
  • Three independent heap-read primitives per pixel (one per LUT). By varying pixel values across crafted files, the attacker can scan different heap offsets and assemble a multi-byte heap dump.
  • Reachable unauthenticated for the planting step under Orthanc's default configuration (DicomAlwaysAllowStore: true); the rendering step requires read-level authentication.
  • If the OOB read reaches an unmapped memory page, the Orthanc process terminates unexpectedly. On stock builds with normal heap layout this is uncommon.

Mitigation

Update Orthanc to version 1.12.11 or later. The fix adds a per-pixel bounds check (*source >= paletteSize → throw) inside the decode loop. As immediate defense in depth, set DicomAlwaysAllowStore: false in orthanc.json and configure an explicit AET allow-list, which closes the unauthenticated planting vector.

Defender's Checklist

  • Verify your version.

    curl -u <user>:<pass> http://<orthanc>:8042/system | jq .Version — the patched range begins at 1.12.11.

  • Close the unauthenticated C-STORE planting vector.

    Set DicomAlwaysAllowStore: false in orthanc.json and define an explicit DicomModalities allow-list of legitimate AETs. Without this, any host that can reach port 4242 can plant the malicious DICOM unauthenticated.

  • Restrict the preview endpoint.

    If your deployment does not require web preview, restrict GET /instances/*/preview at your reverse proxy. The OOB only becomes observable when something renders the malicious image.

  • Audit who has read access.

    An attacker only needs read-level credentials to view the leaked heap data. Review RegisteredUsers in orthanc.json and any external authentication backend for stale or low-privilege accounts that should not have read access to arbitrary studies.

  • Watch for crashes after preview requests.

    On builds with strict heap layout, the OOB can occasionally hit an unmapped page. Alert on Orthanc process restarts that closely follow GET /instances/*/preview log entries.

  • Audit the DICOM store for anomalous PALETTE COLOR studies.

    Search the store for studies with PhotometricInterpretation = PALETTE COLOR and small palette sizes (e.g. paletteSize ≤ 16) accompanied by pixel data containing values larger than the palette range. Any such study planted before patching may already have been used to leak memory.

Severity Reasoning

AV:NReachable over the network via Orthanc's HTTP REST API (POST /instances + /preview) and via DICOM C-STORE on port 4242.AC:LDeterministic leak. In testing 41-53 of 64 pixels carried heap content on every one of 21 consecutive runs against a stock build.PR:NThe published vector reflects deployments where preview rendering is exposed without HTTP authentication and the C-STORE listener accepts unauthenticated requests under default DicomAlwaysAllowStore: true. Both gates are configurable; see the defender's checklist below.UI:NNo interaction by a second user is needed; the planting and triggering steps can both be performed by the same actor.S:UOnly the Orthanc process is affected; no out-of-process resources are reached through this primitive.C:HHeap-content disclosure across multiple independent reads per pixel. Demonstrated leaks include Orthanc internal IDs, DICOM UID fragments, and adjacent tag data.I:NRead-only primitive; nothing is modified.A:HOn builds with strict heap layout the OOB read can reach an unmapped memory page, terminating the Orthanc process.

References

How We Can Help

Who We Are

The security researchers behind this advisory.

Dr. Simon Weber Profile

Dr. rer. nat. Simon Weber

Senior Pentester & MedSec Researcher

I evaluate your SaMD with the same industry-defining security insight I contributed to the BAK MV for the revision of the B3S standard.

  • PhD on Hospital Cybersecurity
  • Critical vulnerabilities found in hospital systems
  • Alumni of THB MedSec Research Group
  • gematik Security Hero
Volker Schönefeld Profile

Dipl.-Inf. Volker Schönefeld

Senior Application Security Expert

As a former CTO and developer turned pentester, I work alongside your team to uncover vulnerabilities and find solutions that fit your architecture.

  • 20+ years as CTO, 50M+ app downloads
  • Architected and secured large-scale IoT fleets
  • Certified Web Exploitation Specialist
  • gematik Security Hero

Looking for a Penetration Test?

Machine Spirits specializes in security assessments for medical devices and healthcare IT. From MDR penetration testing to C5 cloud compliance, we help MedTech companies meet regulatory requirements.