Files
bigbrother/utils/permissions.py
T
n0mad1k 62a1010ab4 Add Phase 1 utility modules: crypto, networking, logging, stealth, resource, permissions, config_loader, bettercap_api
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
2026-03-18 08:11:25 -04:00

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