diff --git a/README.md b/README.md index 52d9052c..47112e48 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/dicom_files/0.dcm b/dicom_files/0.dcm new file mode 100644 index 00000000..cdd8be89 Binary files /dev/null and b/dicom_files/0.dcm differ diff --git a/dicom_files/1.dcm b/dicom_files/1.dcm new file mode 100644 index 00000000..8083169f Binary files /dev/null and b/dicom_files/1.dcm differ diff --git a/dicom_files/2.dcm b/dicom_files/2.dcm new file mode 100644 index 00000000..b5ab01f7 Binary files /dev/null and b/dicom_files/2.dcm differ diff --git a/dicom_files/3.dcm b/dicom_files/3.dcm new file mode 100644 index 00000000..ffb319ee Binary files /dev/null and b/dicom_files/3.dcm differ diff --git a/dicom_files/4.dcm b/dicom_files/4.dcm new file mode 100644 index 00000000..aa5e9fdc Binary files /dev/null and b/dicom_files/4.dcm differ diff --git a/dicom_files/5.dcm b/dicom_files/5.dcm new file mode 100644 index 00000000..5a6bbdb9 Binary files /dev/null and b/dicom_files/5.dcm differ diff --git a/dicom_files/6.dcm b/dicom_files/6.dcm new file mode 100644 index 00000000..9b9bfff4 Binary files /dev/null and b/dicom_files/6.dcm differ diff --git a/dicom_files/7.dcm b/dicom_files/7.dcm new file mode 100644 index 00000000..5200d8a2 Binary files /dev/null and b/dicom_files/7.dcm differ diff --git a/dicom_files/8.dcm b/dicom_files/8.dcm new file mode 100644 index 00000000..49c68c5e Binary files /dev/null and b/dicom_files/8.dcm differ diff --git a/dicom_files/9.dcm b/dicom_files/9.dcm new file mode 100644 index 00000000..8843b684 Binary files /dev/null and b/dicom_files/9.dcm differ diff --git a/dicom_files/test_file.dcm b/dicom_files/test_file.dcm deleted file mode 100644 index 5e573697..00000000 Binary files a/dicom_files/test_file.dcm and /dev/null differ diff --git a/dicom_files/test_file1.dcm b/dicom_files/test_file1.dcm deleted file mode 100644 index 8c00c499..00000000 Binary files a/dicom_files/test_file1.dcm and /dev/null differ diff --git a/dicom_files/test_file2.dcm b/dicom_files/test_file2.dcm deleted file mode 100644 index d44eb182..00000000 Binary files a/dicom_files/test_file2.dcm and /dev/null differ diff --git a/dicom_files/test_file3.dcm b/dicom_files/test_file3.dcm deleted file mode 100644 index 21ab37d6..00000000 Binary files a/dicom_files/test_file3.dcm and /dev/null differ diff --git a/dicom_files/test_file4.dcm b/dicom_files/test_file4.dcm deleted file mode 100644 index 7502bd11..00000000 Binary files a/dicom_files/test_file4.dcm and /dev/null differ diff --git a/dicom_files/test_file5.dcm b/dicom_files/test_file5.dcm deleted file mode 100644 index f64dbb93..00000000 Binary files a/dicom_files/test_file5.dcm and /dev/null differ diff --git a/dicom_server/dicomhawk.py b/dicom_server/dicomhawk.py index ec1fd794..fa295352 100644 --- a/dicom_server/dicomhawk.py +++ b/dicom_server/dicomhawk.py @@ -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' @@ -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"] @@ -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 @@ -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) @@ -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))) @@ -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)) @@ -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)) @@ -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)) @@ -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): @@ -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 @@ -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() \ No newline at end of file diff --git a/dicom_server/requirements.txt b/dicom_server/requirements.txt index 7dd80e4c..4c2df3f0 100644 --- a/dicom_server/requirements.txt +++ b/dicom_server/requirements.txt @@ -3,4 +3,5 @@ gunicorn==20.1.0 pydicom==2.4.4 pynetdicom==2.0.2 numpy==1.23.5 - +requests +python-dotenv \ No newline at end of file diff --git a/logs/dicom_server.log b/logs/dicom_server.log index 9adaf5eb..c5e18614 100644 --- a/logs/dicom_server.log +++ b/logs/dicom_server.log @@ -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 \ No newline at end of file diff --git a/rest/dicom_file_creator.py b/rest/dicom_file_creator.py index a75de7ea..a1e73074 100644 --- a/rest/dicom_file_creator.py +++ b/rest/dicom_file_creator.py @@ -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" @@ -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 @@ -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)