Parcourir la source

concierge-permaudit: check running processes for permission issues

root il y a 7 ans
Parent
commit
ab51877602
1 fichiers modifiés avec 74 ajouts et 7 suppressions
  1. 74 7
      src/concierge-permaudit

+ 74 - 7
src/concierge-permaudit

@@ -1,13 +1,19 @@
 #!/usr/bin/python3
 
+# Code python modules
+import argparse
 import os
 import pwd
 import re
+import shutil
 import subprocess
 import sys
 import glob
 from pathlib import Path
 
+# Third party modules
+import psutil
+
 def get_perl_searchpath():
   # perl -e "print join $/, values @INC"
   try:
@@ -127,6 +133,18 @@ writePatterns = [
   '~/.logout' # tcsh shell
   ]
 
+interps = set([
+  '/bin/sh',
+  '/bin/bash',
+  '/usr/bin/perl',
+  '/usr/bin/python',
+  '/usr/bin/python3'
+  ])
+
+interpArgParse = argparse.ArgumentParser(description='Generic interpreter parser', add_help=False)
+interpArgParse.add_argument('interp', type=str)
+interpArgParse.add_argument('script', type=str)
+
 # Default values for Debian
 # TODO read values from /etc/login.defs
 UID_MIN=1000
@@ -137,6 +155,9 @@ for pw in pwd.getpwall():
   if pw.pw_uid in range(UID_MIN, UID_MAX):
     homePaths.append(pw.pw_dir)
 
+# Password database entry for root. By definition root uid = 0.
+rootPwe = pwd.getpwuid(0)
+
 def patternWalk(pattern):
   if pattern == '~' or pattern[:2] == '~/':
     for homePath in homePaths:
@@ -217,10 +238,54 @@ def logExceptions(message, pathList):
       exceptions += " * %s\n" % path.as_posix()
     exceptions += "\n"
 
+def logException(description, path, context = None):
+  global exceptions
+  exceptions += "%s\n" % description
+  exceptions += "  Path: %s\n" % path.as_posix()
+  if context != None:
+    exceptions += "  Context: %s\n" % context
+  exceptions += "\n"
+
 def printExceptions():
   global exceptions
   print(exceptions, end='')
 
+def auditProcess(proc):
+  ruid = proc.uids()[0]
+  pid = proc.pid
+  exePathStr = proc.exe()
+  if len(exePathStr) > 0:
+    exePath = Path(exePathStr)
+    try:
+      if (exePath.stat().st_uid != rootPwe.pw_uid and
+          exePath.stat().st_uid != ruid):
+        logException('Executable is owned by another, non-root user', exePath.resolve(), 'Process %d' % proc.pid)
+    except:
+      pass
+    if isWorldWritable(exePath):
+      logException('Executable is world-writable', exePath.resolve(), 'Process %d' % proc.pid)
+
+def auditCommand(ruid, argList, cwd, env = {}, context = None):
+  if 'PATH' in env:
+    path = env['PATH']
+  else:
+    path = os.defpath
+  absArg0 = shutil.which(argList[0], path=path)
+  if absArg0 in interps and len(argList) > 1:
+    (args, remainining) = interpArgParse.parse_known_args(argList)
+    scriptPath = Path(args.script)
+    if not scriptPath.is_absolute():
+      scriptPath = Path(cwd, args.script)
+    try:
+      scriptPath = scriptPath.resolve()
+      if (scriptPath.stat().st_uid != rootPwe.pw_uid and
+          scriptPath.stat().st_uid != ruid):
+        logException('Script is owned by another, non-root user', scriptPath.resolve(), context)
+    except FileNotFoundError:
+      pass # warning('File not found')
+    if isWorldWritable(scriptPath):
+      logException('Script is world-writable', scriptPath.resolve(), context)
+
 readExceptions = []
 for pattern in readPatterns:
   for strPath in patternWalk(pattern):
@@ -280,13 +345,15 @@ for strPath in get_ruby_searchpath():
 
 logExceptions('These ruby search paths are world-writable', rubypathWriteExceptions)
 
-processWriteExceptions = []
-for strPath in patternWalk('/proc/*/exe'):
-  path = Path(strPath)
-  if isWorldWritable(path):
-    processWriteExceptions.append(path.resolve())
-
-logExceptions('Running processes use world-writable executables', processWriteExceptions)
+for proc in psutil.process_iter():
+  pid = proc.pid
+  ruid = proc.uids()[0]
+  args = proc.cmdline()
+  cwd = proc.cwd()
+  env = proc.environ()
+  auditProcess(proc)
+  if len(args) > 0:
+    auditCommand(ruid, args, cwd, env, 'Process %d' % pid)
 
 # Passwords should be stored in /etc/shadow, not /etc/passwd
 contentExceptions = []