Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ DICOMHawk provides detailed logging to help you monitor and analyze interactions
- **Server Logs**: Access logs to see detailed information about DICOM associations and DIMSE messages.
- **Simplified Logs**: View simplified logs for a quick overview of events.

### Email alerts/custom canary token implementation
Implementing http based canary tokens for getting attacker info is quite impossible directly via the server itself, currently it uses smtp to send custom alerts to any email via an app password stored in a `.env` file.


You can view these logs through the web interface or by accessing the log files directly within the log server container.

```bash
Expand Down
Binary file added dicom_files/0.dcm
Binary file not shown.
Binary file added dicom_files/1.dcm
Binary file not shown.
Binary file added dicom_files/2.dcm
Binary file not shown.
Binary file added dicom_files/3.dcm
Binary file not shown.
Binary file added dicom_files/4.dcm
Binary file not shown.
Binary file added dicom_files/5.dcm
Binary file not shown.
Binary file added dicom_files/6.dcm
Binary file not shown.
Binary file added dicom_files/7.dcm
Binary file not shown.
Binary file added dicom_files/8.dcm
Binary file not shown.
Binary file added dicom_files/9.dcm
Binary file not shown.
Binary file removed dicom_files/test_file.dcm
Binary file not shown.
Binary file removed dicom_files/test_file1.dcm
Binary file not shown.
Binary file removed dicom_files/test_file2.dcm
Binary file not shown.
Binary file removed dicom_files/test_file3.dcm
Binary file not shown.
Binary file removed dicom_files/test_file4.dcm
Binary file not shown.
Binary file removed dicom_files/test_file5.dcm
Binary file not shown.
43 changes: 36 additions & 7 deletions dicom_server/dicomhawk.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
import json
import time
import random
from requests import get
from dotenv import load_dotenv

load_dotenv()
import os



# Set up logging
log_directory = '/app/logs'
Expand Down Expand Up @@ -61,8 +68,8 @@ def log_simplified_message(message):
except (TypeError, ValueError) as e:
exception_logger.error(f"Failed to log simplified message: {message} - {e}")

# Function to generate fake DICOM files with Danish-like names
def create_fake_dicom_files(directory, num_files=10):

def create_fake_dicom_files(directory, num_files=10, canary_token_url=os.getenv('canary_url')):
os.makedirs(directory, exist_ok=True)

first_names = ["Frederik", "Sofie", "Lukas", "Emma", "William", "Ida", "Noah", "Anna", "Oliver", "Laura"]
Expand Down Expand Up @@ -94,6 +101,14 @@ def create_fake_dicom_files(directory, num_files=10):
ds.SamplesPerPixel = 1
ds.PhotometricInterpretation = "MONOCHROME2"
ds.PixelData = b'\x00' * (ds.Rows * ds.Columns * 2)

#canary test

ds.ImageComments = f"This DICOM file is for testing. Please visit: {canary_token_url}"
ds.StudyDescription = f"Dummy Study - Visit {canary_token_url}"
ds.SeriesDescription = f"Dummy Series - Report issues to {canary_token_url}"
ds.PatientComments = f"Contact us at {canary_token_url} if you have questions."


ds.is_little_endian = True
ds.is_implicit_VR = False
Expand Down Expand Up @@ -128,6 +143,15 @@ def load_dicom_files(directory):
})
return dicom_files

canary_token_url = os.getenv('canary_token_url') #fetch the URL from the env file
dicom_directory = 'dicom_files'
if not os.path.exists(dicom_directory):
create_fake_dicom_files(dicom_directory,canary_token_url=canary_token_url)
dicom_datasets = load_dicom_files(dicom_directory)

# Dictionary to store association session IDs
assoc_sessions = {}

dicom_directory = 'dicom_files'
if not os.path.exists(dicom_directory):
create_fake_dicom_files(dicom_directory)
Expand Down Expand Up @@ -161,6 +185,7 @@ def handle_assoc(event):
"msg": "Client",
"timestamp": datetime.now().isoformat()
})


def handle_release(event):
assoc_id = assoc_sessions.pop(event.assoc, str(int(time.time() * 1000000)))
Expand All @@ -177,6 +202,7 @@ def handle_release(event):
"timestamp": datetime.now().isoformat()
})


def handle_find(event):
assoc_id = assoc_sessions.get(event.assoc, str(int(time.time() * 1000000)))
find_id = str(int(time.time() * 1000000))
Expand Down Expand Up @@ -226,6 +252,7 @@ def handle_find(event):




def handle_store(event):
assoc_id = assoc_sessions.get(event.assoc, str(int(time.time() * 1000000)))
store_id = str(int(time.time() * 1000000))
Expand Down Expand Up @@ -255,8 +282,12 @@ def handle_echo(event):
"msg": "Received",
"timestamp": datetime.now().isoformat()
})



return 0x0000


def handle_move(event):
assoc_id = assoc_sessions.get(event.assoc, str(int(time.time() * 1000000)))
move_id = str(int(time.time() * 1000000))
Expand All @@ -278,6 +309,7 @@ def handle_move(event):
ds.StudyInstanceUID = generate_uid()
ds.SeriesInstanceUID = generate_uid()
ds.SOPClassUID = CTImageStorage

yield 1, ds

def handle_get(event):
Expand All @@ -295,7 +327,7 @@ def handle_get(event):
"timestamp": datetime.now().isoformat()
})
remaining_subops = len(dicom_datasets)

# Yield the number of remaining sub-operations as the first item
yield remaining_subops

Expand Down Expand Up @@ -336,7 +368,4 @@ def start_dicom_server():
return
ae.start_server(('172.29.0.3', dicom_port), evt_handlers=handlers)




start_dicom_server()
start_dicom_server()
3 changes: 2 additions & 1 deletion dicom_server/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ gunicorn==20.1.0
pydicom==2.4.4
pynetdicom==2.0.2
numpy==1.23.5

requests
python-dotenv
2 changes: 1 addition & 1 deletion logs/dicom_server.log
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,4 @@ Data Set : None
C-ECHO request received
Association requested from 127.0.0.1:13964
Association Released
Association released from 127.0.0.1:13964
Association released from 127.0.0.1:13964
32 changes: 24 additions & 8 deletions rest/dicom_file_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
from pydicom.uid import generate_uid
import numpy as np
import datetime
from dotenv import load_dotenv
import os

def create_dummy_dicom(filename):
load_dotenv()

def create_dummy_dicom(filepath, canary_token_url):
file_meta = Dataset()
file_meta.MediaStorageSOPClassUID = pydicom.uid.CTImageStorage
file_meta.MediaStorageSOPInstanceUID = generate_uid()
file_meta.ImplementationClassUID = pydicom.uid.PYDICOM_IMPLEMENTATION_UID
file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian

ds = FileDataset(filename, {}, file_meta=file_meta, preamble=b"\0" * 128)
ds = FileDataset(filepath, {}, file_meta=file_meta, preamble=b"\0" * 128)
ds.PatientName = "Test^Patient"
ds.PatientID = "123456"
ds.Modality = "CT"
Expand All @@ -24,7 +28,11 @@ def create_dummy_dicom(filename):
ds.ContentDate = ds.StudyDate
ds.ContentTime = ds.StudyTime

# Set some values for the image
ds.ImageComments = f"This DICOM file is for testing. If you see this, please visit: {canary_token_url}"
ds.StudyDescription = f"Dummy Study - Visit {canary_token_url}"
ds.SeriesDescription = f"Dummy Series - Report issues to {canary_token_url}"
ds.PatientComments = f"Contact us at {canary_token_url} if you have questions."

ds.Rows = 512
ds.Columns = 512
ds.BitsAllocated = 16
Expand All @@ -35,13 +43,21 @@ def create_dummy_dicom(filename):
ds.PhotometricInterpretation = "MONOCHROME2"
ds.PixelData = (np.random.rand(512, 512) * 4095).astype(np.uint16).tobytes()

# Calculate group lengths (if needed)
ds.file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian
ds.is_little_endian = True
ds.is_implicit_VR = False

# Save the file
pydicom.filewriter.dcmwrite(filename, ds, write_like_original=False)
print(f"DICOM file '{filename}' created successfully.")
pydicom.filewriter.dcmwrite(filepath, ds, write_like_original=False)
print(f"DICOM file '{filepath}' created successfully.")


folder = "dicom_files"
os.makedirs(folder, exist_ok=True)

# Example Usage (Replace with your actual Canary Token URL)
canary_url = os.getenv('canary_url')


create_dummy_dicom("test_file.dcm")
for i in range(10):
output_path = os.path.join(folder, f"test_file{i}.dcm")
create_dummy_dicom(output_path, canary_url)