Mini Shell

Direktori : /opt/maint/bin/
Upload File :
Current File : //opt/maint/bin/secureperms.py

#!/opt/imh-python/bin/python3
"""Script to fix common security issues on shared servers"""
from pathlib import Path
import platform
from os import chown
import pwd
import grp
from stat import S_IWGRP, S_IWOTH, S_IMODE
from subprocess import run, DEVNULL
import distro
import rads


def main():
    users = rads.all_cpusers(owners=False)
    print("Checking /home...")
    set_perms(Path('/home'), 0o711, (0, 0), 'root:root')
    print("Removing shell access for unauthorized users...")
    check_shells(users)
    if not IS_SUPHP:
        return
    print("Checking user home folder permissions...")
    nobody_gid = grp.getgrnam('nobody').gr_gid
    for user in users:
        fix_user_perms(user, nobody_gid)


def check_shells(users: list[str]):
    """Remove shell access for users that shouldn't have it"""
    if distro.id() == 'cloudlinux':
        print("CloudLinux server detected. Allowing /bin/bash shells")
        main_shell = '/bin/bash'
    else:
        main_shell = '/usr/local/cpanel/bin/jailshell'
    allowed_shells = (main_shell, '/usr/local/cpanel/bin/noshell')
    reload_crond = False
    for user in users:
        if user in rads.SYS_USERS:
            continue
        try:
            user_shell = pwd.getpwnam(user).pw_shell
        except KeyError:
            continue
        if user_shell in allowed_shells:
            continue
        reload_crond = True
        LOGGER.info(
            'Changing shell of %s from %s to %s',
            user,
            user_shell,
            main_shell,
        )
        chsh = ['chsh', '-s', main_shell, user]
        run(chsh, stdout=DEVNULL, check=False)
        # fmt: off
        sed = [
            'sed', '-i', f"s@{user_shell}@{main_shell}@g",
            f'/var/spool/cron/{user}',
        ]
        # fmt: on
        run(sed, stdout=DEVNULL, check=False)
    if reload_crond:
        run(['service', 'crond', 'reload'], stdout=DEVNULL, check=False)


def set_perms(
    path: Path, new_mode: int, uid_gid: tuple[int, int], usr_grp: str
):
    try:
        stat = path.stat()
    except OSError:
        return
    cur_mode = S_IMODE(stat.st_mode)
    if cur_mode != new_mode:
        path.chmod(new_mode)
        LOGGER.info(
            '%s --> Changed mode from %s to %s',
            path,
            oct(cur_mode)[2:],
            oct(new_mode)[2:],
        )
    if uid_gid != (stat.st_uid, stat.st_gid):
        chown(path, uid_gid[0], uid_gid[1])
        LOGGER.info('%s --> Changed owner:group to %s', path, usr_grp)


def fix_user_perms(user: str, nobody_gid: int):
    try:
        info = pwd.getpwnam(user)
    except rads.CpuserError as exc:
        LOGGER.error(exc)
        return
    homedir = Path(info.pw_dir)
    if (
        info.pw_dir == '/home'
        or not info.pw_dir.startswith('/home')
        or not homedir.is_dir()
    ):
        return
    if info.pw_uid == 0:  # malicious cpmove restores can do this
        rads.send_email(
            'steam@imhadmin.net',
            subject='SECURITY: cPanel user with UID of 0',
            body=f"secureperms found {user}@{platform.node()} has a UID of 0",
        )
        return
    set_perms(
        homedir,
        0o711,
        (info.pw_uid, info.pw_gid),
        f'{user}:{user}',
    )
    set_perms(
        homedir / 'public_html',
        0o750,
        (info.pw_uid, nobody_gid),
        f'{user}:nobody',
    )
    set_perms(
        homedir / '.my.cnf',
        0o600,
        (info.pw_uid, info.pw_gid),
        f'{user}:{user}',
    )
    # check if homedir/logs is writable by group or world
    try:
        logs_mode = (homedir / 'logs').stat().st_mode
    except OSError:
        logs_mode = 0
    if logs_mode & S_IWOTH or logs_mode & S_IWGRP:
        set_perms(
            homedir / 'logs',
            0o700,
            (info.pw_uid, info.pw_gid),
            f'{user}:{user}',
        )
    # fix docroot perms
    try:
        userdata = rads.UserData(user)
    except rads.CpuserError:
        LOGGER.error(exc)
        return
    for docroot in map(Path, userdata.all_roots):
        set_perms(
            docroot,
            0o750,
            (info.pw_uid, nobody_gid),
            f'{user}:nobody',
        )


if __name__ == '__main__':
    # cron config appends stdout/err to /var/log/maint/secureperms.log
    LOGGER = rads.setup_logging(
        path=None, name='secureperms', print_out='stdout', loglevel='INFO'
    )
    IS_SUPHP = Path('/etc/apache2/conf.modules.d/90-suphp.conf').is_file()
    main()

Zerion Mini Shell 1.0