Eine Backup-Strategie, die mit maximalem Hardware-Einsatz regelmäßig einen korrumpierten Datenbestand sichert, wiegt die Nutzerin in trügerischer Sicherheit. Um unautorisierte Manipulationen von Dateien vor einem Backup zu erkennen, bietet sich daher ein Integritätsmonitor auf Basis kryptographischer Hashes an. Zunächst werden die Hashes aller relevanten Dateien gebildet und gespeichert (in der Hoffnung, dass zu diesem Zeitpunkt noch keine Ransomware auf dem System gewütet hat):
#!/usr/local/bin/python3 import hashlib import json import os hashpath = '/hashes.json' docpath = '/home/snafu/documents' def get_hash(file_path): myhash = hashlib.sha256() with open(file_path,'rb') as file: content = file.read() myhash.update(content) return myhash.hexdigest() def create_baseline(): with open(hashpath, 'r') as f: hash_dict = json.load(f) for current_dir, subdirs, files in os.walk(docpath): for filename in files: relative_path = os.path.join(current_dir, filename) absolute_path = os.path.abspath(relative_path) barename, extension = os.path.splitext(filename) if not filename.startswith('.') and os.path.isfile(absolute_path): if absolute_path in hash_dict: if hash_dict[absolute_path] == get_hash(absolute_path): continue else: selection = input(f'Hash for file {absolute_path} does not match baseline. Update hash and proceed (y) or cancel (n)? ') if selection == 'n': print(f'Not updating hash for file {absolute_path}') continue print(f'Updating hash/creating new hash for file {absolute_path}...') hash_dict[absolute_path] = get_hash(absolute_path) for absolute_path in list(hash_dict): if not os.path.isfile(absolute_path): print(f'Removing hash for deleted file {absolute_path}...') del hash_dict[absolute_path] with open(hashpath, 'w') as f: json.dump(hash_dict, f) if __name__ == '__main__': create_baseline()
Ab dem zweiten Durchlauf enthält die Datei hashes.json
den Maßstab für nicht manipulierte Dateien; jede Änderung muss bei der Aktualisierung von hashes.json
manuell akzeptiert werden. Diese Herangehensweise eignet sich offensichtlich nur für relativ statische Verzeichnisse, in denen vor allem neue Dateien abgelegt werden, und sie ist inkompatibel mit der automatischen Aktualisierung von Dateien (launchctl unload my.localhotel.serversync.plist
).
Mit Hilfe von hashes.json
kann jedenfalls zum Backup-Zeitpunkt geprüft werden, ob alle Dateien integer sind (unter Verwendung der Funktion get_hash()
aus dem obigen Skript):
#!/usr/local/bin/python3 import os import json import pathlib from fhashlib import get_hash import scriptmail hashpath = '/hashes.json' docpath = '/home/snafu/documents' def compare_to_baseline(): print('Checking for hash mismatches...') warnings = '' with open(hashpath, 'r') as f: hash_baseline = json.load(f) for current_dir, subdirs, files in os.walk(docpath): for filename in files: relative_path = os.path.join(current_dir, filename) absolute_path = os.path.abspath(relative_path) barename, extension = os.path.splitext(filename) if not filename.startswith('.') and os.path.isfile(absolute_path): if absolute_path in hash_baseline and hash_baseline[absolute_path] != get_hash(absolute_path): warnings += f'File {absolute_path} has been tampered with.\n' if warnings: subject = 'Alert – file hash mismatch!' message = warnings return_code = 1 else: subject = 'No hash mismatch.' message = subject return_code = 0 scriptmail.send_message(subject, message) return return_code
Ein gesundes Misstrauen prüft aber nicht nur bekannte Dateien, sondern sucht auch nach zwielichtigen Mustern (*want your files back.*
):
patternpath = '/patterns.json' with open(patternpath, 'r') as f: patterns = json.load(f) def check_files(): print('Checking for ransomware file patterns...') warnings = '' p = pathlib.Path(docpath) for pattern in patterns: result_list = sorted(p.rglob(pattern)) if result_list: warnings += f'Ransomware filename pattern {pattern} is present in {docpath}:\n{result_list}\n' if warnings: subject = 'Alert – ransomware file pattern detected!' message = warnings return_code = 1 else: subject = 'No ransomware file pattern detected.' message = subject return_code = 0 scriptmail.send_message(subject, message) return return_code
Die beiden Funktionen check_files()
und compare_to_baseline()
können in rbackup integriert werden und verhindern bei Bedarf die Datensicherung:
import ransomguard def ransom(self): if hasattr(self, 'ransomware_check'): if ransomguard.check_files(): print('Ransomware pattern found, not backing up!') exit(1) if ransomguard.compare_to_baseline(): print('Hash mismatch, not backing up!') exit(1) print('Proceeding to backup...')
Der zeitliche Aufwand für check_files()
lässt sich wieder einsparen, indem man ohne Integritätseinbuße bei der Dateiübertragung auf den rechenintensiven rsync-Parameter --checksum
–
Generating the checksums means that both sides will expend a lot of disk I/O reading all the data in the files in the transfer, so this can slow things down significantly (and this is prior to any reading that will be done to transfer changed files)
– verzichtet, und die Verwendung von SHA256 statt MD5 in get_hash()
spart ebenfalls wertvolle Sekunden (create_baseline()
– MD5: 4.50s user 0.76s system 72% cpu 7.263 total | SHA256: 1.11s user 0.84s system 48% cpu 4.035 total).
Ein gezielter Angriff würde sich den beiden Verteidigungslinien widmen und die Skripte selbst bzw. die JSON-Dateien manipulieren, aber ich vertraue darauf, dass ich kein lohnendes Ziel für PLA Unit 61398 oder Fancy Bear bin.