Skip to content

Instantly share code, notes, and snippets.

@sam2332
Last active July 22, 2024 14:18
Show Gist options
  • Save sam2332/2c1bbbc2d9a176a47b2e79efddb8360c to your computer and use it in GitHub Desktop.
Save sam2332/2c1bbbc2d9a176a47b2e79efddb8360c to your computer and use it in GitHub Desktop.
Check System paths for command overwrites
import os
import pwd
import grp
import hashlib
from collections import defaultdict
# Trusted users
TRUSTED_USERS = ['root']
# Base system directories
BASE_SYSTEM_DIRS = ['/bin', '/sbin', '/usr/bin', '/usr/sbin']
def get_user_name(uid):
try:
return pwd.getpwuid(uid).pw_name
except KeyError:
return None
def get_group_name(gid):
try:
return grp.getgrgid(gid).gr_name
except KeyError:
return None
def check_directory_ownership(directory):
try:
st = os.stat(directory)
uid, gid = st.st_uid, st.st_gid
user_name = get_user_name(uid)
group_name = get_group_name(gid)
return user_name, group_name
except FileNotFoundError as e:
raise e
def is_trusted_user(user_name):
return user_name in TRUSTED_USERS
def get_executables_in_directory(directory):
try:
return [f for f in os.listdir(directory) if os.access(os.path.join(directory, f), os.X_OK)]
except FileNotFoundError:
return []
def compute_file_hash(filepath):
sha256 = hashlib.sha256()
try:
with open(filepath, 'rb') as f:
while chunk := f.read(8192):
sha256.update(chunk)
return sha256.hexdigest()
except FileNotFoundError:
return None
except IsADirectoryError:
return None
def main():
path_dirs = os.getenv('PATH', '').split(os.pathsep)
path_dirs = [d for d in path_dirs if os.path.isdir(d)]
path_dirs = list(set(path_dirs))
# Collect all system commands
base_system_commands = {}
all_commands = defaultdict(list)
for base_dir in BASE_SYSTEM_DIRS:
for cmd in get_executables_in_directory(base_dir):
base_system_commands[cmd] = base_dir
all_commands[cmd].append(os.path.join(base_dir, cmd))
# Check each directory in the PATH variable
for directory in path_dirs:
if directory in BASE_SYSTEM_DIRS:
continue
try:
user_name, group_name = check_directory_ownership(directory)
if not is_trusted_user(user_name):
print(f"Untrusted directory owner: {directory} (owned by {user_name}:{group_name})")
for cmd in get_executables_in_directory(directory):
all_commands[cmd].append(os.path.join(directory, cmd))
if cmd in base_system_commands:
print(f"Command overwrite detected: {cmd} in {directory} (base: {base_system_commands[cmd]})")
except FileNotFoundError:
print(f"Directory not found: {directory}")
# Check for duplicate commands across directories and compare their hashes
for cmd, filepaths in all_commands.items():
if len(filepaths) > 1:
hashes = {fp: compute_file_hash(fp) for fp in filepaths}
unique_hashes = set(hashes.values())
if len(unique_hashes) > 1:
print(f"Duplicate but different command detected: {cmd} in directories {filepaths}")
else:
pass#print(f"Duplicate but identical command detected: {cmd} in directories {filepaths}")
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment