62a1010ab4
9 files in utils/ providing the shared infrastructure layer: - crypto: AES-256-GCM file encryption, argon2id/PBKDF2 key derivation, HKDF network unlock, LUKS container management - networking: interface detection, MAC/IP helpers, BPF compilation via libpcap ctypes, gratuitous ARP, VLAN creation - logging: encrypted log writer (BBLogger) with rotation, per-module files, stdout suppression for stealth - stealth: process rename via prctl, cmdline spoofing, sysctl helpers, timestomping, core dump disable - resource: hardware tier detection (OPi Zero 3 / Pi Zero / generic) via /proc/device-tree and cpuinfo, resource monitoring - permissions: root/capability checks via capget ctypes, privilege dropping, directory permission enforcement - config_loader: YAML merge hierarchy (hardware_tiers -> bigbrother -> modules -> stealth -> CLI), tier auto-detection, SIGHUP reload - bettercap_api: REST client with per-session random credentials, session/events/command methods
191 lines
4.4 KiB
Python
191 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Root/capability checks, privilege dropping, directory permission enforcement."""
|
|
|
|
import ctypes
|
|
import ctypes.util
|
|
import grp
|
|
import os
|
|
import pwd
|
|
import stat
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
# Linux capability constants
|
|
CAP_NET_RAW = 13
|
|
CAP_NET_ADMIN = 15
|
|
CAP_SYS_ADMIN = 21
|
|
CAP_DAC_OVERRIDE = 1
|
|
|
|
# capget/capset structures
|
|
_LINUX_CAPABILITY_VERSION_3 = 0x20080522
|
|
_VFS_CAP_REVISION_2 = 0x02000000
|
|
|
|
_libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
|
|
|
|
|
|
class _cap_header(ctypes.Structure):
|
|
_fields_ = [
|
|
("version", ctypes.c_uint32),
|
|
("pid", ctypes.c_int),
|
|
]
|
|
|
|
|
|
class _cap_data(ctypes.Structure):
|
|
_fields_ = [
|
|
("effective", ctypes.c_uint32),
|
|
("permitted", ctypes.c_uint32),
|
|
("inheritable", ctypes.c_uint32),
|
|
]
|
|
|
|
|
|
def check_root() -> bool:
|
|
return os.geteuid() == 0
|
|
|
|
|
|
def check_capability(cap: int) -> bool:
|
|
if check_root():
|
|
return True
|
|
|
|
try:
|
|
header = _cap_header(version=_LINUX_CAPABILITY_VERSION_3, pid=0)
|
|
# Version 3 uses 2 data structs for 64-bit capability sets
|
|
data = (_cap_data * 2)()
|
|
ret = _libc.capget(ctypes.byref(header), data)
|
|
if ret != 0:
|
|
return False
|
|
|
|
idx = cap // 32
|
|
bit = 1 << (cap % 32)
|
|
return bool(data[idx].effective & bit)
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def has_net_raw() -> bool:
|
|
return check_capability(CAP_NET_RAW)
|
|
|
|
|
|
def has_net_admin() -> bool:
|
|
return check_capability(CAP_NET_ADMIN)
|
|
|
|
|
|
def check_required_capabilities() -> dict:
|
|
return {
|
|
"root": check_root(),
|
|
"CAP_NET_RAW": check_capability(CAP_NET_RAW),
|
|
"CAP_NET_ADMIN": check_capability(CAP_NET_ADMIN),
|
|
"CAP_SYS_ADMIN": check_capability(CAP_SYS_ADMIN),
|
|
}
|
|
|
|
|
|
def drop_privileges(
|
|
username: str = "nobody",
|
|
groupname: Optional[str] = None,
|
|
keep_caps: Optional[list] = None,
|
|
) -> bool:
|
|
if not check_root():
|
|
return False
|
|
|
|
try:
|
|
pw = pwd.getpwnam(username)
|
|
uid = pw.pw_uid
|
|
gid = pw.pw_gid
|
|
|
|
if groupname:
|
|
gr = grp.getgrnam(groupname)
|
|
gid = gr.gr_gid
|
|
|
|
# Set supplementary groups
|
|
os.setgroups([])
|
|
|
|
# Set GID first (can't change after dropping root)
|
|
os.setregid(gid, gid)
|
|
os.setreuid(uid, uid)
|
|
|
|
# Verify drop
|
|
if os.geteuid() == 0:
|
|
return False
|
|
|
|
# Re-apply specific capabilities if requested
|
|
if keep_caps:
|
|
_set_keepcaps()
|
|
_apply_caps(keep_caps)
|
|
|
|
return True
|
|
except (KeyError, PermissionError, OSError):
|
|
return False
|
|
|
|
|
|
def _set_keepcaps():
|
|
PR_SET_KEEPCAPS = 8
|
|
_libc.prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0)
|
|
|
|
|
|
def _apply_caps(caps: list):
|
|
header = _cap_header(version=_LINUX_CAPABILITY_VERSION_3, pid=0)
|
|
data = (_cap_data * 2)()
|
|
|
|
for cap in caps:
|
|
idx = cap // 32
|
|
bit = 1 << (cap % 32)
|
|
data[idx].effective |= bit
|
|
data[idx].permitted |= bit
|
|
|
|
_libc.capset(ctypes.byref(header), data)
|
|
|
|
|
|
def enforce_directory_permissions(
|
|
path: str,
|
|
mode: int = 0o700,
|
|
recursive: bool = True,
|
|
) -> int:
|
|
count = 0
|
|
target = Path(path)
|
|
|
|
if not target.exists():
|
|
target.mkdir(parents=True, mode=mode)
|
|
count += 1
|
|
return count
|
|
|
|
current = target.stat().st_mode & 0o777
|
|
if current != mode:
|
|
target.chmod(mode)
|
|
count += 1
|
|
|
|
if recursive:
|
|
for item in target.rglob("*"):
|
|
if item.is_dir():
|
|
item_mode = item.stat().st_mode & 0o777
|
|
if item_mode != mode:
|
|
item.chmod(mode)
|
|
count += 1
|
|
elif item.is_file():
|
|
file_mode = mode & 0o700 # Files get owner-only based on dir mode
|
|
item_mode = item.stat().st_mode & 0o777
|
|
if item_mode != file_mode:
|
|
item.chmod(file_mode)
|
|
count += 1
|
|
|
|
return count
|
|
|
|
|
|
def secure_path(path: str, owner_uid: Optional[int] = None) -> bool:
|
|
try:
|
|
p = Path(path)
|
|
if not p.exists():
|
|
return False
|
|
|
|
# Set ownership if specified
|
|
if owner_uid is not None:
|
|
os.chown(path, owner_uid, owner_uid)
|
|
|
|
# Restrict permissions
|
|
if p.is_dir():
|
|
p.chmod(0o700)
|
|
else:
|
|
p.chmod(0o600)
|
|
|
|
return True
|
|
except (OSError, PermissionError):
|
|
return False
|