All Advisories

Orthanc DICOM Server

Out-of-Bounds Read in DicomStreamReader Meta-Header Parser

Orthanc's DICOM stream parser allocates a meta-header block sized by the group-length tag, then reads each tag's declared value length and assigns that many bytes from the block. The loop guard verifies the 8/12-byte tag header fits, but not the value length — so a truncated group-length value lets the parser read past the allocation boundary.

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

Description

Orthanc's POST /instances handler streams uploaded DICOM data through DicomStreamReader::HandleMetaHeader. The parser allocates a heap block whose size comes from the group-length tag (0002,0000), then iterates over the meta-header tags inside that block. Each iteration verifies that the tag's 8-byte (short-form) or 12-byte (long-form) header fits within the allocation before reading the tag's value length.

The short-form path reads a 16-bit value length and copies that many bytes out of the block — without first checking that pos + 8 + length stays within block.size():

OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp:197-228

while (pos + 8 <= block.size()) // checks 8 bytes available
{
DicomTag tag = ReadTag(p + pos, true);
ValueRepresentation vr = StringToValueRepresentation(
std::string(p + pos + 4, 2), true);
if (IsShortExplicitTag(vr))
{
uint16_t length = ReadUnsignedInteger16(p + pos + 6, true); // untrusted length
std::string value;
value.assign(p + pos + 8, length); // NO CHECK: pos+8+length <= block.size()
pos += length + 8;
}

View source →

The long-form path uses a 32-bit value length and has the same gap:

OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp:230-248

else if (pos + 12 <= block.size())
{
uint32_t length = ReadUnsignedInteger32(p + pos + 8, true); // untrusted 32-bit length
if (tag.GetGroup() == 0x0002)
{
std::string value;
value.assign(p + pos + 12, length); // NO CHECK: pos+12+length <= block.size()
}
pos += length + 12;
}

View source →

The same parsing logic in DicomMap::ReadNextTag includes the corresponding bounds check:

OrthancFramework/Sources/DicomFormat/DicomMap.cpp:1096

uint16_t length = ReadLittleEndianUint16(dicom + position + 6);
if (position + 8 + length > size) // THIS CHECK IS MISSING IN DicomStreamReader
{
return false;
}
value.assign(dicom + position + 8, length);

View source →

If the group-length tag (0002,0000) is patched to a value small enough that a target tag's 8-byte header still fits inside the block but its value bytes extend past the allocation, value.assign() then reads beyond the heap allocation. AddressSanitizer pinpoints the OOB at DicomStreamReader.cpp:208, immediately past a 157-byte allocation made by StreamBlockReader::Schedule:

AddressSanitizer trace

==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xffff9423cebd
READ of size 64 at 0xffff9423cebd thread T16
#0 in __interceptor_memcpy
#3 in std::__cxx11::basic_string::assign() basic_string.h:1443
#4 in Orthanc::DicomStreamReader::HandleMetaHeader() DicomStreamReader.cpp:208
#8 in Orthanc::DicomStreamReader::LookupPixelDataOffset() DicomStreamReader.cpp:740
#9 in Orthanc::ServerContext::StoreAfterTranscoding() ServerContext.cpp:683
#13 in UploadDicomFile OrthancRestApi.cpp:246
0xffff9423cebd is located 0 bytes to the right of 157-byte region [0xffff9423ce20,0xffff9423cebd)
allocated by thread T16 here:
#4 in Orthanc::StreamBlockReader::Schedule() DicomStreamReader.cpp:49

Two structural factors limit the practical impact. First, DCMTK pre-validates the DICOM file before DicomStreamReader runs; tag value lengths inflated to large values (e.g. 0xFFFF) get the file rejected up-front, capping the OOB read to approximately the original tag value length (~64 bytes). Second, the value bytes are written into a temporary std::string that VisitMetaHeaderTag() inspects only for the Transfer Syntax UID — for any other tag the string is destroyed on stack unwind, with no exfiltration channel in the studied path.

The C-STORE network protocol does not transmit file meta-headers (Orthanc generates its own correct meta-header for C-STORE-received instances), so this vector is HTTP-only.

Impact

  • Out-of-bounds heap read of approximately 64 bytes during DICOM file upload via POST /instances.
  • No exfiltration channel exists in the studied path: the affected tag's value is consumed by VisitMetaHeaderTag(), which only checks for Transfer Syntax UID, then discarded. Information disclosure is theoretical.
  • The 64-byte read stays within accessible heap pages on stock builds; no crash or denial of service is observed.

Mitigation

Update Orthanc to version 1.12.11 or later. The fix adds the missing pos + 8 + length <= block.size() and pos + 12 + length <= block.size() checks before the value.assign() calls, matching the existing logic in DicomMap::ReadNextTag.

Defender's Checklist

  • Verify your version.

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

  • Restrict the affected endpoint.

    Restrict POST /instances at your reverse proxy to the IP ranges of the imaging modalities and integration services that legitimately upload studies.

  • Audit credentials.

    Even though the original CVSS uses PR:N, deployments with HTTP authentication on /instances require an authenticated user. Review RegisteredUsers in orthanc.json and any external authentication backend for stale or over-permissioned accounts.

  • Check upload logs for malformed DICOM.

    Look for POST /instances calls that produced anomalous responses and for parser-level error entries referencing meta-header parsing.

  • Plan an ASAN-instrumented test build.

    For high-risk environments, validate the patched build against your modality-vendor sample DICOM with an AddressSanitizer-instrumented Orthanc to confirm no regression and no remaining OOB reads in adjacent meta-header paths.

Severity Reasoning

AV:NReachable over the network via Orthanc's HTTP REST API on POST /instances.AC:LA single crafted DICOM with a truncated meta-header. No timing or environmental conditions.PR:NThe published vector reflects deployments where POST /instances is exposed without HTTP authentication, which Orthanc permits via configuration.UI:NNo interaction by a second user is needed.S:UOnly the Orthanc process is affected; no out-of-process resources are reachable through this primitive.C:NIn the studied call path the OOB-read bytes are consumed only for the Transfer Syntax UID check and then discarded; no exfiltration channel exists.I:NRead-only primitive into a discarded buffer; nothing is modified.A:HOn builds with strict heap layout the OOB read can reach an unmapped memory page, terminating the Orthanc process.

On stock 1.12.10 builds with default glibc, the 64-byte read typically stays within accessible heap pages and does not crash; the published A:H reflects the worst case on hardened or restricted-heap deployments.

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.