...
1import select
2import re
3import difflib
4import subprocess
5import datetime
6
7config_path = '/samhain/config/samhainrc'
8mount_dir = '/ien_fs/proc/self/mounts'
9inotify_processes = {}
10
11def get_monitored_dirs(path):
12 with open(path) as f:
13 lines = f.readlines()
14
15 regex = re.compile(r'dir.*=.*99(/ien_fs/.*)')
16 paths = []
17 for line in lines:
18 m = regex.search(line)
19 if m:
20 paths.append(m.group(1))
21
22 return paths
23
24def process_mounts(old_mounts, new_mounts, monitored_paths):
25 actions = {'+':'addition', '-':'deletion'}
26 output = [m for m in difflib.ndiff(old_mounts, new_mounts) if m[0] != ' ']
27
28 for mount in output:
29 s = mount.split()
30
31 action = actions.get(s[0], '') if old_mounts else 'existence'
32 # Very rare case where this can fail. Only randomly saw this, but it crashed the program.
33 try:
34 path = s[2]
35 mount_type = s[3]
36 except IndexError:
37 path = "Unknown"
38 mount_type = "Unknown"
39
40
41 result = [p for p in monitored_paths if path.startswith(p)]
42 if result:
43 time_format = '%Y-%m-%dT%H:%M:%S%z'
44 date = datetime.datetime.now().astimezone().replace(microsecond=0).strftime(time_format)
45 msg = f'CRIT : [{date}] msg=<Unusual mount activity detected>, path={path}, action={action},mount_type={mount_type}'
46 print(msg)
47
48 if action == 'addition':
49 # https://stackoverflow.com/a/13143013/17126721
50 # Brilliant fix to orphaned shell command
51 mask = 'modify,move,delete,create'
52 cmd = f"exec inotifywait -m {path} -r -q -e {mask} --timefmt '{time_format}' --format 'CRIT : [%T] msg=<Unusual mount activity detected>, path=%w%f, inotify_event=%:e'"
53 # No problem with using shell=True here IMO.
54 # We don't do anything special with it, and the path will be absolute
55 inotify_processes[path] = subprocess.Popen(cmd, shell=True)
56 elif action == 'deletion' and path in inotify_processes:
57 inotify_processes[path].terminate()
58 inotify_processes.pop(path)
59
60# Grab list of monitored dirs from the samhain config file
61paths = get_monitored_dirs(config_path)
62
63# Initial run through before loop
64# If a suspicious mount exists at this point, we do not need to set up our own watches for this one as Samhain will cover these
65f = open(mount_dir)
66mounts = f.readlines()
67process_mounts([], mounts, paths)
68
69
70
71# Loop forever without constantly polling the file
72while True:
73 r,w,x = select.select([],[],[f])
74 f.seek(0)
75
76 new_mounts = f.readlines()
77 process_mounts(mounts, new_mounts, paths)
78 # We maintain the list of last known mounts to diff on
79 mounts = new_mounts
View as plain text