sync internal repo

This commit is contained in:
Andre Pawlowski 2023-03-13 11:30:24 +01:00
parent 8f8497f63e
commit ad8617ebb1
24 changed files with 660 additions and 50 deletions

View File

@ -1,50 +1,77 @@
# Linux Security and Monitoring Scripts
These are a collection of security and monitoring scripts you can use to monitor your Linux installation for security-related events or for an investigation. Each script works on its own and is independent of other scripts. The scripts can be set up to either print out their results, send them to you via mail, or using [AlertR](https://github.com/sqall01/alertR) as notification channel.
These are a collection of security and monitoring scripts you can use to monitor your Linux installation
for security-related events or for an investigation. Each script works on its own and is independent of other scripts.
The scripts can be set up to either print out their results, send them to you via mail,
or using [AlertR](https://github.com/sqall01/alertR) as notification channel.
## Repository Structure
The scripts are located in the directory `scripts/`. Each script contains a short summary in the header of the file with a description of what it is supposed to do, (if needed) dependencies that have to be installed and (if available) references to where the idea for this script stems from.
The scripts are located in the directory `scripts/`.
Each script contains a short summary in the header of the file with a description of what it is supposed to do,
(if needed) dependencies that have to be installed and (if available) references to where the idea for
this script stems from.
Each script has a configuration file in the `scripts/config/` directory to configure it. If the configuration file was not found during the execution of the script, the script will fall back to default settings and print out the results. Hence, it is not necessary to provide a configuration file.
Each script has a configuration file in the `scripts/config/` directory to configure it.
If the configuration file was not found during the execution of the script,
the script will fall back to default settings and print out the results.
Hence, it is not necessary to provide a configuration file.
The `scripts/lib/` directory contains code that is shared between different scripts.
Scripts using a `monitor_` prefix hold a state and are only useful for monitoring purposes. A single usage of them for an investigation will only result in showing the current state the Linux system and not changes that might be relevant for the system's security.
Scripts using a `monitor_` prefix hold a state and are only useful for monitoring purposes.
A single usage of them for an investigation will only result in showing the current state the
Linux system and not changes that might be relevant for the system's security. If you want to
establish the current state of your system as benign for these scripts, you can provide the `--init` argument.
## Usage
Take a look at the header of the script you want to execute. It contains a short description what this script is supposed to do and what requirements are needed (if any needed at all). If requirements are needed, install them before running the script.
Take a look at the header of the script you want to execute. It contains a short description what this script
is supposed to do and what requirements are needed (if any needed at all). If requirements are needed,
install them before running the script.
The shared configuration file `scripts/config/config.py` contains settings that are used by all scripts. Furthermore, each script can be configured by using the corresponding configuration file in the `scripts/config/` directory. If no configuration file was found, a default setting is used and the results are printed out.
The shared configuration file `scripts/config/config.py` contains settings that are used by all scripts.
Furthermore, each script can be configured by using the corresponding configuration file in the `scripts/config/`
directory. If no configuration file was found, a default setting is used and the results are printed out.
Finally, you can run all configured scripts by executing `start_search.py` (which is located in the main directory) or by executing each script manually. A Python3 interpreter is needed to run the scripts.
Finally, you can run all configured scripts by executing `start_search.py` (which is located in the main directory)
or by executing each script manually. A Python3 interpreter is needed to run the scripts.
### Monitoring
If you want to use the scripts to monitor your Linux system constantly, you have to perform the following steps:
1. Set up a notification channel that is supported by the scripts (currently printing out, mail, or [AlertR](https://github.com/sqall01/alertR)).
1. Set up a notification channel that is supported by the scripts (currently printing out, mail,
or [AlertR](https://github.com/sqall01/alertR)).
2. Configure the scripts that you want to run using the configuration files in the `scripts/config/` directory.
3. Set up a cron job as `root` user that executes `start_search.py` (e.g., `0 * * * * root /opt/LSMS/start_search.py` to start the search hourly).
3. Execute `start_search.py` with the `--init` argument to initialize the scripts with the `monitor_` prefix and let
them establish a state of your system. However, this assumes that your system is currently uncompromised.
If you are unsure of this, you should verify its current state.
4. Set up a cron job as `root` user that executes `start_search.py`
5. (e.g., `0 * * * * root /opt/LSMS/start_search.py` to start the search hourly).
## List of Scripts
| Name | Script |
|---------------------------------------------|--------------------------------------------------------------------------|
| Monitoring cron files | [monitor_cron.py](scripts/monitor_cron.py) |
| Monitoring /etc/hosts file | [monitor_hosts_file.py](scripts/monitor_hosts_file.py) |
| Monitoring /etc/ld.so.preload file | [monitor_ld_preload.py](scripts/monitor_ld_preload.py) |
| Monitoring /etc/passwd file | [monitor_passwd.py](scripts/monitor_passwd.py) |
| Monitoring SSH authorized_keys files | [monitor_ssh_authorized_keys.py](scripts/monitor_ssh_authorized_keys.py) |
| Search executables in /dev/shm | [search_dev_shm.py](scripts/search_dev_shm.py) |
| Search fileless programs (memfd_create) | [search_memfd_create.py](scripts/search_memfd_create.py) |
| Search hidden ELF files | [search_hidden_exe.py](scripts/search_hidden_exe.py) |
| Search immutable files | [search_immutable_files.py](scripts/search_immutable_files.py) |
| Search kernel thread impersonations | [search_non_kthreads.py](scripts/search_non_kthreads.py) |
| Search running deleted programs | [search_deleted_exe.py](scripts/search_deleted_exe.py) |
| Search fileless programs (memfd_create) | [search_memfd_create.py](scripts/search_memfd_create.py) |
| Test script to check if alerting works | [test_alert.py](scripts/test_alert.py) |
| Verify integrity of installed .deb packages | [verify_deb_packages.py](scripts/verify_deb_packages.py) |
| Name | Script |
|----------------------------------------------------------------------|------------------------------------------------------------------------------|
| Monitoring cron files | [monitor_cron.py](scripts/monitor_cron.py) |
| Monitoring /etc/hosts file | [monitor_hosts_file.py](scripts/monitor_hosts_file.py) |
| Monitoring /etc/ld.so.preload file | [monitor_ld_preload.py](scripts/monitor_ld_preload.py) |
| Monitoring /etc/passwd file | [monitor_passwd.py](scripts/monitor_passwd.py) |
| Monitoring modules | [monitor_modules.py](scripts/monitor_modules.py) |
| Monitoring SSH authorized_keys files | [monitor_ssh_authorized_keys.py](scripts/monitor_ssh_authorized_keys.py) |
| Monitoring systemd unit files | [monitor_systemd_units.py](scripts/monitor_systemd_units.py) |
| Search executables in /dev/shm | [search_dev_shm.py](scripts/search_dev_shm.py) |
| Search fileless programs (memfd_create) | [search_memfd_create.py](scripts/search_memfd_create.py) |
| Search hidden ELF files | [search_hidden_exe.py](scripts/search_hidden_exe.py) |
| Search immutable files | [search_immutable_files.py](scripts/search_immutable_files.py) |
| Search kernel thread impersonations | [search_non_kthreads.py](scripts/search_non_kthreads.py) |
| Search processes that were started by a now disconnected SSH session | [search_ssh_leftover_processes.py](scripts/search_ssh_leftover_processes.py) |
| Search running deleted programs | [search_deleted_exe.py](scripts/search_deleted_exe.py) |
| Test script to check if alerting works | [test_alert.py](scripts/test_alert.py) |
| Verify integrity of installed .deb packages | [verify_deb_packages.py](scripts/verify_deb_packages.py) |

View File

@ -0,0 +1,7 @@
from typing import List
# List of modules that are loaded and should be ignored.
MODULES_WHITELIST = [] # type: List[str]
# Is the script allowed to run or not?
ACTIVATED = True

View File

@ -0,0 +1,16 @@
# Is the script allowed to run or not?
ACTIVATED = True
# Directories in which systemd unit files can be placed. Following list are the defaults on Ubuntu/Debian.
SYSTEMD_UNIT_DIRS = ["/etc/systemd/system",
"/etc/systemd/user",
"/etc/systemd/network",
"/usr/lib/systemd/system",
"/usr/lib/systemd/user",
"/usr/lib/systemd/network",
"/usr/local/lib/systemd/system",
"/usr/local/lib/systemd/user"
"/usr/local/lib/systemd/network",
"/lib/systemd/system",
"/lib/systemd/user",
"/lib/systemd/network"] # type: List[str]

View File

@ -0,0 +1,2 @@
# Is the script allowed to run or not?
ACTIVATED = True

View File

@ -25,7 +25,7 @@ def raise_alert_alertr(alertr_fifo: str,
try:
# Will throw an exception if FIFO file does not have a reader instead of blocking.
fd = os.open(alertr_fifo, os.O_WRONLY | os.O_NONBLOCK)
os.write(fd, json.dumps(msg_dict).encode("ascii"))
os.write(fd, (json.dumps(msg_dict) + "\n").encode("ascii"))
os.close(fd)
# Give AlertR sensor time to process the data.
# Otherwise, a parsing error might occur on the FIFO sensor when multiple messages were mixed.

View File

@ -0,0 +1 @@
SUPPRESS_OUTPUT = False

View File

@ -1,6 +1,9 @@
import difflib
import os
import socket
import threading
from . import global_vars
from .alerts import raise_alert_alertr, raise_alert_mail
try:
@ -12,7 +15,17 @@ except:
TO_ADDR = None
def get_diff_per_line(name1: str, data1: str, name2: str, data2: str) -> str:
# difflib function needs trailing newline for each element to build a usable output string
temp1 = ["%s\n" % x for x in data1.split("\n")]
temp2 = ["%s\n" % x for x in data2.split("\n")]
return "".join(difflib.unified_diff(temp1, temp2, fromfile=name1, tofile=name2))
def output_error(file_name: str, msg: str):
# Suppresses output, for example, if an initialization run is performed.
if global_vars.SUPPRESS_OUTPUT:
return
base_name = os.path.basename(file_name)
@ -37,18 +50,21 @@ def output_error(file_name: str, msg: str):
optional_data["script"] = base_name
optional_data["message"] = message
raise_alert_alertr(ALERTR_FIFO,
optional_data)
threading.Thread(target=raise_alert_alertr,
args=(ALERTR_FIFO, optional_data),
daemon=False).start()
if FROM_ADDR is not None and TO_ADDR is not None:
mail_subject = "[Security] Error in '%s' on host '%s'" % (base_name, socket.gethostname())
raise_alert_mail(FROM_ADDR,
TO_ADDR,
mail_subject,
message)
threading.Thread(target=raise_alert_mail,
args=(FROM_ADDR, TO_ADDR, mail_subject, message),
daemon=False).start()
def output_finding(file_name: str, msg: str):
# Suppresses output, for example, if an initialization run is performed.
if global_vars.SUPPRESS_OUTPUT:
return
base_name = os.path.basename(file_name)

View File

@ -12,7 +12,11 @@ Short summary:
Monitor /etc/crontab, /etc/cron.d/*, user specific crontab files and script files run by cron (e.g., script files in /etc/cron.hourly) for changes to detect attempts for attacker persistence.
Additionally, check if crontab entries and user specific crontab files belong to existing system users.
NOTE: The first execution of this script will only show you the current state of the environment which should be acknowledged before monitoring for changes will become an effective security measure.
NOTE: The first execution of this script should be done with the argument "--init".
Otherwise, the script will only show you the current state of the environment since no state was established yet.
However, this assumes that the system is uncompromised during the initial execution.
Hence, if you are unsure this is the case you should verify the current state
before monitoring for changes will become an effective security measure.
Requirements:
None
@ -21,8 +25,10 @@ None
import hashlib
import os
import re
import sys
from typing import Dict, List, Set
import lib.global_vars
from lib.state import load_state, store_state
from lib.util import output_error, output_finding
from lib.util_user import get_system_users
@ -236,4 +242,8 @@ def monitor_cron():
if __name__ == '__main__':
if len(sys.argv) == 2:
# Suppress output in our initial execution to establish a state.
if sys.argv[1] == "--init":
lib.global_vars.SUPPRESS_OUTPUT = True
monitor_cron()

View File

@ -11,15 +11,21 @@
Short summary:
Monitor /etc/hosts for changes to detect malicious attempts to divert traffic.
NOTE: The first execution of this script will only show you the current state of the environment which should be acknowledged before monitoring for changes will become an effective security measure.
NOTE: The first execution of this script should be done with the argument "--init".
Otherwise, the script will only show you the current state of the environment since no state was established yet.
However, this assumes that the system is uncompromised during the initial execution.
Hence, if you are unsure this is the case you should verify the current state
before monitoring for changes will become an effective security measure.
Requirements:
None
"""
import os
import sys
from typing import Dict, Set
import lib.global_vars
from lib.state import load_state, store_state
from lib.util import output_error, output_finding
@ -153,4 +159,8 @@ def monitor_hosts():
if __name__ == '__main__':
if len(sys.argv) == 2:
# Suppress output in our initial execution to establish a state.
if sys.argv[1] == "--init":
lib.global_vars.SUPPRESS_OUTPUT = True
monitor_hosts()

View File

@ -11,15 +11,21 @@
Short summary:
Monitor /etc/ld.so.preload for changes to detect malicious attempts to alter the control flow of binaries.
NOTE: The first execution of this script will only show you the current state of the environment which should be acknowledged before monitoring for changes will become an effective security measure.
NOTE: The first execution of this script should be done with the argument "--init".
Otherwise, the script will only show you the current state of the environment since no state was established yet.
However, this assumes that the system is uncompromised during the initial execution.
Hence, if you are unsure this is the case you should verify the current state
before monitoring for changes will become an effective security measure.
Requirements:
None
"""
import os
import sys
from typing import Set
import lib.global_vars
from lib.state import load_state, store_state
from lib.util import output_error, output_finding
@ -110,4 +116,8 @@ def monitor_ld_preload():
if __name__ == '__main__':
if len(sys.argv) == 2:
# Suppress output in our initial execution to establish a state.
if sys.argv[1] == "--init":
lib.global_vars.SUPPRESS_OUTPUT = True
monitor_ld_preload()

130
scripts/monitor_modules.py Executable file
View File

@ -0,0 +1,130 @@
#!/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 /proc/modules for changes to detect if a malicious module was loaded. The script can run in two different modes:
1) before running the script, whitelist every module that is allowed to be loaded on the host in the configuration file.
2) assume all loaded modules are legitimate during the initial execution of the script with "--init" and monitor for changes.
If using 1), you get fewer false-positives due to the time you spend setting everything up.
If using 2), you assume the host is uncompromised during the initial execution of the script.
If you have a module that is loaded/unloaded frequently, you can still configure the whitelist additionally
to prevent constant alerting.
Requirements:
None
"""
import os
import sys
import lib.global_vars
from lib.state import load_state, store_state
from lib.util import output_error, output_finding
# Read configuration.
try:
from config.config import ALERTR_FIFO, FROM_ADDR, TO_ADDR, STATE_DIR
from config.monitor_modules import ACTIVATED, MODULES_WHITELIST
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
MODULES_WHITELIST = []
STATE_DIR = os.path.join("/tmp", os.path.basename(__file__))
def _get_modules():
"""
Reads all currently loaded modules.
:return: set of loaded modules
"""
loaded_modules = set()
with open("/proc/modules", 'r') as fp:
for line in fp:
line_list = line.split(" ")
loaded_modules.add(line_list[0])
return loaded_modules
def monitor_modules():
# 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_modules_data = set()
try:
state_data = load_state(STATE_DIR)
# Convert list to set.
if "modules_data" in state_data.keys():
stored_modules_data = set(state_data["modules_data"])
except Exception as e:
output_error(__file__, str(e))
return
current_modules = set()
try:
current_modules = _get_modules()
except Exception as e:
output_error(__file__, str(e))
return
# Remove whitelisted modules from the currently loaded modules set.
current_modules = current_modules - set(MODULES_WHITELIST)
# Check for newly loaded modules.
loaded_modules = current_modules - stored_modules_data
if loaded_modules:
message = "New modules loaded.\n\n"
message += "Entries:\n"
for module in loaded_modules:
message += module
message += "\n"
output_finding(__file__, message)
# Check for newly unloaded modules.
unloaded_modules = stored_modules_data - current_modules
if unloaded_modules:
message = "Running modules unloaded.\n\n"
message += "Entries:\n"
for module in unloaded_modules:
message += module
message += "\n"
output_finding(__file__, message)
try:
# Convert set to list.
state_data = {"modules_data": list(current_modules)}
store_state(STATE_DIR, state_data)
except Exception as e:
output_error(__file__, str(e))
if __name__ == '__main__':
if len(sys.argv) == 2:
# Suppress output in our initial execution to establish a state.
if sys.argv[1] == "--init":
lib.global_vars.SUPPRESS_OUTPUT = True
monitor_modules()

View File

@ -11,15 +11,21 @@
Short summary:
Monitor /etc/passwd for changes to detect malicious attempts to hijack/change users.
NOTE: The first execution of this script will only show you the current state of the environment which should be acknowledged before monitoring for changes will become an effective security measure.
NOTE: The first execution of this script should be done with the argument "--init".
Otherwise, the script will only show you the current state of the environment since no state was established yet.
However, this assumes that the system is uncompromised during the initial execution.
Hence, if you are unsure this is the case you should verify the current state
before monitoring for changes will become an effective security measure.
Requirements:
None
"""
import os
import sys
from typing import Dict
import lib.global_vars
from lib.state import load_state, store_state
from lib.util import output_error, output_finding
from lib.util_user import get_system_users
@ -109,4 +115,8 @@ def monitor_passwd():
if __name__ == '__main__':
if len(sys.argv) == 2:
# Suppress output in our initial execution to establish a state.
if sys.argv[1] == "--init":
lib.global_vars.SUPPRESS_OUTPUT = True
monitor_passwd()

View File

@ -11,7 +11,11 @@
Short summary:
Monitor ~/.ssh/authorized_keys for changes to detect malicious backdoor attempts.
NOTE: The first execution of this script will only show you the current state of the environment which should be acknowledged before monitoring for changes will become an effective security measure.
NOTE: The first execution of this script should be done with the argument "--init".
Otherwise, the script will only show you the current state of the environment since no state was established yet.
However, this assumes that the system is uncompromised during the initial execution.
Hence, if you are unsure this is the case you should verify the current state
before monitoring for changes will become an effective security measure.
Requirements:
None
@ -19,8 +23,10 @@ None
import os
import stat
import sys
from typing import List, Tuple, Dict, Any
import lib.global_vars
from lib.state import load_state, store_state
from lib.util import output_error, output_finding
from lib.util_user import get_system_users
@ -172,4 +178,8 @@ def monitor_ssh_authorized_keys():
if __name__ == '__main__':
if len(sys.argv) == 2:
# Suppress output in our initial execution to establish a state.
if sys.argv[1] == "--init":
lib.global_vars.SUPPRESS_OUTPUT = True
monitor_ssh_authorized_keys()

172
scripts/monitor_systemd_units.py Executable file
View File

@ -0,0 +1,172 @@
#!/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 systemd unit files to find ones that are used for malware persistence.
NOTE: The first execution of this script should be done with the argument "--init".
Otherwise, the script will only show you the current state of the environment since no state was established yet.
However, this assumes that the system is uncompromised during the initial execution.
Hence, if you are unsure this is the case you should verify the current state
before monitoring for changes will become an effective security measure.
Requirements:
None
Reference:
https://www.trendmicro.com/en_us/research/23/c/iron-tiger-sysupdate-adds-linux-targeting.html
"""
import os
import sys
from typing import Dict
import lib.global_vars
from lib.state import load_state, store_state
from lib.util import get_diff_per_line, output_error, output_finding
# Read configuration.
try:
from config.config import ALERTR_FIFO, FROM_ADDR, TO_ADDR, STATE_DIR
from config.monitor_systemd_units import ACTIVATED, SYSTEMD_UNIT_DIRS
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__))
SYSTEMD_UNIT_DIRS = ["/etc/systemd/system",
"/etc/systemd/user",
"/etc/systemd/network",
"/usr/lib/systemd/system",
"/usr/lib/systemd/user",
"/usr/lib/systemd/network",
"/usr/local/lib/systemd/system",
"/usr/local/lib/systemd/user"
"/usr/local/lib/systemd/network",
"/lib/systemd/system",
"/lib/systemd/user",
"/lib/systemd/network"]
def _get_system_unit_files() -> Dict[str, str]:
systemd_unit_files = dict()
for systemd_unit_dir in SYSTEMD_UNIT_DIRS:
for root, _, files in os.walk(systemd_unit_dir):
for file in files:
file_location = os.path.join(root, file)
# Some files are broken symlinks, hence, check if they exist
if os.path.exists(file_location):
with open(file_location, "rt") as fp:
data = fp.read()
# Filter for systemd unit files that can execute commands
if "[Unit]" in data and "[Service]" in data:
# Since keys do not have to start at the beginning of the line, we go through each line,
# remove whitespaces leading whitespaces and check if it starts with a key we are interested in
for line in data.split("\n"):
normalized_line = line.strip()
if any(normalized_line.startswith(x) for x in ["ExecStart",
"ExecStartPre",
"ExecStartPost",
"ExecReload",
"ExecStop",
"ExecStopPost"]):
# Store complete data of unit file. Even on a non-server system such as a
# xubuntu 22.04 we only have around 700 unit files of interest. Calculating with
# 1kB of data per file (which is way larger than a normal unit file has) we only
# need a little over 700 kB memory for this. Even on a Raspberry Pi we have no
# problem doing this. Further, it will prevent race-conditions when we already
# have the data stored and do not read it afterwards from the file if we generate
# alerts.
systemd_unit_files[file_location] = data
break
return systemd_unit_files
def monitor_systemd_units():
# 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_systemd_units_data = {}
try:
stored_systemd_units_data = load_state(STATE_DIR)
except Exception as e:
output_error(__file__, str(e))
return
# Add units key in case we do not have any stored data yet.
if "units" not in stored_systemd_units_data.keys():
stored_systemd_units_data["units"] = {}
curr_systemd_units_data = {}
try:
curr_systemd_units_data = _get_system_unit_files()
except Exception as e:
output_error(__file__, str(e))
return
# Compare stored unit files data with current one.
stored_units_data = stored_systemd_units_data["units"]
for stored_unit_file, stored_unit_data in stored_units_data.items():
# Check if unit file was deleted.
if stored_unit_file not in curr_systemd_units_data.keys():
message = "Systemd unit file '%s' was deleted." % stored_unit_file
output_finding(__file__, message)
continue
# Check if unit file was modified.
if stored_unit_data != curr_systemd_units_data[stored_unit_file]:
diff = get_diff_per_line("Old",
stored_unit_data,
"New",
curr_systemd_units_data[stored_unit_file])
message = "Systemd unit file '%s' was modified:\n\nDiff:\n%s\n\nNew file:\n%s" % (stored_unit_file,
diff,
curr_systemd_units_data[stored_unit_file]) # noqa:E501
output_finding(__file__, message)
# Check new unit file added.
for curr_unit_file in curr_systemd_units_data.keys():
if curr_unit_file not in stored_units_data.keys():
message = "Systemd unit file '%s' was added:\n\n%s" % (curr_unit_file,
curr_systemd_units_data[curr_unit_file])
output_finding(__file__, message)
try:
store_state(STATE_DIR, {"units": curr_systemd_units_data})
except Exception as e:
output_error(__file__, str(e))
if __name__ == '__main__':
if len(sys.argv) == 2:
# Suppress output in our initial execution to establish a state.
if sys.argv[1] == "--init":
lib.global_vars.SUPPRESS_OUTPUT = True
monitor_systemd_units()

View File

@ -16,6 +16,7 @@ None
"""
import os
import sys
from lib.util import output_finding
@ -59,4 +60,11 @@ def search_deleted_exe_files():
if __name__ == '__main__':
search_deleted_exe_files()
is_init_run = False
if len(sys.argv) == 2:
if sys.argv[1] == "--init":
is_init_run = True
# Script does not need to establish a state.
if not is_init_run:
search_deleted_exe_files()

View File

@ -16,11 +16,12 @@ Requirements:
None
Reference:
https://twitter.com/CraigHRowland/status/1268863172825346050?s=20
https://twitter.com/CraigHRowland/status/1268863172825346050
https://twitter.com/CraigHRowland/status/1269196509079166976
"""
import os
import sys
from lib.util import output_finding
@ -71,4 +72,11 @@ def search_suspicious_files():
if __name__ == '__main__':
search_suspicious_files()
is_init_run = False
if len(sys.argv) == 2:
if sys.argv[1] == "--init":
is_init_run = True
# Script does not need to establish a state.
if not is_init_run:
search_suspicious_files()

View File

@ -16,6 +16,7 @@ None
"""
import os
import sys
from typing import List
from lib.step_state import StepLocation, load_step_state, store_step_state
@ -141,4 +142,11 @@ def search_hidden_exe_files():
if __name__ == '__main__':
search_hidden_exe_files()
is_init_run = False
if len(sys.argv) == 2:
if sys.argv[1] == "--init":
is_init_run = True
# Script does not need to establish a state.
if not is_init_run:
search_hidden_exe_files()

View File

@ -16,6 +16,7 @@ None
"""
import os
import sys
from typing import List, cast
from lib.step_state import StepLocation, load_step_state, store_step_state
@ -159,4 +160,11 @@ def search_immutable_files():
if __name__ == '__main__':
search_immutable_files()
is_init_run = False
if len(sys.argv) == 2:
if sys.argv[1] == "--init":
is_init_run = True
# Script does not need to establish a state.
if not is_init_run:
search_immutable_files()

View File

@ -19,6 +19,7 @@ https://www.sandflysecurity.com/blog/detecting-linux-memfd_create-fileless-malwa
"""
import os
import sys
from lib.util import output_finding
@ -62,4 +63,11 @@ def search_deleted_memfd_files():
if __name__ == '__main__':
search_deleted_memfd_files()
is_init_run = False
if len(sys.argv) == 2:
if sys.argv[1] == "--init":
is_init_run = True
# Script does not need to establish a state.
if not is_init_run:
search_deleted_memfd_files()

View File

@ -24,6 +24,7 @@ https://www.sandflysecurity.com/blog/detecting-linux-kernel-process-masquerading
"""
import os
import sys
from lib.util import output_error, output_finding
@ -97,4 +98,11 @@ def search_suspicious_process():
if __name__ == '__main__':
search_suspicious_process()
is_init_run = False
if len(sys.argv) == 2:
if sys.argv[1] == "--init":
is_init_run = True
# Script does not need to establish a state.
if not is_init_run:
search_suspicious_process()

View File

@ -0,0 +1,119 @@
#!/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:
Searches for processes that were started by an SSH session that is now disconnected.
Requirements:
None
Reference:
https://twitter.com/CraigHRowland/status/1579582776529281026
"""
import os
import re
import sys
from lib.util import output_error, output_finding
# Read configuration.
try:
from config.config import ALERTR_FIFO, FROM_ADDR, TO_ADDR
from config.search_ssh_leftover_processes import ACTIVATED
except:
ALERTR_FIFO = None
FROM_ADDR = None
TO_ADDR = None
ACTIVATED = True
def search_leftover_ssh_process():
# 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
# Search for SSH_CONNECTION and SSH_CLIENT
fd = os.popen("grep -l SSH_C /proc/*/environ 2> /dev/null")
ssh_processes = fd.read().strip()
fd.close()
for ssh_process in ssh_processes.split("\n"):
# Example output: /proc/996/environ
# noinspection RegExpRedundantEscape
matches = re.search(r'proc/(\d*)/environ', ssh_process, re.IGNORECASE)
if not matches:
continue
pid = matches.group(1)
try:
with open("/proc/" + str(pid) + "/status", "r") as fp:
status_data = fp.read()
except FileNotFoundError: # Process got terminated while searching
continue
ppid = None
name = None
for line in status_data.split("\n"):
if line.startswith("PPid:"):
line_split = line.split("\t")
try:
ppid = int(line_split[-1])
except Exception as e:
output_error(__file__, "PPid not parsable for pid %d\n\n%s\n\n%s" % (pid, status_data, str(e)))
break
elif line.startswith("Name:"):
line_split = line.split("\t")
name = line_split[-1]
if ppid is not None and name is not None:
break
if ppid is not None and name is not None:
if ppid == 1:
# Get executed file
exe_link = "/proc/" + str(pid) + "/exe"
fd = os.popen("ls -laR %s" % exe_link)
exe_raw = fd.read().strip()
fd.close()
matches = re.search(r'/proc/\d*/exe -> (.*)', exe_raw, re.IGNORECASE)
exe_file = exe_raw
if matches:
exe_file = matches.group(1)
message = "Leftover process of SSH session found.\n\n"
message += "Name: %s\n" % name
message += "Exe: %s\n" % exe_file
message += "Pid: %s\n" % pid
output_finding(__file__, message)
if __name__ == '__main__':
is_init_run = False
if len(sys.argv) == 2:
if sys.argv[1] == "--init":
is_init_run = True
# Script does not need to establish a state.
if not is_init_run:
search_leftover_ssh_process()

View File

@ -15,6 +15,7 @@ Requirements:
None
"""
import sys
from lib.util import output_finding
# Read configuration.
@ -29,6 +30,13 @@ except:
if __name__ == '__main__':
if ACTIVATED:
message = "Alert test."
output_finding(__file__, message)
is_init_run = False
if len(sys.argv) == 2:
if sys.argv[1] == "--init":
is_init_run = True
# Script does not need to establish a state.
if not is_init_run:
if ACTIVATED:
message = "Alert test."
output_finding(__file__, message)

View File

@ -19,6 +19,7 @@ https://www.sandflysecurity.com/blog/detecting-linux-binary-file-poisoning/
"""
import os
import sys
from typing import List
from lib.util import output_finding
@ -61,7 +62,7 @@ def verify_deb_packages():
print("Module deactivated.")
return
fd = os.popen("%s -c" % DEBSUMS_EXE)
fd = os.popen("%s -c 2> /dev/null" % DEBSUMS_EXE)
output_raw = fd.read().strip()
fd.close()
@ -78,4 +79,11 @@ def verify_deb_packages():
if __name__ == '__main__':
verify_deb_packages()
is_init_run = False
if len(sys.argv) == 2:
if sys.argv[1] == "--init":
is_init_run = True
# Script does not need to establish a state.
if not is_init_run:
verify_deb_packages()

View File

@ -10,6 +10,7 @@
import os
import subprocess
import socket
import sys
import time
from scripts.config.config import START_PROCESS_TIMEOUT, TO_ADDR, FROM_ADDR, ALERTR_FIFO
from scripts.lib.alerts import raise_alert_alertr, raise_alert_mail
@ -29,7 +30,12 @@ if __name__ == '__main__':
if print_output:
print("Executing %s" % script)
to_execute = script_dir + script
to_execute = [script_dir + script]
# Pass arguments to scripts.
if len(sys.argv) > 1:
to_execute.extend(sys.argv[1:])
process = None
try:
process = subprocess.Popen(to_execute,