Source code for deescalate.main

__author__ = 'stef'

import os
import platform

from ._deescalate import py_prctl, C_CapabilitySet, C_BoundingSet
from .constants import C
from .utils import normalize_uid, normalize_gid, normalize_list_of_caps, capset_string_to_flag

is_linux = platform.system().lower().strip().startswith('linux')


[docs]class CapabilitySet(C_CapabilitySet): """ Represent a set of capabilities. Notes ----- - Usually a CapabilitySet is used directly from one of its instance object (effective, permitted, inheritable). - A CapabilitySet is iterable, so to get the capabilities it countains:: set(effective) - To check if a capability is in the set:: b'net_admin' in effective - Arithmetic operators can be used to add/remove capabilities:: effective -= b'net_admin' permitted += b'net_raw' inheritable += b'setuid, setgid' inheritable -= [b'sys_chroot', b'sys_ptrace'] References ---------- - `Capabilities manual page <http://man7.org/linux/man-pages/man7/capabilities.7.html>`_ """ instances = {} def __init__(self, capset): super(CapabilitySet, self).__init__(capset)
[docs] def remove_all_except(self, caps_to_keep): """ Remove every capability from the set, except the ones given in `caps_to_keep`. Parameters ---------- caps_to_keep: bytes or list of bytes Do not drop these capabilities """ caps_to_drop = C.SUPPORTED_CAPS_VALUES.difference(normalize_list_of_caps(caps_to_keep)) self.__isub__(caps_to_drop)
def set(self, caps): caps_to_keep = normalize_list_of_caps(caps) caps_to_clear = C.SUPPORTED_CAPS_VALUES.difference(caps_to_keep) self._modify(caps_to_keep, C.FLAG_VALUES['set']) self._modify(caps_to_clear, C.FLAG_VALUES['clear']) def __iadd__(self, caps_to_add): caps_to_add = normalize_list_of_caps(caps_to_add).difference(set(self)) self._modify(caps_to_add, C.FLAG_VALUES['set']) return self def __isub__(self, caps_to_drop): caps_to_drop = normalize_list_of_caps(caps_to_drop).intersection(set(self)) self._modify(caps_to_drop, C.FLAG_VALUES['clear']) return self @classmethod
[docs] def get_instance(cls, capset): """ CapabilitySet factory (class method). Parameters ---------- capset: int or string which capability set to deal with ("effective", "permitted" or "inheritable") """ capset = capset_string_to_flag(capset) if capset not in cls.instances: cls.instances[capset] = cls(capset) return cls.instances[capset]
[docs]class BoundingSet(C_BoundingSet): """ Represents the bounding capability set. Notes ----- - BoundindSet is iterable:: list_of_caps = list(bounding_set) - To check if a capability is in the bounding set:: b'net_admin' in bounding_set - The BoundingSet just supports removing some capabilities it countains. Use:: bounding_set -= b'net_admin,mac_override' bounding_set -= [b'syslog', b'wake_alarm'] """ instance = None def __init__(self): super(BoundingSet, self).__init__() def __isub__(self, caps_to_drop): [self._remove_one_cap(i) for i in normalize_list_of_caps(caps_to_drop)] return self def remove_all_except(self, caps_to_keep): caps_to_drop = set(self).difference(normalize_list_of_caps(caps_to_keep)) self.__isub__(caps_to_drop) @classmethod
[docs] def get_instance(cls): """ BoundingSet factory (class method). """ if cls.instance is None: cls.instance = cls() return cls.instance
permitted = CapabilitySet.get_instance(C.FLAGS['permitted']) """Permitted capability set""" inheritable = CapabilitySet.get_instance(C.FLAGS['inheritable']) """Inheritable capability set""" effective = CapabilitySet.get_instance(C.FLAGS['effective']) """Effective capability set""" bounding_set = BoundingSet.get_instance() """Capability bounding set"""
[docs]def get_securebits(): """ Return the currently defined secure bits Returns ------- 2uple (the securebits as an int, a dict of securebits) """ res = py_prctl(C.PRCTL['get_securebits'], 0, 0, 0, 0) if res == -1: raise RuntimeError("get_securebits() failed") d = { 'SECBIT_NOROOT': res & C.SECBIT_NOROOT, 'SECBIT_NOROOT_LOCKED': res & C.SECBIT_NOROOT_LOCKED, 'SECBIT_KEEP_CAPS': res & C.SECBIT_KEEP_CAPS, 'SECBIT_KEEP_CAPS_LOCKED': res & C.SECBIT_KEEP_CAPS_LOCKED, 'SECBIT_NO_SETUID_FIXUP': res & C.SECBIT_NO_SETUID_FIXUP, 'SECBIT_NO_SETUID_FIXUP_LOCKED': res & C.SECBIT_NO_SETUID_FIXUP_LOCKED } return res, d
[docs]def set_noroot(locked=True): """ Set the `SECBIT_NOROOT` securebit. Parameters ---------- locked: bool if True, also set `SECBIT_NOROOT_LOCKED` Raises ------ RuntimeError if operation fails """ current = get_securebits()[0] modified = (current | C.SECBIT_NOROOT_LOCKED | C.SECBIT_NOROOT) if locked else (current | C.SECBIT_NOROOT) res = py_prctl(C.PRCTL['set_securebits'], modified, 0, 0, 0) if res == -1: raise RuntimeError("set_noroot failed")
[docs]def set_keep_caps(locked=True): """ Set the `SECBIT_KEEP_CAPS` securebit. Parameters ---------- locked: bool if True, also set `SECBIT_KEEP_CAPS_LOCKED` Raises ------ RuntimeError if operation fails """ current = get_securebits()[0] modified = (current | C.SECBIT_KEEP_CAPS_LOCKED | C.SECBIT_KEEP_CAPS) if locked else (current | C.SECBIT_KEEP_CAPS) res = py_prctl(C.PRCTL['set_securebits'], modified, 0, 0, 0) if res == -1: raise RuntimeError("set_keep_caps failed")
[docs]def set_no_setuid_fixup(locked=True): """ Set the `SECBIT_NO_SETUID_FIXUP` securebit. Parameters ---------- locked: bool if True, also set `SECBIT_NO_SETUID_FIXUP_LOCKED` Raises ------ RuntimeError if operation fails """ current = get_securebits()[0] modified = (current | C.SECBIT_NO_SETUID_FIXUP_LOCKED | C.SECBIT_NO_SETUID_FIXUP) if locked \ else (current | C.SECBIT_NO_SETUID_FIXUP) res = py_prctl(C.PRCTL['set_securebits'], modified, 0, 0, 0) if res == -1: raise RuntimeError("set_no_setuid_fixup failed")
[docs]def lockdown_account(uid=None, gid=None, caps_to_keep=None): """ Deescalate the privileges of the running process. `lockdown_account` will: - set the secure bits `noroot`, `keep_caps`, `no_setuid_fixup` and their locked companions - perform a `setgid` and a `setuid` - restrict the 3 cap sets and the bounding set to the list given in `caps_to_keep` - set `no_new_privs` Parameters ---------- uid: int or string, optional switch to this UID gid: int or string, optional switch to this GID caps_to_keep: list of bytes, optional a list of capabilities to keep Raises ------ RuntimeError if some capability operation fails OSError operation not permitted Note ---- When not on Linux, only setgid and setuid will be performed Examples -------- >>> lockdown_account('www-data', 'www-data', 'net_bind_service') >>> lockdown_account('scapy', 'scapy', ['net_admin', 'net_raw']) """ caps_to_keep = normalize_list_of_caps(caps_to_keep) global effective, inheritable, permitted, is_linux if is_linux: if b"setpcap" not in effective: if b"setpcap" not in permitted: raise RuntimeError("the current process doesn't have setpcap cap") else: effective += b"setpcap" if uid is not None: if b'setuid' not in effective: if b'setuid' not in permitted: raise RuntimeError("the current process doesn't have setuid cap") else: effective += b"setuid" if gid is not None: if b'setgid' not in effective: if b'setgid' not in permitted: raise RuntimeError("the current process doesn't have setgid cap") else: effective += b"setgid" set_noroot() set_keep_caps() set_no_setuid_fixup() if uid is not None: os.setgid(normalize_gid(uid, gid)) os.setuid(normalize_uid(uid)) if is_linux: capset = set(permitted).intersection(caps_to_keep) effective += b'setpcap' bounding_set.remove_all_except(capset) inheritable.set(capset) effective.set(capset) permitted.set(capset) set_no_new_privs()
[docs]def set_no_new_privs(): """ Set `no_new_privs`. Notes ----- - With `no_new_privs` set, `execve` promises not to grant the privilege to do anything that could not have been done without the `execve` call. - See `prctl manual page <http://man7.org/linux/man-pages/man2/prctl.2.html>`_ """ py_prctl(C.PRCTL['set_no_new_privs'], 1, 0, 0, 0)