ÿØÿà JFIF    ÿÛ „  ( %!1!%)+//.383,7(-.+  -%%-////---/-.+/--+------/------/--0+--/-/-----.-----ÿÀ  ¥2" ÿÄ     ÿÄ J    ! 1AQ"aq2‘#BR‚¡ÁÑ3br’¢±Âð$CSƒ²á4c“%DsÓñÿÄ   ÿÄ *  !1AQa‘"2q3±ð#b¡ÿÚ   ? ¼QxJQaÍuò¸Zö Úü8,ÐÚú "SSn<rçù–´âE—^ªBÖ9À\†¸ÔÁT­ÃÛ5 ëd´³Í#Ý;Þ38œî ¶H£M:wÎ3…³…âpÔF&‚FK¸9„â4àGEõªfÿ ‘ñ(ßw­pŽF|È¥ù®häðÍѶ¹‘[ÒinÙW¶ùñY˜Q{›K"išÒ[Ú8žë\F¹@-?v"ÔU”,ìöžkÿ {I‡£šÍ?e ríV ?> ......................................... ............................................................................. ÿØÿà JFIF    ÿÛ „  ( %!1!%)+//.383,7(-.+  -%%-////---/-.+/--+------/------/--0+--/-/-----.-----ÿÀ  ¥2" ÿÄ     ÿÄ J    ! 1AQ"aq2‘#BR‚¡ÁÑ3br’¢±Âð$CSƒ²á4c“%DsÓñÿÄ   ÿÄ *  !1AQa‘"2q3±ð#b¡ÿÚ   ? ¼QxJQaÍuò¸Zö Úü8,ÐÚú "SSn<rçù–´âE—^ªBÖ9À\†¸ÔÁT­ÃÛ5 ëd´³Í#Ý;Þ38œî ¶H£M:wÎ3…³…âpÔF&‚FK¸9„â4àGEõªfÿ ‘ñ(ßw­pŽF|È¥ù®häðÍѶ¹‘[ÒinÙW¶ùñY˜Q{›K"išÒ[Ú8žë\F¹@-?v"ÔU”,ìöžkÿ {I‡£šÍ?e ríV ?> ......................................... ............................................................................. ???????????????????????????????????? ???????????????????????????????????? ÿØÿà JFIF    ÿÛ „  ( %!1!%)+//.383,7(-.+  -%%-////---/-.+/--+------/------/--0+--/-/-----.-----ÿÀ  ¥2" ÿÄ     ÿÄ J    ! 1AQ"aq2‘#BR‚¡ÁÑ3br’¢±Âð$CSƒ²á4c“%DsÓñÿÄ   ÿÄ *  !1AQa‘"2q3±ð#b¡ÿÚ   ? ¼QxJQaÍuò¸Zö Úü8,ÐÚú "SSn<rçù–´âE—^ªBÖ9À\†¸ÔÁT­ÃÛ5 ëd´³Í#Ý;Þ38œî ¶H£M:wÎ3…³…âpÔF&‚FK¸9„â4àGEõªfÿ ‘ñ(ßw­pŽF|È¥ù®häðÍѶ¹‘[ÒinÙW¶ùñY˜Q{›K"išÒ[Ú8žë\F¹@-?v"ÔU”,ìöžkÿ {I‡£šÍ?e ríV ?> ......................................... ............................................................................. ÿØÿà JFIF    ÿÛ „  ( %!1!%)+//.383,7(-.+  -%%-////---/-.+/--+------/------/--0+--/-/-----.-----ÿÀ  ¥2" ÿÄ     ÿÄ J    ! 1AQ"aq2‘#BR‚¡ÁÑ3br’¢±Âð$CSƒ²á4c“%DsÓñÿÄ   ÿÄ *  !1AQa‘"2q3±ð#b¡ÿÚ   ? ¼QxJQaÍuò¸Zö Úü8,ÐÚú "SSn<rçù–´âE—^ªBÖ9À\†¸ÔÁT­ÃÛ5 ëd´³Í#Ý;Þ38œî ¶H£M:wÎ3…³…âpÔF&‚FK¸9„â4àGEõªfÿ ‘ñ(ßw­pŽF|È¥ù®häðÍѶ¹‘[ÒinÙW¶ùñY˜Q{›K"išÒ[Ú8žë\F¹@-?v"ÔU”,ìöžkÿ {I‡£šÍ?e ríV ?> ......................................... ............................................................................. ???????????????????????????????????? ???????????????????????????????????? # coding=utf-8 # Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2019 All Rights Reserved # # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENSE.TXT """ clsudoers unix group management for lvemanager. Operator upgrade note (CLOS-4576, F-37): ``add_unix_user_to_sudoers()`` now fails closed when the ``clsudoers`` unix group has pre-existing members outside the legitimate set (``admins()`` + the user being added + package-owned members ``root`` / ``lvemanager``). Previously, ``groupadd -f`` was silently idempotent and any stowaway primary-GID owners or ``gr_mem`` entries (left by manual admin steps or another RPM's post-install) were quietly elevated to passwordless sudo for selectorctl / cloudlinux-selector. The allowlist explicitly includes ``root`` (implicit group membership in many tools) and ``lvemanager`` (the package's runtime user, placed in ``clsudoers`` by the lvemanager RPM's ``%post`` for SPA's clsudo flow on DirectAdmin/Plesk) — so the gate fires only on the F-37 attack signature (a stowaway secondary member or a primary-GID-reuse owner), not on legitimate package-installed state. After upgrading to a build that ships this change, the first admin-add operation on an AFFECTED host (one with non-package stowaways in the group) raises an exception from ``_assert_sudoers_group_clean()``. To recover, operators must either: * remove the stowaway entries from the ``clsudoers`` group (audit with ``getent group clsudoers`` and ``awk -F: '$4== {print $1}' /etc/passwd`` for primary-GID owners), OR * add those users via the panel's ``admins()`` interface (panel admin user list) so they are recognised as legitimate members before retrying. This is intentional — silent elevation of unaudited accounts is the vulnerability being closed; reverting to ``groupadd -f`` idempotence is not an acceptable workaround. """ from __future__ import print_function from __future__ import division from __future__ import absolute_import import grp import pwd import subprocess from cl_proc_hidepid import remount_proc from clcommon.cpapi import admins, getCPName from clcommon.sysctl import SysCtlConf, SYSCTL_CL_CONF_FILE from clcommon.const import Feature from clcommon.cpapi import is_panel_feature_supported from clsudo import Clsudo # Default admins group DEFAULT_GROUP_NAME = "admin" # Group name for fs.proc_super_gid SUPER_GROUP_NAME = "clsupergid" # Groupname for sudoers SUDOERS_GROUP_NAME = "clsudoers" # System-UID ceiling: an upper bound on the UID an account may have to be # considered "package-installed" (`useradd --system`). The allowlist below # additionally requires the account name to belong to the lvemanager # package family — UID range alone is NOT the gate; it is the SECOND # precondition. Hard-coding 999 is safe across CL7/8/9/10 and ubuntu22. _SYSTEM_UID_MAX = 999 # Always-allowed package-owned member names. The lvemanager package's # canonical SPA runtime user is `lvemanager`. `root` is implicit / always # benign. DirectAdmin installs sometimes append a UUID suffix # (`lvemanager_`); the helper below matches that family by name # prefix AND requires the matched account to live in the system-UID # range. Anything else (a `daemon`/`bin`/`mail` system account, a # regular user, a stowaway with a fabricated name) fails closed. _PACKAGE_OWNED_CLSUDOERS_MEMBERS = frozenset({"root", "lvemanager"}) _PACKAGE_OWNED_CLSUDOERS_NAME_PREFIXES = ("lvemanager_",) def _is_system_owned_user(name): """Return True if `name` is a known package-owned `clsudoers` member (always-allowed name OR an lvemanager-package-family name) AND lives in the system-UID range. Both conditions are required — a regular user named `lvemanager_evil` (UID 5000) is NOT package-owned; an unrelated system account named `daemon` (UID 2) is NOT package-owned either. The gate exists to fire on the F-37 priv_group_reuse signature; we only want to exempt the RPM's own post-install state. """ if name == "root": # Implicit group membership in many tools; never a stowaway. return True is_known_name = ( name in _PACKAGE_OWNED_CLSUDOERS_MEMBERS or any(name.startswith(p) for p in _PACKAGE_OWNED_CLSUDOERS_NAME_PREFIXES) ) if not is_known_name: return False try: return pwd.getpwnam(name).pw_uid <= _SYSTEM_UID_MAX except KeyError: # gr_mem references a non-existent user — that itself is suspicious # (stowaway entry); let the gate flag it. return False def _add_user_to_group(user_name, group_name): """Add user to given unix group""" retcode = subprocess.call(["/usr/bin/gpasswd", "-a", user_name, group_name]) if retcode != 0: return False return True # Remove user from group def _remove_user_from_group(user_name, group_name): retcode = subprocess.call(["/usr/bin/gpasswd", "-d", user_name, group_name]) if retcode != 0: return False return True def _add_admins_into_group(group_name, new_admin_name): """ Add all present DA admins (plus new_admin_name admin) to supplied group :param new_admin_name: new admin name to add :return: """ # Get admin list from DA and append new admin name to it admin_list = list(admins()) if new_admin_name not in admin_list: admin_list.append(new_admin_name) for admin in admin_list: _add_user_to_group(admin, group_name) def _create_group(group_name): """Create group with given name""" retcode = subprocess.call(["/usr/sbin/groupadd", "-f", group_name]) if retcode != 0: return False return True def _add_admins_into_supergid_grp(new_admin_name): """ Add all present DA admins (plus new_admin_name admin) to current supergid group :param new_admin_name: new admin name to add :return: """ # Determine SUPER_GROUP_NAME gid super_gid = str(grp.getgrnam(SUPER_GROUP_NAME).gr_gid) sysctl_cfg = SysCtlConf(config_file=SYSCTL_CL_CONF_FILE) # returns set gid from sysctl.conf or kernel default proc_super_gid = sysctl_cfg.get('fs.proc_super_gid') # set fs.proc_super_gid and add admins to group with this gid if: # 1. it was not found in sysctl.conf; if not sysctl_cfg.has_parameter('fs.proc_super_gid'): sysctl_cfg.set('fs.proc_super_gid', super_gid) _add_admins_into_group(SUPER_GROUP_NAME, new_admin_name) return elif getCPName() == 'DirectAdmin': # Only for DA try: admin_gid = str(grp.getgrnam(DEFAULT_GROUP_NAME).gr_gid) except KeyError: admin_gid = None if proc_super_gid == admin_gid: sysctl_cfg.set('fs.proc_super_gid', super_gid) _add_admins_into_group(SUPER_GROUP_NAME, new_admin_name) return # otherwise read fs.proc_super_gid and add admins to group with this gid try: proc_super_gid = int(proc_super_gid) except ValueError: raise RuntimeError("Bad fs.proc_super_gid option value in /etc/sysctl.conf") # add all panel admins into custom proc_super_gid group proc_super_name = grp.getgrgid(proc_super_gid).gr_name _add_admins_into_group(proc_super_name, new_admin_name) def _assert_sudoers_group_clean(group_name, expected_members): """ Refuse to grant %group_name passwordless sudo if the unix group already exists with members or primary-GID owners that the lvemanager flow did not place there. `groupadd -f` is silently idempotent and would otherwise elevate any pre-existing member on the first call. :param group_name: target group (clsudoers) :param expected_members: iterable of usernames the lvemanager flow is about to authorize (panel admins + the user being added). :raises Exception: if any unexpected secondary member or primary-GID owner is found. """ try: gr = grp.getgrnam(group_name) except KeyError: # Group does not exist yet — _create_group will create it empty. return expected = set(expected_members) # Secondary `gr_mem` entries that are neither expected (panel admins + # the user being added) nor package-installed system accounts are the # F-37 stowaway signature. The system-account allowlist covers # legitimate runtime users the lvemanager RPM (and DA-side installers) # place in the group at install time, including DA variants like # `lvemanager_` which still land in the UID system range. unexpected_members = [ m for m in gr.gr_mem if m not in expected and not _is_system_owned_user(m) ] # Primary-GID owners are the canonical F-37 attack: a user account # whose primary group landed on clsudoers.gr_gid via GID-reuse or a # buggy installer. Only the lvemanager-package family of system # accounts is legitimately allowed to have clsudoers as a primary # group; an unrelated system user with the same primary GID is the # priv_group_reuse signature and must trip the gate. Reuse the # `_is_system_owned_user` predicate so the name + UID criteria stay # in lockstep with the gr_mem path above. unexpected_primary = [ p.pw_name for p in pwd.getpwall() if p.pw_gid == gr.gr_gid and p.pw_name not in expected and not _is_system_owned_user(p.pw_name) ] if unexpected_members or unexpected_primary: raise Exception( "ERROR: refusing to grant %%%s passwordless sudo: group already " "has unexpected members %r and primary-GID owners %r" % ( group_name, unexpected_members, unexpected_primary)) def add_unix_user_to_sudoers(name): # create all supergid stuff only if regular CL edition if is_panel_feature_supported(Feature.LVE): if not _create_group(SUPER_GROUP_NAME): raise Exception("ERROR: Can't create %s group\n" % SUPER_GROUP_NAME) _add_admins_into_supergid_grp(name) if not _add_user_to_group(name, SUPER_GROUP_NAME): raise Exception("ERROR: Can't add user %s to %s group\n" % ( name, SUPER_GROUP_NAME)) # Gate: if clsudoers already exists with non-lvemanager members, fail # closed rather than silently elevate them via Clsudo.add_lvemanager_group. # Allowlist members the lvemanager RPM itself places in the group at # package install time (see _PACKAGE_OWNED_CLSUDOERS_MEMBERS) so the # gate fires only on the F-37 attack signature, not on legitimate # package-installed state. expected_clsudoers = set(admins()) expected_clsudoers.add(name) expected_clsudoers.update(_PACKAGE_OWNED_CLSUDOERS_MEMBERS) _assert_sudoers_group_clean(SUDOERS_GROUP_NAME, expected_clsudoers) if not _create_group(SUDOERS_GROUP_NAME): raise Exception("ERROR: Can't create %s group\n" % SUDOERS_GROUP_NAME) if not _add_user_to_group(name, SUDOERS_GROUP_NAME): raise Exception("ERROR: Can't add user %s to %s group\n" % ( name, SUDOERS_GROUP_NAME)) # Add SUDOERS_GROUP_NAME group to /etc/sudoers sudo = Clsudo() sudo.add_lvemanager_group(SUDOERS_GROUP_NAME) # CAG-796: use hidepid=2 when mounting /proc remount_proc() def remove_unix_user_from_sudoers(name): # Remove user from all groups _remove_user_from_group(name, SUPER_GROUP_NAME) _remove_user_from_group(name, SUDOERS_GROUP_NAME)