Mini Shell
#!/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