Bug 258 - Security - Denial of Service via Deeply Nested DICOM Sequences
Summary: Security - Denial of Service via Deeply Nested DICOM Sequences
Status: CONFIRMED
Alias: None
Product: Orthanc
Classification: Unclassified
Component: Orthanc Core (show other bugs)
Version: unspecified
Hardware: All All
: --- major
Assignee: Alain Mazy
URL:
Depends on:
Blocks:
 
Reported: 2026-04-26 09:37 CEST by elp3pinill0
Modified: 2026-06-02 09:06 CEST (History)
1 user (show)

See Also:


Attachments
dicom_nested_50 (2.25 KB, application/dicom)
2026-04-26 09:37 CEST, elp3pinill0
Details
poc_deep_sequence python script to generate dicoms (7.14 KB, text/x-python3)
2026-05-05 18:19 CEST, elp3pinill0
Details

Note You need to log in before you can comment on or make changes to this bug.
Description elp3pinill0 2026-04-26 09:37:41 CEST
Created attachment 149 [details]
dicom_nested_50

### Denial of Service via Deeply Nested DICOM Sequences
**Severity:** High (CVSS 7.5)  
**Component:** `OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp` + DCMTK `libdcmdata`  
**Affected versions:** Orthanc ≤ 1.12.10 (all current releases)

#### Description
Uploading a DICOM file containing deeply nested Sequence of Items (SQ) causes a stack overflow in the civetweb HTTP worker thread, crashing the entire Orthanc process. 

Two mutually recursive call chains both lack any depth limit:

**Chain 1 — DCMTK parsing (crashes at ~50 levels):**
```
DcmItem::read()
  └─ DcmSequenceOfItems::read()
       └─ DcmItem::read()
            └─ ...  (no depth check anywhere)
```

**Chain 2 — Orthanc JSON serialization (would crash independently):**
```cpp
// FromDcmtkBridge.cpp ~line 1218
void ElementToJson(..., unsigned int depth) {
    DcmSequenceOfItems& seq = dynamic_cast<DcmSequenceOfItems&>(element);
    for (unsigned long i = 0; i < seq.card(); i++) {
        DatasetToJson(v, *child, ..., depth + 1);  // NO DEPTH LIMIT
    }
}
void DatasetToJson(..., unsigned int depth) {
    for (unsigned long i = 0; i < item.card(); i++) {
        ElementToJson(parent, *element, ..., depth);  // CALLS BACK
    }
}
```

#### Exploitation
Craft a DICOM with N levels of nested private SQ sequences (Explicit VR Little Endian, undefined-length encoding). Upload to `POST /instances`.

```python
# 50 levels of nesting → SIGSEGV in civetweb worker thread
ITEM_TAG = struct.pack('<HH', 0xFFFE, 0xE000) + b'\xff\xff\xff\xff'
for _ in range(50):
    buf.write(pack_tag(0x7777, 0x0001) + b'SQ\x00\x00' + b'\xff\xff\xff\xff')
    buf.write(ITEM_TAG)
for _ in range(50):
    buf.write(SEQ_END_TAG + ITEM_END_TAG)
```

Crash confirmed in `DcmItem::readTagAndLength` (libdcmdata.so.19.3.6.9 offset 0x10ae0b):
```
dmesg: civetweb-worker[...]: segfault at ... error 6 in libdcmdata.so.19.3.6.9
```

The crash kills the entire Orthanc server process (all HTTP/DICOM services stop), not just the handler thread, because the worker is part of the single Orthanc process.

#### Impact
- Complete availability loss for all Orthanc services until manual restart
- Attackable from any network with HTTP access to port 8042
- A single 2.3 KB DICOM file is sufficient; no looping or sustained traffic needed
- Particularly severe in clinical environments where Orthanc is used for patient imaging

#### Root Cause
DCMTK's `DcmItem::read()` / `DcmSequenceOfItems::read()` recursion has no depth limit.
Orthanc's `DatasetToJson()` / `ElementToJson()` also has no depth limit. The fix requires adding a depth check in both:

```cpp
// In DcmItem::read() / DcmSequenceOfItems::read() (DCMTK upstream fix)
if (nestingDepth > MAX_SQ_DEPTH) return EC_InvalidTag;

// In Orthanc FromDcmtkBridge.cpp
void DatasetToJson(..., unsigned int depth) {
    if (depth > 64) {
        target.append(Json::objectValue);
        return;
    }
    ...
}
```


#### Remediation
- **Orthanc:** Add depth limit (~64 levels) to `DatasetToJson()` / `ElementToJson()` in  `OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp`
- **DCMTK:** Add depth limit to `DcmItem::read()` (report to DCMTK maintainers separately)


---
Comment 1 Alain Mazy 2026-05-05 17:49:19 CEST
Thanks for the report.

Could you provide us with the full python code to generate a file with a defect so I can increase the nesting level to trigger the issue on my dev PC ?

I just tested it with the mainline (post 1.12.11) and I have no crash in the DCMTK although there is indeed a deep recursion.

I can see that DCMTK has already fixed the nesting issue last month but that won't be easy to :
https://github.com/DCMTK/dcmtk/commit/885ff0f10372bd589b5f44cea974f28a3964cb0f

Thanks for your help
Comment 2 elp3pinill0 2026-05-05 18:19:57 CEST
Created attachment 150 [details]
poc_deep_sequence python script to generate dicoms

find attached the .py to generate DICOMs with more more sequence nested.
Comment 3 elp3pinill0 2026-05-05 18:26:06 CEST
(In reply to Alain Mazy from comment #1)
> Thanks for the report.
> 
> Could you provide us with the full python code to generate a file with a
> defect so I can increase the nesting level to trigger the issue on my dev PC
> ?
> 
> I just tested it with the mainline (post 1.12.11) and I have no crash in the
> DCMTK although there is indeed a deep recursion.
> 
> I can see that DCMTK has already fixed the nesting issue last month but that
> won't be easy to :
> https://github.com/DCMTK/dcmtk/commit/
> 885ff0f10372bd589b5f44cea974f28a3964cb0f
> 
> Thanks for your help

True, dimcon_nested_50 might be too small for some devices, try with a bigger one. I've attached the python script so you can generate bigger nested sequences. I just wanted to mention I just tested in a docker with 1.12.10 version and it crashed at 3500-4000 more or less but you can try bigger. I dont know if the outcome will be the same with version 1.12.11.
Comment 4 Alain Mazy 2026-05-06 08:51:15 CEST
Thanks !  I can reproduce it now.  Let's fix it ...

Do you wish to be credited for this finding in the release notes ?
If yes, can you provide your Name and Organization.

Thanks again !
Comment 5 elp3pinill0 2026-05-06 10:06:44 CEST
(In reply to Alain Mazy from comment #4)
> Thanks !  I can reproduce it now.  Let's fix it ...
> 
> Do you wish to be credited for this finding in the release notes ?
> If yes, can you provide your Name and Organization.
> 
> Thanks again !

sure, you can call me Jose Lopez Martinez aka elpe_pinillo :) and I work as offensive security researcher at Deloitte. 

Would you mind if I apply for CVE as well? If it's ok do you have any preference for a CNA in particular? Or if you prefer to do it yourselve... just let me know.

Thanks for your time.
Comment 6 Alain Mazy 2026-05-06 10:30:21 CEST
Feel free to apply for a CVE (I prefer you take care of it).

I already have an account on https://kb.cert.org/vince but don't hesitate to select another platform
Comment 7 Alain Mazy 2026-05-06 12:42:08 CEST
Fix implemented in this commit: https://orthanc.uclouvain.be/hg/orthanc/rev/bae99026ca97

I'm waiting for the CVE number to integrate in the release notes before closing this issue.
Comment 8 elp3pinill0 2026-05-06 12:46:35 CEST
It's ok I can apply for it, I will let you know as soon as I get it. Keep in touch.
Comment 9 elp3pinill0 2026-06-02 09:06:12 CEST
CVE-2026-10528 assigned.