|
@@ -0,0 +1,224 @@
|
|
|
|
+#!/usr/bin/python3
|
|
|
|
+
|
|
|
|
+import os
|
|
|
|
+import pwd
|
|
|
|
+import subprocess
|
|
|
|
+import sys
|
|
|
|
+import glob
|
|
|
|
+from pathlib import Path
|
|
|
|
+
|
|
|
|
+def get_perl_inc():
|
|
|
|
+ # perl -e "print join $/, values @INC"
|
|
|
|
+ try:
|
|
|
|
+ res = subprocess.check_output(['perl', '-e', 'print join $/, values @INC'])
|
|
|
|
+ return res.decode('utf-8').split("\n")
|
|
|
|
+ except FileNotFoundError:
|
|
|
|
+ return []
|
|
|
|
+
|
|
|
|
+readPatterns = [
|
|
|
|
+ '/etc/ssl/*_key',
|
|
|
|
+ '~/.ssh/identity',
|
|
|
|
+ '~/.ssh/id_dsa',
|
|
|
|
+ '~/.ssh/id_rsa',
|
|
|
|
+ '~/.ssh/id_ecdsa',
|
|
|
|
+ '~/.ssh/id_ed25519',
|
|
|
|
+ '~/.git-credentials',
|
|
|
|
+ '~/.config/git/credentials',
|
|
|
|
+ '~/.subversion/auth',
|
|
|
|
+ '%APPDATA%/Subversion/auth/',
|
|
|
|
+ '~/.hgrc',
|
|
|
|
+ '~/.netrc',
|
|
|
|
+ '~/.config/filezilla/filezilla.xml',
|
|
|
|
+ '~/.xchat2/servlist_.conf',
|
|
|
|
+ '~/.mozilla/firefox/*/key3.db',
|
|
|
|
+ '~/.mozilla/firefox/*/logins.json',
|
|
|
|
+ '~/.icedove/*/key3.db',
|
|
|
|
+ '~/.icedove/*/logins.json',
|
|
|
|
+ '~/.thunderbird/*/key3.db',
|
|
|
|
+ '~/.thunderbird/*/logins.json',
|
|
|
|
+ '~/.purple/accounts.xml',
|
|
|
|
+ '~/.python_history',
|
|
|
|
+ '~/.bash_history',
|
|
|
|
+ '~/.config/sonata/sonatarc',
|
|
|
|
+ '/etc/sympa/sympa.conf*',
|
|
|
|
+ '/etc/dolibarr/conf.php*',
|
|
|
|
+ '/etc/letsencrypt/archive/*/privkey*.pem',
|
|
|
|
+ '/etc/letsencrypt/accounts/*/directory/*/private_key.json',
|
|
|
|
+ '/etc/letsencrypt/keys/*.pem',
|
|
|
|
+ '/etc/cups/ssl/*.key',
|
|
|
|
+ '/etc/unbound/*.key'
|
|
|
|
+ ]
|
|
|
|
+
|
|
|
|
+writePatterns = [
|
|
|
|
+ '/etc/ld.so.conf',
|
|
|
|
+ '/etc/ld.so.conf.d/*',
|
|
|
|
+ '/etc/init.d/',
|
|
|
|
+ '/etc/rc.local',
|
|
|
|
+ '/etc/rd*.d/*',
|
|
|
|
+ '/etc/cron.d/*',
|
|
|
|
+ '/etc/cron.hourly/*',
|
|
|
|
+ '/etc/cron.weekly/*',
|
|
|
|
+ '/etc/cron.daily/*',
|
|
|
|
+ '/etc/cron.monthly/*',
|
|
|
|
+ '~/.aliases',
|
|
|
|
+ '/etc/update-motd.d/*',
|
|
|
|
+ '/etc/profile', # dash shell, bash shell
|
|
|
|
+ '/etc/profile.d/*.sh', # dash shell, bash shell
|
|
|
|
+ '~/.profile',
|
|
|
|
+ '~/.inputrc',
|
|
|
|
+ '/etc/bash.bashrc', # bash shell
|
|
|
|
+ '~/.bashrc', # bash shell
|
|
|
|
+ '~/.bash_aliases', # bash shell
|
|
|
|
+ '/etc/bash_completion', # bash shell
|
|
|
|
+ '/etc/bash_completion.d/*', # bash shell
|
|
|
|
+ '~/.bash_completion', # bash shell
|
|
|
|
+ '~/.bash_profile', # bash shell
|
|
|
|
+ '~/.bash_login', # bash shell
|
|
|
|
+ '~/.bash_logout', # bash shell
|
|
|
|
+ '~/.config/fish/config.fish', # fish shell
|
|
|
|
+ '~/.zshrc', # zsh shell
|
|
|
|
+ '/etc/csh.cshrc', # tcsh shell
|
|
|
|
+ '/etc/csh.login', # tcsh shell
|
|
|
|
+ '~/.tcshrc', # tcsh shell
|
|
|
|
+ '~/.cshrc', # tcsh shell
|
|
|
|
+ '~/.login', # tcsh shell
|
|
|
|
+ '/etc/csh.logout', # tcsh shell
|
|
|
|
+ '~/.logout' # tcsh shell
|
|
|
|
+ ]
|
|
|
|
+
|
|
|
|
+# Default values for Debian
|
|
|
|
+# TODO read values from /etc/login.defs
|
|
|
|
+UID_MIN=1000
|
|
|
|
+UID_MAX=60000
|
|
|
|
+
|
|
|
|
+homePaths = []
|
|
|
|
+for pwd in pwd.getpwall():
|
|
|
|
+ if pwd.pw_uid in range(UID_MIN, UID_MAX):
|
|
|
|
+ homePaths.append(pwd.pw_dir)
|
|
|
|
+
|
|
|
|
+def patternWalk(pattern):
|
|
|
|
+ if pattern == '~' or pattern[:2] == '~/':
|
|
|
|
+ for homePath in homePaths:
|
|
|
|
+ yield from patternWalk(homePath + pattern[1:])
|
|
|
|
+ else:
|
|
|
|
+ yield from glob.glob(pattern)
|
|
|
|
+
|
|
|
|
+writePatternsParents = [
|
|
|
|
+ ]
|
|
|
|
+
|
|
|
|
+for writePattern in writePatterns:
|
|
|
|
+ path = Path(writePattern).parent
|
|
|
|
+ homePath = Path('~')
|
|
|
|
+ while path not in writePatternsParents:
|
|
|
|
+ writePatternsParents.append(path)
|
|
|
|
+ # Do not walk up past the home path for now
|
|
|
|
+ if path != homePath:
|
|
|
|
+ path = path.parent
|
|
|
|
+ # Now, walk past the home path(s)
|
|
|
|
+ for homePath in patternWalk('~'):
|
|
|
|
+ path = Path(homePath).parent
|
|
|
|
+ while path not in writePatternsParents:
|
|
|
|
+ writePatternsParents.append(path)
|
|
|
|
+ path = path.parent
|
|
|
|
+
|
|
|
|
+def hasMode(path, mode):
|
|
|
|
+ try:
|
|
|
|
+ return ( path.stat().st_mode & mode ) == mode
|
|
|
|
+ except PermissionError:
|
|
|
|
+ return ( 0o0000 & mode ) == mode
|
|
|
|
+
|
|
|
|
+def isWorldReadable(path):
|
|
|
|
+ if hasMode(path, 0o0004):
|
|
|
|
+ anchor = Path(path.anchor)
|
|
|
|
+ path = path.parent
|
|
|
|
+ while path != anchor:
|
|
|
|
+ if hasMode(path, 0o0001):
|
|
|
|
+ path = path.parent
|
|
|
|
+ else:
|
|
|
|
+ return False
|
|
|
|
+ return True
|
|
|
|
+ else:
|
|
|
|
+ return False
|
|
|
|
+
|
|
|
|
+def isWorldWritable(path):
|
|
|
|
+ anchor = Path(path.anchor)
|
|
|
|
+ # If the path do not exist, find a parent that does
|
|
|
|
+ while path != anchor and not path.exists():
|
|
|
|
+ path = path.parent
|
|
|
|
+ if hasMode(path, 0o0002):
|
|
|
|
+ path = path.parent
|
|
|
|
+ while path != anchor:
|
|
|
|
+ if hasMode(path, 0o0001):
|
|
|
|
+ path = path.parent
|
|
|
|
+ else:
|
|
|
|
+ return False
|
|
|
|
+ return True
|
|
|
|
+ else:
|
|
|
|
+ return False
|
|
|
|
+
|
|
|
|
+exceptions = ''
|
|
|
|
+
|
|
|
|
+def logExceptions(message, pathList):
|
|
|
|
+ global exceptions
|
|
|
|
+ if len(pathList) > 0:
|
|
|
|
+ exceptions += "%s: \n" % message
|
|
|
|
+ for path in (pathList):
|
|
|
|
+ exceptions += " * %s\n" % path.as_posix()
|
|
|
|
+ exceptions += "\n"
|
|
|
|
+
|
|
|
|
+def printExceptions():
|
|
|
|
+ global exceptions
|
|
|
|
+ print(exceptions, end='')
|
|
|
|
+
|
|
|
|
+readExceptions = []
|
|
|
|
+for pattern in readPatterns:
|
|
|
|
+ for strPath in patternWalk(pattern):
|
|
|
|
+ path = Path(strPath)
|
|
|
|
+ if isWorldReadable(path):
|
|
|
|
+ readExceptions.append(path)
|
|
|
|
+
|
|
|
|
+logExceptions('These paths are world readable', readExceptions)
|
|
|
|
+
|
|
|
|
+writeExceptions = []
|
|
|
|
+for pattern in writePatterns:
|
|
|
|
+ for strPath in patternWalk(pattern):
|
|
|
|
+ path = Path(strPath)
|
|
|
|
+ if isWorldWritable(path):
|
|
|
|
+ writeExceptions.append(path)
|
|
|
|
+
|
|
|
|
+logExceptions('These paths are world-writable', writeExceptions)
|
|
|
|
+
|
|
|
|
+parentWriteExceptions = []
|
|
|
|
+for pattern in writePatternsParents:
|
|
|
|
+ for strPath in patternWalk(str(pattern)):
|
|
|
|
+ path = Path(strPath)
|
|
|
|
+ if isWorldWritable(path):
|
|
|
|
+ parentWriteExceptions.append(path)
|
|
|
|
+
|
|
|
|
+logExceptions('These paths are world-writable', parentWriteExceptions)
|
|
|
|
+
|
|
|
|
+execpathWriteExceptions = []
|
|
|
|
+for strPath in os.get_exec_path():
|
|
|
|
+ path = Path(strPath)
|
|
|
|
+ if isWorldWritable(path):
|
|
|
|
+ execpathWriteExceptions.append(path)
|
|
|
|
+
|
|
|
|
+logExceptions('These executable search paths are world-writable', execpathWriteExceptions)
|
|
|
|
+
|
|
|
|
+pythonpathWriteExceptions = []
|
|
|
|
+for strPath in sys.path:
|
|
|
|
+ path = Path(strPath)
|
|
|
|
+ if isWorldWritable(path):
|
|
|
|
+ pythonpathWriteExceptions.append(path)
|
|
|
|
+
|
|
|
|
+logExceptions('These python search paths are world-writable', pythonpathWriteExceptions)
|
|
|
|
+
|
|
|
|
+perlpathWriteExceptions = []
|
|
|
|
+for strPath in get_perl_inc():
|
|
|
|
+ path = Path(strPath)
|
|
|
|
+ if isWorldWritable(path):
|
|
|
|
+ perlpathWriteExceptions.append(path)
|
|
|
|
+
|
|
|
|
+logExceptions('These perl include paths are world-writable', perlpathWriteExceptions)
|
|
|
|
+
|
|
|
|
+printExceptions()
|