Created
November 23, 2021 14:27
-
-
Save wdormann/5709cb7eb9e5db06c728551dd7d72876 to your computer and use it in GitHub Desktop.
Python script to check for explicitly privileged services that are controllable by non-admin users
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import subprocess | |
import ctypes | |
# See: https://blogs.msmvps.com/erikr/2007/09/26/set-permissions-on-a-specific-service-windows/ | |
svcinfo = {} | |
#nonadmin = ['AU', 'AN', 'BG', 'BU', 'DG', 'WD', 'IU', 'LG'] | |
nonadmin = ['AU', 'AN', 'BG', 'BU', 'DG', 'IU', 'LG'] | |
FNULL = open(os.devnull, 'w') | |
def checkadmin(): | |
return ctypes.windll.shell32.IsUserAnAdmin() | |
def sliceperms(permstring): | |
permparts = [] | |
if permstring: | |
parts = int(len(permstring)) | |
for i in range(0, parts, 2): | |
permparts.append(permstring[i:i + 2]) | |
return permparts | |
def getsvcacls(svcname): | |
svcacl = None | |
try: | |
svcacl = subprocess.check_output(['sc', 'sdshow', svcname], stderr=FNULL).decode('utf-8', | |
'backslashreplace').strip() | |
except subprocess.CalledProcessError: | |
pass | |
return svcacl | |
def checkuserstartable(svcacl): | |
userstartable = False | |
if svcacl: | |
#print(svcacl) | |
acllist = svcacl.split(')') | |
for acl in acllist: | |
if acl: | |
if acl.startswith('D:'): | |
acl = acl.replace('D:', '') | |
acl = acl.replace('(', '') | |
aclparts = acl.split(';') | |
usercode = aclparts[-1] | |
if usercode in nonadmin and aclparts[0] == 'A': | |
perms = aclparts[2] | |
permparts = sliceperms(perms) | |
if 'RP' in permparts: | |
#print('**** This can be started by a non-admin! ****') | |
userstartable = True | |
return userstartable | |
def checkuserstoppable(svcacl): | |
userstoppable = False | |
if svcacl: | |
acllist = svcacl.split(')') | |
for acl in acllist: | |
if acl: | |
if acl.startswith('D:'): | |
acl = acl.replace('D:', '') | |
acl = acl.replace('(', '') | |
aclparts = acl.split(';') | |
usercode = aclparts[-1] | |
if usercode in nonadmin: | |
perms = aclparts[2] | |
permparts = sliceperms(perms) | |
if 'WP' in permparts: | |
#print('**** This can be stopped by a non-admin! ****') | |
userstoppable = True | |
return userstoppable | |
def whichfile(file): | |
if file: | |
fullpath = '' | |
try: | |
fullpath = subprocess.check_output(['where', file, ], stderr=FNULL).decode('utf-8', | |
'backslashreplace').strip() | |
except subprocess.CalledProcessError: | |
pass | |
return fullpath | |
def svchosttarget(svcname): | |
#print('Trying to find real target of svchost...') | |
parsed = '' | |
svctarget = '' | |
try: | |
parsed = subprocess.check_output(['reg', 'query', | |
r'HKLM\SYSTEM\CurrentControlSet\Services\%s\Parameters' % | |
svcname], stderr=FNULL).decode('utf-8', 'backslashreplace') | |
except subprocess.CalledProcessError: | |
pass | |
for line in parsed.splitlines(): | |
if ' servicedll ' in line.lower(): | |
lineparts = line.split(' REG_EXPAND_SZ ') | |
svctarget = os.path.expandvars((lineparts[-1])) | |
if not os.path.exists(svctarget): | |
svctarget = whichfile(svctarget) | |
return svctarget | |
def checkservices(): | |
nameindex = None | |
dispnameindex = None | |
privindex = None | |
stateindex = None | |
pathindex = None | |
startmodeindex = None | |
parsed = subprocess.check_output(['wmic', 'service', 'get', 'name,startname,displayname,state,pathname,startmode,' | |
'DesktopInteract', | |
'/format:csv'], stderr=FNULL).decode( | |
'utf-8', 'backslashreplace') | |
for line in parsed.splitlines(): | |
if line: | |
columns = line.split(',') | |
if line.startswith('Node,'): | |
nameindex = columns.index('Name') | |
dispnameindex = columns.index('DisplayName') | |
privindex = columns.index('StartName') | |
stateindex = columns.index('State') | |
pathindex = columns.index('PathName') | |
startmodeindex = columns.index('StartMode') | |
desktopindex = columns.index('DesktopInteract') | |
else: | |
reasons = [] | |
svcname = columns[nameindex] | |
svcdict = {} | |
svcdict['priv'] = columns[privindex] | |
svcdict['dispname'] = columns[dispnameindex] | |
svcdict['state'] = columns[stateindex] | |
svcdict['path'] = columns[pathindex] | |
svcdict['startmode'] = columns[startmodeindex] | |
svcdict['desktop'] = columns[desktopindex] | |
if svcdict['desktop'].lower() == 'true': | |
svcdict['desktop'] = True | |
else: | |
svcdict['desktop'] = False | |
if 'svchost.exe' in svcdict['path']: | |
realtarget = svchosttarget(svcname) | |
if realtarget: | |
svcdict['path'] = realtarget | |
elif '"' in svcdict['path']: | |
# Quoted path | |
splitpath = svcdict['path'].split('"') | |
if len(splitpath) > 2 and splitpath[2]: | |
# Quoted path with arguments | |
svcdict['path'] = '"%s"' % splitpath[1] | |
else: | |
# Quoted path without arguments | |
pass | |
else: | |
# Non-quoted path | |
splitpath = svcdict['path'].split() | |
if len(splitpath) > 1: | |
# Non-quoted path with arguments | |
svcdict['path'] = splitpath[0] | |
svcacls = getsvcacls(svcname) | |
startable = checkuserstartable(svcacls) | |
stoppable = checkuserstoppable(svcacls) | |
svcdict['startable'] = False | |
if startable and svcdict['startmode'] != 'Disabled': | |
if svcdict['state'] == 'Stopped' or (svcdict['state'] == 'Running' and stoppable): | |
svcdict['startable'] = True | |
svcinfo[svcname] = svcdict | |
if svcdict['startable'] and svcdict['priv'].lower() == 'localsystem': | |
reasons.append('User-controlled and high-privileged') | |
if svcdict['desktop'] and svcdict['priv'].lower() == 'localsystem' and (svcdict['state'].lower() == \ | |
'running' or svcdict['startable']): | |
reasons.append('Desktop interactive') | |
reasons = ', '.join(reasons) | |
if reasons: | |
print('%s -=-= %s MAY be vulnerable (%s) =-=-' % (os.linesep, svcname, reasons)) | |
print('Service: %s' % svcname) | |
print('Display name: %s' % svcinfo[svcname]['dispname']) | |
print('Privilege: %s' % svcinfo[svcname]['priv']) | |
print('Path: %s' % svcinfo[svcname]['path']) | |
print('State: %s' % svcinfo[svcname]['state']) | |
print('User-startable: %s' % svcinfo[svcname]['startable']) | |
print('Can interact with desktop: %s' % svcinfo[svcname]['desktop']) | |
if __name__ == "__main__": | |
if checkadmin(): | |
checkservices() | |
else: | |
print('Please run this script with administrative privileges.') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment