mirror of
https://github.com/sqall01/LSMS.git
synced 2024-11-17 01:28:25 +01:00
301 lines
8.9 KiB
Python
Executable file
301 lines
8.9 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
# written by sqall
|
|
# twitter: https://twitter.com/sqall01
|
|
# blog: https://h4des.org
|
|
# github: https://github.com/sqall01
|
|
#
|
|
# Licensed under the MIT License.
|
|
|
|
"""
|
|
Short summary:
|
|
Monitor /etc/hosts for changes to detect malicious attempts to divert traffic.
|
|
|
|
Requirements:
|
|
None
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import socket
|
|
import stat
|
|
from typing import Dict, Set
|
|
|
|
from lib.alerts import raise_alert_alertr, raise_alert_mail
|
|
|
|
# Read configuration and library functions.
|
|
try:
|
|
from config.config import ALERTR_FIFO, FROM_ADDR, TO_ADDR, STATE_DIR
|
|
from config.monitor_hosts_file import ACTIVATED
|
|
STATE_DIR = os.path.join(os.path.dirname(__file__), STATE_DIR, os.path.basename(__file__))
|
|
except:
|
|
ALERTR_FIFO = None
|
|
FROM_ADDR = None
|
|
TO_ADDR = None
|
|
ACTIVATED = True
|
|
STATE_DIR = os.path.join("/tmp", os.path.basename(__file__))
|
|
|
|
MAIL_SUBJECT = "[Security] Monitoring /etc/hosts on host '%s'" % socket.gethostname()
|
|
|
|
|
|
class MonitorHostsException(Exception):
|
|
def __init__(self, msg: str):
|
|
self._msg = msg
|
|
|
|
def __str__(self):
|
|
return self._msg
|
|
|
|
|
|
def _get_hosts() -> Dict[str, Set[str]]:
|
|
|
|
hosts_data = {}
|
|
with open("/etc/hosts", 'rt') as fp:
|
|
for line in fp:
|
|
line = line.strip()
|
|
|
|
if line == "":
|
|
continue
|
|
|
|
# Ignore comments.
|
|
if line[0] == "#":
|
|
continue
|
|
|
|
entry = line.split()
|
|
if len(entry) < 2:
|
|
raise MonitorHostsException("Not able to parse line: %s" % line)
|
|
|
|
ip = entry[0]
|
|
hosts = set(entry[1:])
|
|
if ip not in hosts_data.keys():
|
|
hosts_data[ip] = hosts
|
|
|
|
else:
|
|
for host in hosts:
|
|
hosts_data[ip].add(host)
|
|
|
|
return hosts_data
|
|
|
|
|
|
def _load_hosts_data() -> Dict[str, Set[str]]:
|
|
state_file = os.path.join(STATE_DIR, "state")
|
|
hosts_data = {}
|
|
if os.path.isfile(state_file):
|
|
data = None
|
|
try:
|
|
with open(state_file, 'rt') as fp:
|
|
data = fp.read()
|
|
if data is None:
|
|
raise MonitorHostsException("Read state data is None.")
|
|
|
|
temp = json.loads(data)
|
|
|
|
# Convert list to set.
|
|
for k,v in temp.items():
|
|
hosts_data[k] = set(v)
|
|
|
|
except Exception as e:
|
|
raise MonitorHostsException("State data: '%s'; Exception: '%s'" % (str(data), str(e)))
|
|
|
|
return hosts_data
|
|
|
|
|
|
def _output_error(msg: str):
|
|
|
|
# Decide where to output results.
|
|
print_output = False
|
|
if ALERTR_FIFO is None and FROM_ADDR is None and TO_ADDR is None:
|
|
print_output = True
|
|
|
|
if print_output:
|
|
print(msg)
|
|
|
|
else:
|
|
hostname = socket.gethostname()
|
|
message = "Error monitoring /etc/hosts on host '%s': %s" \
|
|
% (hostname, msg)
|
|
|
|
if ALERTR_FIFO:
|
|
optional_data = dict()
|
|
optional_data["error"] = True
|
|
optional_data["message"] = message
|
|
|
|
raise_alert_alertr(ALERTR_FIFO,
|
|
optional_data)
|
|
|
|
if FROM_ADDR is not None and TO_ADDR is not None:
|
|
raise_alert_mail(FROM_ADDR,
|
|
TO_ADDR,
|
|
MAIL_SUBJECT,
|
|
message)
|
|
|
|
|
|
def _store_hosts_data(hosts_data: Dict[str, Set[str]]):
|
|
# Create state dir if it does not exist.
|
|
if not os.path.exists(STATE_DIR):
|
|
os.makedirs(STATE_DIR)
|
|
|
|
state_file = os.path.join(STATE_DIR, "state")
|
|
|
|
# Convert set to list.
|
|
temp = {}
|
|
for k, v in hosts_data.items():
|
|
temp[k] = list(v)
|
|
|
|
with open(state_file, 'wt') as fp:
|
|
fp.write(json.dumps(temp))
|
|
|
|
os.chmod(state_file, stat.S_IREAD | stat.S_IWRITE)
|
|
|
|
|
|
def monitor_hosts():
|
|
|
|
# Decide where to output results.
|
|
print_output = False
|
|
if ALERTR_FIFO is None and FROM_ADDR is None and TO_ADDR is None:
|
|
print_output = True
|
|
|
|
if not ACTIVATED:
|
|
if print_output:
|
|
print("Module deactivated.")
|
|
return
|
|
|
|
stored_hosts_data = {}
|
|
try:
|
|
stored_hosts_data =_load_hosts_data()
|
|
|
|
except Exception as e:
|
|
_output_error(str(e))
|
|
return
|
|
|
|
curr_hosts_data = {}
|
|
try:
|
|
curr_hosts_data = _get_hosts()
|
|
|
|
except Exception as e:
|
|
_output_error(str(e))
|
|
return
|
|
|
|
# Compare stored data with current one.
|
|
for stored_entry_ip in stored_hosts_data.keys():
|
|
|
|
# Extract current entry belonging to the same ip.
|
|
if stored_entry_ip not in curr_hosts_data.keys():
|
|
hostname = socket.gethostname()
|
|
message = "Host name for IP '%s' was deleted on host '%s'." \
|
|
% (stored_entry_ip, hostname)
|
|
|
|
if print_output:
|
|
print(message)
|
|
print("#" * 80)
|
|
|
|
if ALERTR_FIFO:
|
|
optional_data = dict()
|
|
optional_data["ip"] = stored_entry_ip
|
|
optional_data["hostname"] = hostname
|
|
optional_data["message"] = message
|
|
|
|
raise_alert_alertr(ALERTR_FIFO,
|
|
optional_data)
|
|
|
|
if FROM_ADDR is not None and TO_ADDR is not None:
|
|
raise_alert_mail(FROM_ADDR,
|
|
TO_ADDR,
|
|
MAIL_SUBJECT,
|
|
message)
|
|
|
|
continue
|
|
|
|
# Check host entry was removed.
|
|
for host in stored_hosts_data[stored_entry_ip]:
|
|
if host not in curr_hosts_data[stored_entry_ip]:
|
|
hostname = socket.gethostname()
|
|
message = "Host name entry for IP '%s' was removed on host '%s'.\n\n" % (stored_entry_ip, hostname)
|
|
message += "Entry: %s" % host
|
|
|
|
if print_output:
|
|
print(message)
|
|
print("#" * 80)
|
|
|
|
if ALERTR_FIFO:
|
|
optional_data = dict()
|
|
optional_data["ip"] = stored_entry_ip
|
|
optional_data["host_entry"] = host
|
|
optional_data["hostname"] = hostname
|
|
optional_data["message"] = message
|
|
|
|
raise_alert_alertr(ALERTR_FIFO,
|
|
optional_data)
|
|
|
|
if FROM_ADDR is not None and TO_ADDR is not None:
|
|
raise_alert_mail(FROM_ADDR,
|
|
TO_ADDR,
|
|
MAIL_SUBJECT,
|
|
message)
|
|
|
|
# Check host entry was added.
|
|
for host in curr_hosts_data[stored_entry_ip]:
|
|
if host not in stored_hosts_data[stored_entry_ip]:
|
|
hostname = socket.gethostname()
|
|
message = "Host name entry for IP '%s' was added on host '%s'.\n\n" % (stored_entry_ip, hostname)
|
|
message += "Entry: %s" % host
|
|
|
|
if print_output:
|
|
print(message)
|
|
print("#" * 80)
|
|
|
|
if ALERTR_FIFO:
|
|
optional_data = dict()
|
|
optional_data["ip"] = stored_entry_ip
|
|
optional_data["host_entry"] = host
|
|
optional_data["hostname"] = hostname
|
|
optional_data["message"] = message
|
|
|
|
raise_alert_alertr(ALERTR_FIFO,
|
|
optional_data)
|
|
|
|
if FROM_ADDR is not None and TO_ADDR is not None:
|
|
raise_alert_mail(FROM_ADDR,
|
|
TO_ADDR,
|
|
MAIL_SUBJECT,
|
|
message)
|
|
|
|
# Check new data was added.
|
|
for curr_entry_ip in curr_hosts_data.keys():
|
|
if curr_entry_ip not in stored_hosts_data.keys():
|
|
hostname = socket.gethostname()
|
|
message = "New host name was added for IP '%s' on host '%s'.\n\n" \
|
|
% (curr_entry_ip, hostname)
|
|
message += "Entries:\n"
|
|
for host in curr_hosts_data[curr_entry_ip]:
|
|
message += host
|
|
message += "\n"
|
|
|
|
if print_output:
|
|
print(message)
|
|
print("#" * 80)
|
|
|
|
if ALERTR_FIFO:
|
|
optional_data = dict()
|
|
optional_data["ip"] = curr_entry_ip
|
|
optional_data["hosts"] = list(curr_hosts_data[curr_entry_ip])
|
|
optional_data["hostname"] = hostname
|
|
optional_data["message"] = message
|
|
|
|
raise_alert_alertr(ALERTR_FIFO,
|
|
optional_data)
|
|
|
|
if FROM_ADDR is not None and TO_ADDR is not None:
|
|
raise_alert_mail(FROM_ADDR,
|
|
TO_ADDR,
|
|
MAIL_SUBJECT,
|
|
message)
|
|
|
|
try:
|
|
_store_hosts_data(curr_hosts_data)
|
|
|
|
except Exception as e:
|
|
_output_error(str(e))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
monitor_hosts()
|