From 56d5897de454a7efe9dc07f5d12fa199cea467cf Mon Sep 17 00:00:00 2001 From: Sune Koch Hansen Date: Fri, 13 Dec 2024 14:22:18 +0100 Subject: [PATCH] =?UTF-8?q?issue=20#2=20-=20tilf=C3=B8j=20sikkerhedsscript?= =?UTF-8?q?s.=20Disse=20vises=20under=20fanen=20'Sikkerhedsscripts'=20i=20?= =?UTF-8?q?admin-site=20som=20f=C3=B8lge=20af=20'metadata.security=3Dtrue'?= =?UTF-8?q?=20i=20md-filerne.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- detect_keyboard_event.md | 45 ++++++++++ detect_sudo_event.md | 26 ++++++ detect_user_expired_event.md | 22 +++++ scripts/detect_keyboard_event.py | 129 +++++++++++++++++++++++++++ scripts/detect_sudo_event.py | 80 +++++++++++++++++ scripts/detect_user_expired_event.py | 98 ++++++++++++++++++++ 6 files changed, 400 insertions(+) create mode 100644 detect_keyboard_event.md create mode 100644 detect_sudo_event.md create mode 100644 detect_user_expired_event.md create mode 100644 scripts/detect_keyboard_event.py create mode 100644 scripts/detect_sudo_event.py create mode 100644 scripts/detect_user_expired_event.py diff --git a/detect_keyboard_event.md b/detect_keyboard_event.md new file mode 100644 index 0000000..b4f97e3 --- /dev/null +++ b/detect_keyboard_event.md @@ -0,0 +1,45 @@ +--- +title: "Detekter nytilsluttet keyboard" +parent: "Sikkerhed" +source: scripts/detect_keyboard_event.py +compatibility: + - "22.04" + - "BorgerPC" +metadata: + security: true +--- + +## Beskrivelse +Sikkerhedsscriptet identificerer ny-tilsluttede keyboards helt tilbage fra sidste gang systemet blev tjekket. Findes det resulterer det i en sikkerhedshændelse. +At fjerne et keyboard giver IKKE en advarsel. + +BEMÆRK: Dette sikkerhedsscript er afhængigt af, om USB-enheden identificerer sig selv som et keyboard. Af denne grund, vil vi - såfremt det er muligt i jeres anvendelse - anbefale i stedet at benytte scriptet, der låser maskinen ved indsættelse af ALLE USB-enheder ("Bloker for login ved USB-event"), sammen med sikkerhedscriptet der giver en advarsel, når Borger-kontoen er blevet låst ("Detekter låst Borgerkonto"). + +Dette sikkerhedsscript virker både på OS2borgerPC og OS2borgerPC Kiosk, men vi mener det er mest relevant på førstnævnte, ift. faren ved keyloggers på publikumsmaskiner. + +VIGTIG BEMÆRKNING: +Sikkerhedsscriptet er kun aktivt, når maskinen er tændt! + +Af ovenstående grund er det centralt, at besøgende ikke kan tilgå maskinerne mens de er slukket, uden at det opdages. + +Derfor foreslår vi at kombinere det med følgende tre scripts: + +1. Scriptet "Desktop - Fjern Luk Ned og Genstart fra sessionmenuen og blokér for nedlukning via systempolitik" + +2. Scriptet "OS2borgerPC - Blokér for login ved hård nedlukning" + +3. Sikkerhedsscriptet "Detekter låst Borgerkonto" + +Sammen betyder de tre: +1. At brugeren ikke kan lukke maskinen ned fra menuen. +2. Trykker de på knappen for at lukke den ned, eller hiver de strømstikket ud, så låses der for login for Borger-kontoen. (Da de potentielt være have indsat en keylogger) +3. Hvis Borger-kontoen låses modtager man, pga. script tre, en sikkerhedshændelse + +Ønsker man slet ikke at Borgere skal kunne rode med USB-enheder så kan man bruge scriptet +"OS2borgerPC - Blokér for login ved USB-event" +Med dette andet script vil både at tilføje eller fjerne en USB-enhed mens maskinen er tændt, betyde at Borgeren logges øjeblikkeligt ud, og at der derefter låses for login. + +Når Borger-kontoen er låst kan man fra adminsitet køre scriptet "OS2borgerPC - Sæt bruger aktiv efter blokeret login (lås op)" for at åbne for login på Borger-kontoen igen. + +## Parametre +Ingen \ No newline at end of file diff --git a/detect_sudo_event.md b/detect_sudo_event.md new file mode 100644 index 0000000..aa665c4 --- /dev/null +++ b/detect_sudo_event.md @@ -0,0 +1,26 @@ +--- +title: "Detekter sudo-kørsel" +parent: "Sikkerhed" +source: scripts/detect_sudo_event.py +compatibility: + - "22.04" + - "BorgerPC" +metadata: + security: true +--- + +## Beskrivelse +Dette Sikkerhedsscript giver en sikkerhedshændelse ved sudo-kørsel. + +Dette script virker både på OS2borgerPC og OS2borgerPC Kiosk. + +Der gives både en advarsel hvis sudo køres med succes, hvis det fejler pga. det køres fra Borger fremfor superuser, eller den indtastede kode er forkert. +Nærmere specifikt: +sudo-kommandoen giver én tre forsøg på at indtaste koden - taster man forkert tre gange vil det give en advarsel. Taster man korrekt vil det ligeledes give en advarsel. + +Derfor: Hvis du har tilføjet en regel for en maskine, og du selv er inde på den fra superuser, vil der også komme en advarsel, hvis du kører sudo. + +Når du modtager en advarsel vil der ofte stå USER=root i beskeden. Dette betyder ikke, at brugeren allerede har root-adgang (dvs. administrator-adgang), men alene at brugeren, personen forsøger at køre kommandoer som, er administrator-kontoen. + +## Parametre +Ingen \ No newline at end of file diff --git a/detect_user_expired_event.md b/detect_user_expired_event.md new file mode 100644 index 0000000..fdea454 --- /dev/null +++ b/detect_user_expired_event.md @@ -0,0 +1,22 @@ +--- +title: "Detekter låst Borgerkonto" +parent: "Sikkerhed" +source: scripts/detect_user_expired_event.py +compatibility: + - "22.04" + - "BorgerPC" +metadata: + security: true +--- + +## Beskrivelse +Dette Sikkerhedsscript giver en Sikkerhedshændelse hvis Borger bliver låst ude/sat til udløbet. + +Dette script virker kun på OS2borgerPC, ikke OS2borgerPC Kiosk. + +Bruges sammen med en eller begge af følgende: +- "Bloker for login ved USB-event" + "Sæt bruger aktiv efter blokeret login" +- "OS2borgerPC - Bloker for login ved hård nedlukning" + "Sæt bruger aktiv efter blokeret login" + +## Parametre +Ingen diff --git a/scripts/detect_keyboard_event.py b/scripts/detect_keyboard_event.py new file mode 100644 index 0000000..3211553 --- /dev/null +++ b/scripts/detect_keyboard_event.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 + +""" +Security Script for finding USB keyboard attachment events +""" + +import sys +from datetime import datetime, timedelta +import re + +__copyright__ = "Copyright 2017-2024 Magenta ApS" +__license__ = "GPL" + + +def log_read(last_security_check, log_name): + """Search a (system) log for events that occurred + between "last_security_check" and now.""" + log_event_tuples = [] + now = datetime.now() + + with open(log_name) as f: + for line in f.readlines(): + line = str(line.replace("\0", "")) + log_event_timestamp = line[:15] + log_event = line.strip("\n") + # convert from log event timestamp to security event log timestamp. + log_event_datetime = datetime.strptime( + str(now.year) + " " + log_event_timestamp, "%Y %b %d %H:%M:%S" + ) + security_event_log_timestamp = datetime.strftime( + log_event_datetime, "%Y%m%d%H%M%S" + ) + # Detect lines from within the last x seconds to now. + if last_security_check <= log_event_datetime <= now: + log_event_tuples.append((security_event_log_timestamp, log_event)) + + return log_event_tuples + + +def csv_writer(security_events): + """Write security events to security events file.""" + with open("/etc/os2borgerpc/security/securityevent.csv", "at") as csvfile: + for timestamp, security_problem_uid, log_event in security_events: + event_line = log_event.replace("\n", " ").replace("\r", "").replace(",", "") + csvfile.write(f"{timestamp},{security_problem_uid},{event_line}\n") + + +def filter_duplicate_events(security_events): + """This function filters duplicate events related to + the same keyboard""" + + unique_tuples = [] + unique_keyboards = [] + + for security_event in security_events: + # This identifier is based on the ID of the USB device. + # The ID is identical for identical USB devices so + # if two identical keyboards are inserted simultaneously, + # only one event will be generated. + regex = ( + r"[0-9a-z]{4}:[0-9a-z]{4}:[0-9a-z]{4}" + r"(?!.*/[0-9a-z]{4}:[0-9a-z]{4}:[0-9a-z]{4})" + ) + match = re.search(regex, security_event[2], flags=re.IGNORECASE) + # Keyboard event lines should always contain a match, but in order + # to prevent possibly overlooking a relevant event, we always include + # events with no match + if match: + keyboard_identifier = match.group(0) + if keyboard_identifier not in unique_keyboards: + unique_tuples.append(security_event) + unique_keyboards.append(keyboard_identifier) + else: # This part should never be relevant, but it is here just in case + unique_tuples.append(security_event) + + return unique_tuples + + +# The file to inspect for events +log_name = "/var/log/syslog" + +now = datetime.now() +# The default value in case lastcheck.txt is nonexisting or empty: +last_security_check = now - timedelta(hours=24) +try: + with open("/etc/os2borgerpc/security/lastcheck.txt", "r") as fp: + timestamp = fp.read() + if timestamp: + last_security_check = datetime.strptime(timestamp, "%Y%m%d%H%M%S") +except IOError: + pass + +log_event_tuples = log_read(last_security_check, log_name) + +security_problem_uid_template_var = "%SECURITY_PROBLEM_UID%" + +# Match keyboard events that are after 9.9999 seconds of boot up +# (so we don't match upstart keyboard events), +# Also remove system control and consumer control entries: +# The reason is that some keyboards add three keyboard entries when connected. +# Example from inserting a keyboard once: +# Jun 28 14:24:43 kbh-nuc-venstre kernel: [ 1948.130701] input: Logitech HID compliant keyboard as /devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0/0003:046D:C30E.000A/input/input25 +# Jun 28 14:24:43 kbh-nuc-venstre kernel: [ 1948.264053] input: Logitech HID compliant keyboard System Control as /devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.1/0003:046D:C30E.000B/input/input27 +# Jun 28 14:24:43 kbh-nuc-venstre kernel: [ 1948.204460] input: Logitech HID compliant keyboard Consumer Control as /devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.1/0003:046D:C30E.000B/input/input26 +# Fortunately it seems Consumer Control and System Control aren't Logitech specific, as we've seen the exact same with Lenovo keyboards. +# The second regex matches certain keyboards that do not generate a log line with "input:" and "Keyboard" +# Most regular keyboards also generate a log line that matches the second regex, but +# the duplicate events are removed by the filtering +regexes = [ + r".*\[[ ]{0,3}[0-9]{2,}\..*\] input: .*Keyboard " + r"(?!((mouse )?system control))(?!((mouse )?consumer control)).*", + r".*\[[ ]{0,3}[0-9]{2,}\..*\].*input,hidraw.*keyboard (?!mouse)", +] + +# Filter log_event_tuples based on regex matches and put them +# on the form the admin site expects: +# (timestamp, security_problem_uid, summary) +log_event_tuples = [ + (log_timestamp, security_problem_uid_template_var, log_event) + for (log_timestamp, log_event) in log_event_tuples + if any([re.search(regex, log_event, flags=re.IGNORECASE) for regex in regexes]) +] + +log_event_tuples = filter_duplicate_events(log_event_tuples) + +if not log_event_tuples: + sys.exit() + +csv_writer(log_event_tuples) \ No newline at end of file diff --git a/scripts/detect_sudo_event.py b/scripts/detect_sudo_event.py new file mode 100644 index 0000000..4426a73 --- /dev/null +++ b/scripts/detect_sudo_event.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +""" +Security Script for finding sudo events +""" + +import sys +from datetime import datetime, timedelta +import re + +__copyright__ = "Copyright 2017-2024 Magenta ApS" +__license__ = "GPL" + + +def log_read(last_security_check, log_name): + """Search a (system) log for events that occurred + between "last_security_check" and now.""" + log_event_tuples = [] + now = datetime.now() + + with open(log_name) as f: + for line in f.readlines(): + line = str(line.replace("\0", "")) + log_event_timestamp = line[:15] + log_event = line.strip("\n") + # convert from log event timestamp to security event log timestamp. + log_event_datetime = datetime.strptime( + str(now.year) + " " + log_event_timestamp, "%Y %b %d %H:%M:%S" + ) + security_event_log_timestamp = datetime.strftime( + log_event_datetime, "%Y%m%d%H%M%S" + ) + # Detect lines from within the last x seconds to now. + if last_security_check <= log_event_datetime <= now: + log_event_tuples.append((security_event_log_timestamp, log_event)) + + return log_event_tuples + + +def csv_writer(security_events): + """Write security events to security events file.""" + with open("/etc/os2borgerpc/security/securityevent.csv", "at") as csvfile: + for timestamp, security_problem_uid, log_event in security_events: + event_line = log_event.replace("\n", " ").replace("\r", "").replace(",", "") + csvfile.write(f"{timestamp},{security_problem_uid},{event_line}\n") + + +# The file to inspect for events +log_name = "/var/log/auth.log" + +now = datetime.now() +# The default value in case lastcheck.txt is nonexisting or empty: +last_security_check = now - timedelta(hours=24) +try: + with open("/etc/os2borgerpc/security/lastcheck.txt", "r") as fp: + timestamp = fp.read() + if timestamp: + last_security_check = datetime.strptime(timestamp, "%Y%m%d%H%M%S") +except IOError: + pass + +log_event_tuples = log_read(last_security_check, log_name) + +security_problem_uid_template_var = "%SECURITY_PROBLEM_UID%" +# Ignore if not a sudo event or if a sudo event from root +regexes = [r"sudo:(?!\s*root).*COMMAND"] + +# Filter log_event_tuples based on regex matches and put them +# on the form the admin site expects: +# (timestamp, security_problem_uid, summary) +log_event_tuples = [ + (log_timestamp, security_problem_uid_template_var, log_event) + for (log_timestamp, log_event) in log_event_tuples + if any([re.search(regex, log_event, flags=re.IGNORECASE) for regex in regexes]) +] + +if not log_event_tuples: + sys.exit() + +csv_writer(log_event_tuples) \ No newline at end of file diff --git a/scripts/detect_user_expired_event.py b/scripts/detect_user_expired_event.py new file mode 100644 index 0000000..fc127d7 --- /dev/null +++ b/scripts/detect_user_expired_event.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 + +""" +Security Script for finding user expired events. + +For use with the "lockdown_usb.sh" and "unexpire_user.sh" +script. +""" + + +import sys +from datetime import datetime, timedelta +import re + +__copyright__ = "Copyright 2017-2024 Magenta ApS" +__license__ = "GPL" + + +def log_read(last_security_check, log_name): + """Search a (system) log for events that occurred + between "last_security_check" and now.""" + log_event_tuples = [] + now = datetime.now() + + with open(log_name) as f: + for line in f.readlines(): + line = str(line.replace("\0", "")) + log_event_timestamp = line[:15] + log_event = line.strip("\n") + # convert from log event timestamp to security event log timestamp. + log_event_datetime = datetime.strptime( + str(now.year) + " " + log_event_timestamp, "%Y %b %d %H:%M:%S" + ) + security_event_log_timestamp = datetime.strftime( + log_event_datetime, "%Y%m%d%H%M%S" + ) + # Detect lines from within the last x seconds to now. + if last_security_check <= log_event_datetime <= now: + log_event_tuples.append((security_event_log_timestamp, log_event)) + + return log_event_tuples + + +def csv_writer(security_events): + """Write security events to security events file.""" + with open("/etc/os2borgerpc/security/securityevent.csv", "at") as csvfile: + for timestamp, security_problem_uid, log_event in security_events: + event_line = log_event.replace("\n", " ").replace("\r", "").replace(",", "") + csvfile.write(f"{timestamp},{security_problem_uid},{event_line}\n") + + +# Sync these dates with the dates set in hard_shutdown_lockdown, lockdown_usb or any future script that may use this expiry mechanism +def annotate_event_type(event): + """Adds the type of the security event (USB/Hard shutdown) to the start of the event, as inferred from the expiry date""" + if event.endswith("'1970-01-05'"): + event = f"USB event detected: {event}" + if event.endswith("'1970-01-04'"): + event = f"Hard shutdown detected: {event}" + return event + + +# The file to inspect for events +log_name = "/var/log/auth.log" + +now = datetime.now() +# The default value in case lastcheck.txt is nonexisting or empty: +last_security_check = now - timedelta(hours=24) +try: + with open("/etc/os2borgerpc/security/lastcheck.txt", "r") as fp: + timestamp = fp.read() + if timestamp: + last_security_check = datetime.strptime(timestamp, "%Y%m%d%H%M%S") +except IOError: + pass + +log_event_tuples = log_read(last_security_check, log_name) + +security_problem_uid_template_var = "%SECURITY_PROBLEM_UID%" + +# Example event: +# Jul 13 11:50:20 bpc usermod[328713]: change user 'user' expiration from 'never' to '1970-01-02' +regexes = [ + (r"(usermod\[[0-9]+\]: change user 'user' expiration from 'never' to '[0-9-]+')") +] + +# Filter log_event_tuples based on regex matches and put them +# on the form the admin site expects: +# (timestamp, security_problem_uid, summary) +log_event_tuples = [ + (log_timestamp, security_problem_uid_template_var, annotate_event_type(log_event)) + for (log_timestamp, log_event) in log_event_tuples + if any([re.search(regex, log_event, flags=re.IGNORECASE) for regex in regexes]) +] + +if not log_event_tuples: + sys.exit() + +csv_writer(log_event_tuples) \ No newline at end of file