|
@@ -9,6 +9,7 @@ import shutil
|
|
|
import subprocess
|
|
|
import sys
|
|
|
import glob
|
|
|
+from enum import IntEnum
|
|
|
from pathlib import Path
|
|
|
|
|
|
# Third party modules
|
|
@@ -178,6 +179,20 @@ interpArgParse.add_argument('script', type=str)
|
|
|
UID_MIN=1000
|
|
|
UID_MAX=60000
|
|
|
|
|
|
+class Severity(IntEnum):
|
|
|
+ EMERG = 0
|
|
|
+ ALERT = 1
|
|
|
+ CRIT = 2
|
|
|
+ ERR = 3
|
|
|
+ WARNING = 4
|
|
|
+ NOTICE = 5
|
|
|
+ INFO = 6
|
|
|
+ DEBUG = 7
|
|
|
+
|
|
|
+argparser = argparse.ArgumentParser()
|
|
|
+argparser.add_argument('--severity', type = int, default = 3, choices = list(map(int, Severity)))
|
|
|
+args = argparser.parse_args()
|
|
|
+
|
|
|
homePaths = []
|
|
|
for pw in pwd.getpwall():
|
|
|
if pw.pw_uid in range(UID_MIN, UID_MAX):
|
|
@@ -260,6 +275,8 @@ exceptions = ''
|
|
|
|
|
|
def logExceptions(description, paths = [], context = None, severity = 3):
|
|
|
global exceptions
|
|
|
+ if severity > args.severity:
|
|
|
+ return
|
|
|
exceptions += "%s\n" % description
|
|
|
if context != None:
|
|
|
exceptions += " Context: %s\n" % context
|
|
@@ -292,6 +309,14 @@ def auditProcess(proc):
|
|
|
logWarnings('Executable was moved or deleted', [exePath], 'Process %d' % proc.pid)
|
|
|
if isWorldWritable(exePath):
|
|
|
logExceptions('Executable is world-writable', [exePath], 'Process %d' % proc.pid)
|
|
|
+ if ruid == rootPwe.pw_uid:
|
|
|
+ connlist = proc.connections(kind = 'inet')
|
|
|
+ connset = set(c for c in connlist if c.status == psutil.CONN_LISTEN)
|
|
|
+ if len(connset) > 0:
|
|
|
+ childlist = proc.children(recursive = True)
|
|
|
+ childset = set(p for p in childlist if p.uids()[0] != rootPwe.pw_uid)
|
|
|
+ if len(childset) == 0:
|
|
|
+ logExceptions('Root process listen for connections but has no unprivileged worker', [exePath], 'Process %s' % proc.pid, 5)
|
|
|
|
|
|
def auditCommand(ruid, argList, cwd, env = {}, context = None):
|
|
|
if 'PATH' in env:
|
|
@@ -300,10 +325,10 @@ def auditCommand(ruid, argList, cwd, env = {}, context = None):
|
|
|
path = os.defpath
|
|
|
absArg0 = shutil.which(argList[0], path=path)
|
|
|
if absArg0 != None and is_interpreter(absArg0) and len(argList) > 1:
|
|
|
- (args, remainining) = interpArgParse.parse_known_args(argList)
|
|
|
- scriptPath = Path(args.script)
|
|
|
+ (interpargs, remainining) = interpArgParse.parse_known_args(argList)
|
|
|
+ scriptPath = Path(interpargs.script)
|
|
|
if not scriptPath.is_absolute():
|
|
|
- scriptPath = Path(cwd, args.script)
|
|
|
+ scriptPath = Path(cwd, interpargs.script)
|
|
|
try:
|
|
|
scriptPath = scriptPath.resolve()
|
|
|
if (scriptPath.stat().st_uid != rootPwe.pw_uid and
|
|
@@ -383,12 +408,12 @@ if len(rubypathWriteExceptions) > 0:
|
|
|
for proc in psutil.process_iter():
|
|
|
pid = proc.pid
|
|
|
ruid = proc.uids()[0]
|
|
|
- args = proc.cmdline()
|
|
|
+ procargs = proc.cmdline()
|
|
|
cwd = proc.cwd()
|
|
|
env = proc.environ()
|
|
|
auditProcess(proc)
|
|
|
- if len(args) > 0:
|
|
|
- auditCommand(ruid, args, cwd, env, 'Process %d' % pid)
|
|
|
+ if len(procargs) > 0:
|
|
|
+ auditCommand(ruid, procargs, cwd, env, 'Process %d' % pid)
|
|
|
|
|
|
# Passwords should be stored in /etc/shadow, not /etc/passwd
|
|
|
contentExceptions = []
|