2023-04-27 13:54:51 +00:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
2023-05-05 09:35:12 +00:00
import json
2023-07-25 11:15:09 +00:00
import logging
2023-05-05 08:56:24 +00:00
import operator
import time
2023-09-22 19:22:54 +00:00
from impacket . system_errors import ERROR_NO_MORE_ITEMS , ERROR_FILE_NOT_FOUND , ERROR_OBJECT_NOT_FOUND
2023-07-25 11:15:09 +00:00
from termcolor import colored
2023-05-05 08:56:24 +00:00
2023-09-14 21:07:15 +00:00
from nxc . logger import nxc_logger
2023-05-02 18:35:56 +00:00
from impacket . dcerpc . v5 import rrp , samr , scmr
2023-04-27 13:54:51 +00:00
from impacket . dcerpc . v5 . rrp import DCERPCSessionError
2023-04-28 17:29:40 +00:00
from impacket . smbconnection import SessionError as SMBSessionError
2023-04-27 13:54:51 +00:00
from impacket . examples . secretsdump import RemoteOperations
2023-04-28 14:45:58 +00:00
2023-06-02 15:17:50 +00:00
# Configuration variables
2023-04-28 14:45:58 +00:00
OUTDATED_THRESHOLD = 30
2023-09-22 19:22:54 +00:00
DEFAULT_OUTPUT_FILE = " ./wcc_results.json "
DEFAULT_OUTPUT_FORMAT = " json "
VALID_OUTPUT_FORMATS = [ " json " , " csv " ]
2023-04-27 13:54:51 +00:00
2023-06-02 15:17:50 +00:00
# Registry value types
REG_VALUE_TYPE_UNDEFINED = 0
REG_VALUE_TYPE_UNICODE_STRING = 1
REG_VALUE_TYPE_UNICODE_STRING_WITH_ENV = 2
REG_VALUE_TYPE_BINARY = 3
REG_VALUE_TYPE_32BIT_LE = 4
REG_VALUE_TYPE_32BIT_BE = 5
REG_VALUE_TYPE_UNICODE_STRING_SEQUENCE = 7
REG_VALUE_TYPE_64BIT_LE = 11
2023-06-02 15:15:55 +00:00
# Setup file logger
2023-09-22 19:22:54 +00:00
if " wcc_logger " not in globals ( ) :
wcc_logger = logging . getLogger ( " WCC " )
2023-08-16 09:14:46 +00:00
wcc_logger . propagate = False
2023-09-14 21:07:15 +00:00
log_filename = nxc_logger . init_log_file ( )
2023-09-22 19:22:54 +00:00
log_filename = log_filename . replace ( " log_ " , " wcc_ " )
2023-08-16 09:14:46 +00:00
wcc_logger . setLevel ( logging . INFO )
wcc_file_handler = logging . FileHandler ( log_filename )
2023-09-22 19:22:54 +00:00
wcc_file_handler . setFormatter ( logging . Formatter ( " %(asctime)s [ %(levelname)s ] %(message)s " ) )
2023-08-16 09:14:46 +00:00
wcc_logger . addHandler ( wcc_file_handler )
2023-09-22 19:22:54 +00:00
2023-06-09 09:46:01 +00:00
class ConfigCheck :
2023-08-16 09:14:46 +00:00
"""
Class for performing the checks and holding the results
"""
module = None
def __init__ ( self , name , description = " " , checkers = [ None ] , checker_args = [ [ ] ] , checker_kwargs = [ { } ] ) :
self . check_id = None
self . name = name
self . description = description
assert len ( checkers ) == len ( checker_args ) and len ( checkers ) == len ( checker_kwargs )
self . checkers = checkers
self . checker_args = checker_args
self . checker_kwargs = checker_kwargs
self . ok = True
self . reasons = [ ]
def run ( self ) :
for checker , args , kwargs in zip ( self . checkers , self . checker_args , self . checker_kwargs ) :
if checker is None :
checker = HostChecker . check_registry
ok , reasons = checker ( * args , * * kwargs )
self . ok = self . ok and ok
self . reasons . extend ( reasons )
def log ( self , context ) :
2023-09-22 19:22:54 +00:00
result = " passed " if self . ok else " did not pass "
reasons = " , " . join ( self . reasons )
2023-09-23 01:10:21 +00:00
wcc_logger . info ( f ' { self . connection . host } : Check " { self . name } " { result } because: { reasons } ' )
2023-08-16 09:14:46 +00:00
if self . module . quiet :
return
2023-09-22 19:22:54 +00:00
status = colored ( " OK " , " green " , attrs = [ " bold " ] ) if self . ok else colored ( " KO " , " red " , attrs = [ " bold " ] )
reasons = " : " + " , " . join ( self . reasons )
2023-09-23 01:10:21 +00:00
msg = f " { status } { self . name } "
info_msg = f " { status } { self . name } { reasons } "
2023-08-16 09:14:46 +00:00
context . log . highlight ( msg )
2023-08-17 17:45:20 +00:00
context . log . info ( info_msg )
2023-06-09 09:46:01 +00:00
2023-09-22 19:22:54 +00:00
2023-09-17 20:20:40 +00:00
class NXCModule :
2023-09-22 19:22:54 +00:00
"""
2023-08-16 09:14:46 +00:00
Windows Configuration Checker
Module author : @__fpr ( Orange Cyberdefense )
2023-09-22 19:22:54 +00:00
"""
2023-09-23 01:10:21 +00:00
2023-09-22 19:22:54 +00:00
name = " wcc "
description = " Check various security configuration items on Windows machines "
supported_protocols = [ " smb " ]
opsec_safe = True
2023-08-16 09:14:46 +00:00
multiple_hosts = True
def options ( self , context , module_options ) :
2023-09-22 19:22:54 +00:00
"""
2023-08-16 09:14:46 +00:00
OUTPUT_FORMAT Format for report ( Default : ' json ' )
OUTPUT Path for report
QUIET Do not print results to stdout ( Default : False )
2023-09-22 19:22:54 +00:00
"""
self . output = module_options . get ( " OUTPUT " )
self . output_format = module_options . get ( " OUTPUT_FORMAT " , DEFAULT_OUTPUT_FORMAT )
2023-08-16 09:14:46 +00:00
if self . output_format not in VALID_OUTPUT_FORMATS :
self . output_format = DEFAULT_OUTPUT_FORMAT
2023-09-22 19:22:54 +00:00
self . quiet = module_options . get ( " QUIET " , " false " ) . lower ( ) in ( " true " , " 1 " )
2023-08-16 09:14:46 +00:00
self . results = { }
ConfigCheck . module = self
HostChecker . module = self
def on_admin_login ( self , context , connection ) :
2023-09-22 19:22:54 +00:00
self . results . setdefault ( connection . host , { " checks " : [ ] } )
2023-08-16 09:14:46 +00:00
self . context = context
HostChecker ( context , connection ) . run ( )
def on_shutdown ( self , context , connection ) :
if self . output is not None :
self . export_results ( )
def add_result ( self , host , result ) :
2023-09-23 01:10:21 +00:00
self . results [ host ] [ " checks " ] . append ( { " Check " : result . name , " Description " : result . description , " Status " : " OK " if result . ok else " KO " , " Reasons " : result . reasons } )
2023-08-16 09:14:46 +00:00
def export_results ( self ) :
2023-09-22 19:22:54 +00:00
with open ( self . output , " w " ) as output :
if self . output_format == " json " :
2023-08-16 09:14:46 +00:00
json . dump ( self . results , output )
2023-09-22 19:22:54 +00:00
elif self . output_format == " csv " :
output . write ( " Host,Check,Description,Status,Reasons " )
2023-08-16 09:14:46 +00:00
for host in self . results :
2023-09-23 01:10:21 +00:00
for result in self . results [ host ] [ " checks " ] :
output . write ( f " \n { host } " )
for field in ( result [ " Check " ] , result [ " Description " ] , result [ " Status " ] , " ; " . join ( result [ " Reasons " ] ) . replace ( " \x00 " , " " ) ) :
2023-09-22 19:22:54 +00:00
if " , " in field :
2023-08-16 09:14:46 +00:00
field = field . replace ( ' " ' , ' " " ' )
field = f ' " { field } " '
2023-09-22 19:22:54 +00:00
output . write ( f " , { field } " )
self . context . log . success ( f " Results written to { self . output } " )
2023-04-27 13:54:51 +00:00
2023-06-21 15:46:44 +00:00
class HostChecker :
2023-08-16 09:14:46 +00:00
module = None
def __init__ ( self , context , connection ) :
self . context = context
self . connection = connection
remoteOps = RemoteOperations ( smbConnection = connection . conn , doKerberos = False )
remoteOps . enableRegistry ( )
self . dce = remoteOps . _RemoteOperations__rrp
def run ( self ) :
# Prepare checks
self . init_checks ( )
# Perform checks
self . check_config ( )
# Check methods #
#################
def init_checks ( self ) :
# Declare the checks to do and how to do them
2023-09-23 01:10:21 +00:00
self . checks = [ ConfigCheck ( " Last successful update " , " Checks how old is the last successful update " , checkers = [ self . check_last_successful_update ] ) , ConfigCheck ( " LAPS " , " Checks if LAPS is installed " , checkers = [ self . check_laps ] ) , ConfigCheck ( " Administrator ' s name " , " Checks if Administror user name has been changed " , checkers = [ self . check_administrator_name ] ) , ConfigCheck ( " UAC configuration " , " Checks if UAC configuration is secure " , checker_args = [ [ self , ( " HKLM \\ Software \\ Microsoft \\ Windows \\ CurrentVersion \\ Policies \\ System " , " EnableLUA " , 1 ) , ( " HKLM \\ Software \\ Microsoft \\ Windows \\ CurrentVersion \\ Policies \\ System " , " LocalAccountTokenFilterPolicy " , 0 ) ] ] ) , ConfigCheck ( " Hash storage format " , " Checks if storing hashes in LM format is disabled " , checker_args = [ [ self , ( " HKLM \\ System \\ CurrentControlSet \\ Control \\ Lsa " , " NoLMHash " , 1 ) ] ] ) , ConfigCheck ( " Always install elevated " , " Checks if AlwaysInstallElevated is disabled " , checker_args = [ [ self , ( " HKCU \\ SOFTWARE \\ Policies \\ Microsoft \\ Windows \\ Installer " , " AlwaysInstallElevated " , 0 ) ] ] ) , ConfigCheck ( " IPv6 preference " , " Checks if IPv6 is preferred over IPv4 " , checker_args = [ [ self , ( " HKLM \\ SYSTEM \\ CurrentControlSet \\ Services \\ Tcpip6 \\ Parameters " , " DisabledComponents " , ( 32 , 255 ) , in_ ) ] ] ) , ConfigCheck ( " Spooler service " , " Checks if the spooler service is disabled " , checkers = [ self . check_spooler_service ] ) , ConfigCheck ( " WDigest authentication " , " Checks if WDigest authentication is disabled " , checker_args = [ [ self , ( " HKLM \\ SYSTEM \\ CurrentControlSet \\ Control \\ SecurityProviders \\ WDigest " , " UseLogonCredential " , 0 ) ] ] ) , ConfigCheck ( " WSUS configuration " , " Checks if WSUS configuration uses HTTPS " , checkers = [ self . check_wsus_running , None ] , checker_args = [ [ ] , [ self , ( " HKLM \\ Software \\ Policies \\ Microsoft \\ Windows \\ WindowsUpdate " , " WUServer " , " https:// " , startswith ) , ( " HKLM \\ Software \\ Policies \\ Microsoft \\ Windows \\ WindowsUpdate " , " UseWUServer " , 0 , operator . eq ) ] ] , checker_kwargs = [ { } , { " options " : { " lastWins " : True } } ] ) , ConfigCheck ( " LSA cache " , " Checks how many logons are kept in the LSA cache " , checker_args = [ [ self , ( " HKLM \\ SOFTWARE \\ Microsoft \\ Windows NT \\ CurrentVersion \\ Winlogon " , " CachedLogonsCount " , 2 , le ) ] ] ) , ConfigCheck ( " AppLocker " , " Checks if there are AppLocker rules defined " , checkers = [ self . check_applocker ] ) , ConfigCheck ( " RDP expiration time " , " Checks RDP session timeout " , checker_args = [ [ self , ( " HKLM \\ SOFTWARE \\ Policies \\ Microsoft \\ Windows NT \\ Terminal Services " , " MaxDisconnectionTime " , 0 , operator . gt ) , ( " HKCU \\ SOFTWARE \\ Policies \\ Microsoft \\ Windows NT \\ Terminal Services " , " MaxDisconnectionTime " , 0 , operator . gt ) ] ] ) , ConfigCheck ( " CredentialGuard " , " Checks if CredentialGuard is enabled " , checker_args = [ [ self , ( " HKLM \\ SYSTEM \\ CurrentControlSet \\ Control \\ DeviceGuard " , " EnableVirtualizationBasedSecurity " , 1 ) , ( " HKLM \\ SYSTEM \\ CurrentControlSet \\ Control \\ Lsa " , " LsaCfgFlags " , 1 ) ] ] ) , ConfigCheck ( " PPL " , " Checks if lsass runs as a protected process " , checker_args = [ [ self , ( " HKLM \\ SYSTEM \\ CurrentControlSet \\ Control \\ Lsa " , " RunAsPPL " , 1 ) ] ] ) , ConfigCheck ( " Powershell v2 availability " , " Checks if powershell v2 is available " , checker_args = [ [ self , ( " HKLM \\ SOFTWARE \\ Microsoft \\ PowerShell \\ 3 \\ PowerShellEngine " , " PSCompatibleVersion " , " 2.0 " , not_ ( operator . contains ) ) ] ] ) , ConfigCheck ( " LmCompatibilityLevel " , " Checks if LmCompatibilityLevel is set to 5 " , checker_args = [ [ self , ( " HKLM \\ SYSTEM \\ CurrentControlSet \\ Control \\ Lsa " , " LmCompatibilityLevel " , 5 , operator . ge ) ] ] ) , ConfigCheck ( " NBTNS " , " Checks if NBTNS is disabled on all interfaces " , checkers = [ self . check_nbtns ] ) , ConfigCheck ( " mDNS " , " Checks if mDNS is disabled " , checker_args = [ [ self , ( " HKLM \\ SYSTEM \\ CurrentControlSet \\ Services \\ DNScache \\ Parameters " , " EnableMDNS " , 0 ) ] ] ) , ConfigCheck ( " SMB signing " , " Checks if SMB signing is enabled " , checker_args = [ [ self , ( " HKLM \\ System \\ CurrentControlSet \\ Services \\ LanmanServer \\ Parameters " , " requiresecuritysignature " , 1 ) ] ] ) , ConfigCheck ( " LDAP signing " , " Checks if LDAP signing is enabled " , checker_args = [ [ self , ( " HKLM \\ SYSTEM \\ CurrentControlSet \\ Services \\ NTDS \\ Parameters " , " LDAPServerIntegrity " , 2 ) , ( " HKLM \\ SYSTEM \\ CurrentControlSet \\ Service
2023-08-16 09:14:46 +00:00
# Add check to conf_checks table if missing
db_checks = self . connection . db . get_checks ( )
2023-09-22 19:22:54 +00:00
[ check . _asdict ( ) [ " name " ] . strip ( ) . lower ( ) for check in db_checks ]
2023-08-16 09:14:46 +00:00
added = [ ]
2023-09-22 19:22:54 +00:00
for i , check in enumerate ( self . checks ) :
2023-08-16 09:14:46 +00:00
check . connection = self . connection
missing = True
for db_check in db_checks :
db_check = db_check . _asdict ( )
2023-09-22 19:22:54 +00:00
if check . name . strip ( ) . lower ( ) == db_check [ " name " ] . strip ( ) . lower ( ) :
2023-08-16 09:14:46 +00:00
missing = False
2023-09-22 19:22:54 +00:00
self . checks [ i ] . check_id = db_check [ " id " ]
2023-08-16 09:14:46 +00:00
break
if missing :
self . connection . db . add_check ( check . name , check . description )
added . append ( check )
# Update check_id for checks added to the db
db_checks = self . connection . db . get_checks ( )
2023-09-22 19:22:54 +00:00
for i , check in enumerate ( added ) :
2023-08-16 09:14:46 +00:00
check_id = None
for db_check in db_checks :
db_check = db_check . _asdict ( )
2023-09-23 01:10:21 +00:00
if db_check [ " name " ] . strip ( ) . lower ( ) == check . name . strip ( ) . lower ( ) :
check_id = db_check [ " id " ]
2023-08-16 09:14:46 +00:00
break
added [ i ] . check_id = check_id
def check_config ( self ) :
# Get host ID from db
host_id = None
hosts = self . connection . db . get_hosts ( self . connection . host )
for host in hosts :
host = host . _asdict ( )
2023-09-22 19:22:54 +00:00
if host [ " ip " ] == self . connection . host and host [ " hostname " ] == self . connection . hostname and host [ " domain " ] == self . connection . domain :
host_id = host [ " id " ]
2023-08-16 09:14:46 +00:00
break
# Perform all the checks and store the results
for check in self . checks :
try :
check . run ( )
except Exception as e :
2023-09-22 19:22:54 +00:00
self . context . log . error ( f " HostChecker.check_config(): Error while performing check { check . name } : { e } " )
2023-08-16 09:14:46 +00:00
check . log ( self . context )
self . module . add_result ( self . connection . host , check )
if host_id is not None :
2023-09-23 01:10:21 +00:00
self . connection . db . add_check_result ( host_id , check . check_id , check . ok , " , " . join ( check . reasons ) . replace ( " \x00 " , " " ) )
2023-08-16 09:14:46 +00:00
def check_registry ( self , * specs , options = { } ) :
"""
Perform checks that only require to compare values in the registry with expected values , according to the specs
a spec may be either a 3 - tuple : ( key name , value name , expected value ) , or a 4 - tuple ( key name , value name , expected value , operation ) , where operation is a function that implements a comparison operator
"""
2023-09-23 01:10:21 +00:00
default_options = { " lastWins " : False , " stopOnOK " : False , " stopOnKO " : False , " KOIfMissing " : True }
2023-08-16 09:14:46 +00:00
default_options . update ( options )
options = default_options
op = operator . eq
ok = True
reasons = [ ]
for spec in specs :
try :
if len ( spec ) == 3 :
( key , value_name , expected_value ) = spec
elif len ( spec ) == 4 :
( key , value_name , expected_value , op ) = spec
else :
ok = False
2023-09-22 19:22:54 +00:00
reasons = [ " Check could not be performed (invalid specification provided) " ]
2023-08-16 09:14:46 +00:00
return ok , reasons
except Exception as e :
2023-09-23 01:10:21 +00:00
self . module . log . error ( f " Check could not be performed. Details: specs= { specs } , dce= { self . dce } , error: { e } " )
2023-08-16 09:14:46 +00:00
return ok , reasons
if op == operator . eq :
2023-09-22 19:22:54 +00:00
opstring = " {left} == {right} "
nopstring = " {left} != {right} "
2023-08-16 09:14:46 +00:00
elif op == operator . contains :
2023-09-22 19:22:54 +00:00
opstring = " {left} in {right} "
nopstring = " {left} not in {right} "
2023-08-16 09:14:46 +00:00
elif op == operator . gt :
2023-09-22 19:22:54 +00:00
opstring = " {left} > {right} "
nopstring = " {left} <= {right} "
2023-08-16 09:14:46 +00:00
elif op == operator . ge :
2023-09-22 19:22:54 +00:00
opstring = " {left} >= {right} "
nopstring = " {left} < {right} "
2023-08-16 09:14:46 +00:00
elif op == operator . lt :
2023-09-22 19:22:54 +00:00
opstring = " {left} < {right} "
nopstring = " {left} >= {right} "
2023-08-16 09:14:46 +00:00
elif op == operator . le :
2023-09-22 19:22:54 +00:00
opstring = " {left} <= {right} "
nopstring = " {left} > {right} "
2023-08-16 09:14:46 +00:00
elif op == operator . ne :
2023-09-22 19:22:54 +00:00
opstring = " {left} != {right} "
nopstring = " {left} == {right} "
2023-08-16 09:14:46 +00:00
else :
2023-09-22 19:22:54 +00:00
opstring = f " { op . __name__ } ( {{ left }} , {{ right }} ) == True "
nopstring = f " { op . __name__ } ( {{ left }} , {{ right }} ) == True "
2023-08-16 09:14:46 +00:00
value = self . reg_query_value ( self . dce , self . connection , key , value_name )
if type ( value ) == DCERPCSessionError :
2023-09-22 19:22:54 +00:00
if options [ " KOIfMissing " ] :
2023-08-16 09:14:46 +00:00
ok = False
if value . error_code in ( ERROR_NO_MORE_ITEMS , ERROR_FILE_NOT_FOUND ) :
2023-09-22 19:22:54 +00:00
reasons . append ( f " { key } : Key not found " )
2023-08-16 09:14:46 +00:00
elif value . error_code == ERROR_OBJECT_NOT_FOUND :
2023-09-22 19:22:54 +00:00
reasons . append ( f " { value_name } : Value not found " )
2023-08-16 09:14:46 +00:00
else :
ok = False
2023-09-22 19:22:54 +00:00
reasons . append ( f " Error while retrieving value of { key } \\ { value_name } : { value } " )
2023-08-16 09:14:46 +00:00
continue
if op ( value , expected_value ) :
2023-09-22 19:22:54 +00:00
if options [ " lastWins " ] :
2023-08-16 09:14:46 +00:00
ok = True
2023-09-22 19:22:54 +00:00
reasons . append ( opstring . format ( left = f " { key } \\ { value_name } ( { value } ) " , right = expected_value ) )
2023-08-16 09:14:46 +00:00
else :
2023-09-22 19:22:54 +00:00
reasons . append ( nopstring . format ( left = f " { key } \\ { value_name } ( { value } ) " , right = expected_value ) )
2023-08-16 09:14:46 +00:00
ok = False
2023-09-22 19:22:54 +00:00
if ok and options [ " stopOnOK " ] :
2023-08-16 09:14:46 +00:00
break
2023-09-22 19:22:54 +00:00
if not ok and options [ " stopOnKO " ] :
2023-08-16 09:14:46 +00:00
break
return ok , reasons
def check_laps ( self ) :
reasons = [ ]
success = False
2023-09-22 19:22:54 +00:00
lapsv2_ad_key_name = " Software \\ Microsoft \\ Windows \\ CurrentVersion \\ Policies \\ LAPS "
lapsv2_aad_key_name = " Software \\ Microsoft \\ Policies \\ LAPS "
2023-08-16 09:14:46 +00:00
# Checking LAPSv2
2023-09-22 19:22:54 +00:00
ans = self . _open_root_key ( self . dce , self . connection , " HKLM " )
2023-08-16 09:14:46 +00:00
if ans is None :
2023-09-22 19:22:54 +00:00
return False , [ " Could not query remote registry " ]
2023-08-16 09:14:46 +00:00
2023-09-22 19:22:54 +00:00
root_key_handle = ans [ " phKey " ]
2023-08-16 09:14:46 +00:00
try :
ans = rrp . hBaseRegOpenKey ( self . dce , root_key_handle , lapsv2_ad_key_name )
reasons . append ( f " HKLM \\ { lapsv2_ad_key_name } found, LAPSv2 AD installed " )
success = True
return success , reasons
except DCERPCSessionError as e :
if e . error_code != ERROR_FILE_NOT_FOUND :
reasons . append ( f " HKLM \\ { lapsv2_ad_key_name } not found " )
try :
ans = rrp . hBaseRegOpenKey ( self . dce , root_key_handle , lapsv2_aad_key_name )
reasons . append ( f " HKLM \\ { lapsv2_aad_key_name } found, LAPSv2 AAD installed " )
success = True
return success , reasons
except DCERPCSessionError as e :
if e . error_code != ERROR_FILE_NOT_FOUND :
reasons . append ( f " HKLM \\ { lapsv2_aad_key_name } not found " )
# LAPSv2 does not seems to be installed, checking LAPSv1
2023-09-22 19:22:54 +00:00
lapsv1_key_name = " HKLM \\ Software \\ Microsoft \\ Windows NT \\ CurrentVersion \\ Winlogon \\ GPextensions "
subkeys = self . reg_get_subkeys ( self . dce , self . connection , lapsv1_key_name )
laps_path = " \\ Program Files \\ LAPS \\ CSE "
2023-08-16 09:14:46 +00:00
for subkey in subkeys :
2023-09-23 01:10:21 +00:00
value = self . reg_query_value ( self . dce , self . connection , lapsv1_key_name + " \\ " + subkey , " DllName " )
2023-09-22 19:22:54 +00:00
if type ( value ) == str and " laps \\ cse \\ admpwd.dll " in value . lower ( ) :
reasons . append ( f " { lapsv1_key_name } \\ ... \\ DllName matches AdmPwd.dll " )
2023-08-16 09:14:46 +00:00
success = True
2023-09-23 01:10:21 +00:00
laps_path = " \\ " . join ( value . split ( " \\ " ) [ 1 : - 1 ] )
2023-08-16 09:14:46 +00:00
break
if not success :
2023-09-22 19:22:54 +00:00
reasons . append ( f " No match found in { lapsv1_key_name } \\ ... \\ DllName " )
2023-08-16 09:14:46 +00:00
2023-09-22 19:24:22 +00:00
file_listing = self . ls ( self . connection , laps_path )
if file_listing :
2023-09-22 19:22:54 +00:00
reasons . append ( " Found LAPS folder at " + laps_path )
2023-08-16 09:14:46 +00:00
else :
success = False
2023-09-22 19:22:54 +00:00
reasons . append ( " LAPS folder does not exist " )
2023-08-16 09:14:46 +00:00
return success , reasons
2023-09-22 19:24:22 +00:00
file_listing = self . ls ( self . connection , laps_path + " \\ AdmPwd.dll " )
if file_listing :
2023-09-22 19:22:54 +00:00
reasons . append ( f " Found { laps_path } \\ AdmPwd.dll " )
2023-08-16 09:14:46 +00:00
else :
success = False
2023-09-23 01:10:21 +00:00
reasons . append ( f " { laps_path } \\ AdmPwd.dll not found " )
2023-08-16 09:14:46 +00:00
return success , reasons
def check_last_successful_update ( self ) :
2023-09-23 01:10:21 +00:00
records = self . connection . wmi ( wmi_query = " Select TimeGenerated FROM Win32_ReliabilityRecords Where EventIdentifier=19 " , namespace = " root \\ cimv2 " )
2023-08-23 04:23:28 +00:00
if isinstance ( records , bool ) or len ( records ) == 0 :
2023-09-22 19:22:54 +00:00
return False , [ " No update found " ]
most_recent_update_date = records [ 0 ] [ " TimeGenerated " ] [ " value " ]
2023-09-23 01:10:21 +00:00
most_recent_update_date = most_recent_update_date . split ( " . " ) [ 0 ]
2023-09-22 19:22:54 +00:00
most_recent_update_date = time . strptime ( most_recent_update_date , " % Y % m %d % H % M % S " )
2023-08-16 09:14:46 +00:00
most_recent_update_date = time . mktime ( most_recent_update_date )
now = time . time ( )
2023-09-22 19:22:54 +00:00
days_since_last_update = ( now - most_recent_update_date ) / / 86400
2023-08-16 09:14:46 +00:00
if days_since_last_update < = OUTDATED_THRESHOLD :
2023-09-22 19:22:54 +00:00
return True , [ f " Last update was { days_since_last_update } <= { OUTDATED_THRESHOLD } days ago " ]
2023-08-16 09:14:46 +00:00
else :
2023-09-22 19:22:54 +00:00
return False , [ f " Last update was { days_since_last_update } > { OUTDATED_THRESHOLD } days ago " ]
2023-08-16 09:14:46 +00:00
def check_administrator_name ( self ) :
user_info = self . get_user_info ( self . connection , rid = 500 )
2023-09-22 19:22:54 +00:00
name = user_info [ " UserName " ]
ok = name not in ( " Administrator " , " Administrateur " )
reasons = [ f " Administrator name changed to { name } " if ok else " Administrator name unchanged " ]
2023-08-16 09:14:46 +00:00
return ok , reasons
def check_guest_account_disabled ( self ) :
user_info = self . get_user_info ( self . connection , rid = 501 )
2023-09-22 19:22:54 +00:00
uac = user_info [ " UserAccountControl " ]
2023-08-16 09:14:46 +00:00
disabled = bool ( uac & samr . USER_ACCOUNT_DISABLED )
2023-09-22 19:22:54 +00:00
reasons = [ " Guest account disabled " if disabled else " Guest account enabled " ]
2023-08-16 09:14:46 +00:00
return disabled , reasons
def check_spooler_service ( self ) :
ok = False
2023-09-22 19:22:54 +00:00
service_config , service_status = self . get_service ( " Spooler " , self . connection )
if service_config [ " dwStartType " ] == scmr . SERVICE_DISABLED :
2023-08-16 09:14:46 +00:00
ok = True
2023-09-22 19:22:54 +00:00
reasons = [ " Spooler service disabled " ]
2023-08-16 09:14:46 +00:00
else :
2023-09-23 01:10:21 +00:00
reasons = [ " Spooler service enabled " ]
2023-08-16 09:14:46 +00:00
if service_status == scmr . SERVICE_RUNNING :
2023-09-22 19:22:54 +00:00
reasons . append ( " Spooler service running " )
2023-08-16 09:14:46 +00:00
elif service_status == scmr . SERVICE_STOPPED :
ok = True
2023-09-22 19:22:54 +00:00
reasons . append ( " Spooler service not running " )
2023-08-16 09:14:46 +00:00
return ok , reasons
def check_wsus_running ( self ) :
ok = True
reasons = [ ]
2023-09-22 19:22:54 +00:00
service_config , service_status = self . get_service ( " wuauserv " , self . connection )
if service_config [ " dwStartType " ] == scmr . SERVICE_DISABLED :
reasons = [ " WSUS service disabled " ]
2023-08-16 09:14:46 +00:00
elif service_status != scmr . SERVICE_RUNNING :
2023-09-22 19:22:54 +00:00
reasons = [ " WSUS service not running " ]
2023-08-16 09:14:46 +00:00
return ok , reasons
def check_nbtns ( self ) :
2023-09-22 19:22:54 +00:00
key_name = " HKLM \\ SYSTEM \\ CurrentControlSet \\ Services \\ NetBT \\ Parameters \\ Interfaces "
2023-08-16 09:14:46 +00:00
subkeys = self . reg_get_subkeys ( self . dce , self . connection , key_name )
success = False
reasons = [ ]
missing = 0
nbtns_enabled = 0
for subkey in subkeys :
2023-09-22 19:22:54 +00:00
value = self . reg_query_value ( self . dce , self . connection , key_name + " \\ " + subkey , " NetbiosOptions " )
2023-08-16 09:14:46 +00:00
if type ( value ) == DCERPCSessionError :
if value . error_code == ERROR_OBJECT_NOT_FOUND :
missing + = 1
continue
if value != 2 :
nbtns_enabled + = 1
if missing > 0 :
2023-09-23 01:10:21 +00:00
reasons . append ( f " HKLM \\ SYSTEM \\ CurrentControlSet \\ Services \\ NetBT \\ Parameters \\ Interfaces \\ <interface> \\ NetbiosOption: value not found on { missing } interfaces " )
2023-08-16 09:14:46 +00:00
if nbtns_enabled > 0 :
2023-09-22 19:22:54 +00:00
reasons . append ( f " NBTNS enabled on { nbtns_enabled } interfaces out of { len ( subkeys ) } " )
2023-08-16 09:14:46 +00:00
if missing == 0 and nbtns_enabled == 0 :
success = True
2023-09-22 19:22:54 +00:00
reasons . append ( " NBTNS disabled on all interfaces " )
2023-08-16 09:14:46 +00:00
return success , reasons
def check_applocker ( self ) :
2023-09-22 19:22:54 +00:00
key_name = " HKLM \\ SOFTWARE \\ Policies \\ Microsoft \\ Windows \\ SrpV2 "
2023-08-16 09:14:46 +00:00
subkeys = self . reg_get_subkeys ( self . dce , self . connection , key_name )
rule_count = 0
for collection in subkeys :
2023-09-23 01:10:21 +00:00
collection_key_name = key_name + " \\ " + collection
2023-08-16 09:14:46 +00:00
rules = self . reg_get_subkeys ( self . dce , self . connection , collection_key_name )
rule_count + = len ( rules )
success = rule_count > 0
2023-09-22 19:22:54 +00:00
reasons = [ f " Found { rule_count } AppLocker rules defined " ]
2023-08-16 09:14:46 +00:00
return success , reasons
# Methods for getting values from the remote registry #
#######################################################
def _open_root_key ( self , dce , connection , root_key ) :
ans = None
retries = 1
2023-09-23 01:10:21 +00:00
opener = { " HKLM " : rrp . hOpenLocalMachine , " HKCR " : rrp . hOpenClassesRoot , " HKU " : rrp . hOpenUsers , " HKCU " : rrp . hOpenCurrentUser , " HKCC " : rrp . hOpenCurrentConfig }
2023-08-16 09:14:46 +00:00
while retries > 0 :
try :
ans = opener [ root_key . upper ( ) ] ( dce )
break
except KeyError :
2023-09-23 01:10:21 +00:00
self . context . log . error ( f " HostChecker._open_root_key(): { connection . host } : Invalid root key. Must be one of HKCR, HKCC, HKCU, HKLM or HKU " )
2023-08-16 09:14:46 +00:00
break
except Exception as e :
2023-09-23 01:10:21 +00:00
self . context . log . error ( f " HostChecker._open_root_key(): { connection . host } : Error while trying to open { root_key . upper ( ) } : { e } " )
if " Broken pipe " in e . args :
2023-09-22 19:22:54 +00:00
self . context . log . error ( " Retrying " )
2023-08-16 09:14:46 +00:00
retries - = 1
return ans
def reg_get_subkeys ( self , dce , connection , key_name ) :
2023-09-23 01:10:21 +00:00
root_key , subkey = key_name . split ( " \\ " , 1 )
2023-08-16 09:14:46 +00:00
ans = self . _open_root_key ( dce , connection , root_key )
subkeys = [ ]
if ans is None :
return subkeys
2023-09-22 19:22:54 +00:00
root_key_handle = ans [ " phKey " ]
2023-08-16 09:14:46 +00:00
try :
ans = rrp . hBaseRegOpenKey ( dce , root_key_handle , subkey )
except DCERPCSessionError as e :
if e . error_code != ERROR_FILE_NOT_FOUND :
2023-09-22 19:22:54 +00:00
self . context . log . error ( f " HostChecker.reg_get_subkeys(): Could not retrieve subkey { subkey } : { e } \n " )
2023-08-16 09:14:46 +00:00
return subkeys
except Exception as e :
2023-09-23 01:10:21 +00:00
self . context . log . error ( f " HostChecker.reg_get_subkeys(): Error while trying to retrieve subkey { subkey } : { e } \n " )
2023-08-16 09:14:46 +00:00
return subkeys
2023-09-22 19:22:54 +00:00
subkey_handle = ans [ " phkResult " ]
2023-08-16 09:14:46 +00:00
i = 0
while True :
try :
ans = rrp . hBaseRegEnumKey ( dce = dce , hKey = subkey_handle , dwIndex = i )
2023-09-22 19:22:54 +00:00
subkeys . append ( ans [ " lpNameOut " ] [ : - 1 ] )
2023-08-16 09:14:46 +00:00
i + = 1
2023-09-20 15:59:16 +00:00
except DCERPCSessionError :
2023-08-16 09:14:46 +00:00
break
return subkeys
def reg_query_value ( self , dce , connection , keyName , valueName = None ) :
"""
Query remote registry data for a given registry value
"""
2023-09-22 19:22:54 +00:00
2023-08-16 09:14:46 +00:00
def subkey_values ( subkey_handle ) :
2023-09-22 19:22:54 +00:00
dw_index = 0
2023-08-16 09:14:46 +00:00
while True :
try :
2023-09-22 19:22:54 +00:00
value_type , value_name , value_data = get_value ( subkey_handle , dw_index )
yield value_type , value_name , value_data
dw_index + = 1
2023-08-16 09:14:46 +00:00
except DCERPCSessionError as e :
if e . error_code == ERROR_NO_MORE_ITEMS :
break
else :
2023-09-23 01:10:21 +00:00
self . context . log . error ( f " HostChecker.reg_query_value()->sub_key_values(): Received error code { e . error_code } " )
2023-08-16 09:14:46 +00:00
return
def get_value ( subkey_handle , dwIndex = 0 ) :
ans = rrp . hBaseRegEnumValue ( dce = dce , hKey = subkey_handle , dwIndex = dwIndex )
2023-09-22 19:22:54 +00:00
value_type = ans [ " lpType " ]
value_name = ans [ " lpValueNameOut " ]
value_data = ans [ " lpData " ]
2023-08-16 09:14:46 +00:00
# Do any conversion necessary depending on the registry value type
2023-09-23 01:10:21 +00:00
if value_type in ( REG_VALUE_TYPE_UNICODE_STRING , REG_VALUE_TYPE_UNICODE_STRING_WITH_ENV , REG_VALUE_TYPE_UNICODE_STRING_SEQUENCE ) :
value_data = b " " . join ( value_data ) . decode ( " utf-16 " )
2023-08-16 09:14:46 +00:00
else :
2023-09-23 01:10:21 +00:00
value_data = b " " . join ( value_data )
if value_type in ( REG_VALUE_TYPE_32BIT_LE , REG_VALUE_TYPE_64BIT_LE ) :
2023-09-22 19:22:54 +00:00
value_data = int . from_bytes ( value_data , " little " )
2023-08-16 09:14:46 +00:00
elif value_type == REG_VALUE_TYPE_32BIT_BE :
2023-09-22 19:22:54 +00:00
value_data = int . from_bytes ( value_data , " big " )
2023-08-16 09:14:46 +00:00
return value_type , value_name [ : - 1 ] , value_data
try :
2023-09-23 01:10:21 +00:00
root_key , subkey = keyName . split ( " \\ " , 1 )
2023-08-16 09:14:46 +00:00
except ValueError :
2023-09-22 19:22:54 +00:00
self . context . log . error ( f " HostChecker.reg_query_value(): Could not split keyname { keyName } " )
2023-08-16 09:14:46 +00:00
return
ans = self . _open_root_key ( dce , connection , root_key )
if ans is None :
return ans
2023-09-22 19:22:54 +00:00
root_key_handle = ans [ " phKey " ]
2023-08-16 09:14:46 +00:00
try :
ans = rrp . hBaseRegOpenKey ( dce , root_key_handle , subkey )
except DCERPCSessionError as e :
if e . error_code == ERROR_FILE_NOT_FOUND :
return e
2023-09-22 19:22:54 +00:00
subkey_handle = ans [ " phkResult " ]
2023-08-16 09:14:46 +00:00
if valueName is None :
2023-09-22 19:22:54 +00:00
_ , _ , data = get_value ( subkey_handle )
2023-08-16 09:14:46 +00:00
else :
found = False
2023-09-22 19:22:54 +00:00
for _ , name , data in subkey_values ( subkey_handle ) :
2023-08-16 09:14:46 +00:00
if name . upper ( ) == valueName . upper ( ) :
found = True
break
if not found :
return DCERPCSessionError ( error_code = ERROR_OBJECT_NOT_FOUND )
return data
# Methods for getting values from SAMR and SCM #
################################################
def get_service ( self , service_name , connection ) :
"""
Get the service status and configuration for specified service
"""
remoteOps = RemoteOperations ( smbConnection = connection . conn , doKerberos = False )
2023-09-22 19:22:54 +00:00
machine_name , _ = remoteOps . getMachineNameAndDomain ( )
2023-08-16 09:14:46 +00:00
remoteOps . _RemoteOperations__connectSvcCtl ( )
dce = remoteOps . _RemoteOperations__scmr
2023-09-22 19:22:54 +00:00
scm_handle = scmr . hROpenSCManagerW ( dce , machine_name ) [ " lpScHandle " ]
service_handle = scmr . hROpenServiceW ( dce , scm_handle , service_name ) [ " lpServiceHandle " ]
service_config = scmr . hRQueryServiceConfigW ( dce , service_handle ) [ " lpServiceConfig " ]
service_status = scmr . hRQueryServiceStatus ( dce , service_handle ) [ " lpServiceStatus " ] [ " dwCurrentState " ]
2023-08-16 09:14:46 +00:00
remoteOps . finish ( )
return service_config , service_status
def get_user_info ( self , connection , rid = 501 ) :
"""
Get user information for the user with the specified RID
"""
2023-09-22 19:22:54 +00:00
remote_ops = RemoteOperations ( smbConnection = connection . conn , doKerberos = False )
machine_name , domain_name = remote_ops . getMachineNameAndDomain ( )
2023-08-16 09:14:46 +00:00
try :
2023-09-22 19:22:54 +00:00
remote_ops . connectSamr ( machine_name )
2023-08-16 09:14:46 +00:00
except samr . DCERPCSessionError :
# If connecting to machine_name didn't work, it's probably because
# we're dealing with a domain controller, so we need to use the
# actual domain name instead of the machine name, because DCs don't
# use the SAM
2023-09-22 19:22:54 +00:00
remote_ops . connectSamr ( domain_name )
2023-08-16 09:14:46 +00:00
2023-09-22 19:22:54 +00:00
dce = remote_ops . _RemoteOperations__samr
domain_handle = remote_ops . _RemoteOperations__domainHandle
user_handle = samr . hSamrOpenUser ( dce , domain_handle , userId = rid ) [ " UserHandle " ]
2023-08-16 09:14:46 +00:00
user_info = samr . hSamrQueryInformationUser2 ( dce , user_handle , samr . USER_INFORMATION_CLASS . UserAllInformation )
2023-09-22 19:22:54 +00:00
user_info = user_info [ " Buffer " ] [ " All " ]
remote_ops . finish ( )
2023-08-16 09:14:46 +00:00
return user_info
2023-09-22 19:22:54 +00:00
def ls ( self , smb , path = " \\ " , share = " C$ " ) :
2023-09-22 19:24:22 +00:00
file_listing = [ ]
2023-08-16 09:14:46 +00:00
try :
2023-09-22 19:24:22 +00:00
file_listing = smb . conn . listPath ( share , path )
2023-08-16 09:14:46 +00:00
except SMBSessionError as e :
2023-09-22 19:22:54 +00:00
if e . getErrorString ( ) [ 0 ] not in ( " STATUS_NO_SUCH_FILE " , " STATUS_OBJECT_NAME_NOT_FOUND " ) :
self . context . log . error ( f " ls(): C: \\ { path } { e . getErrorString ( ) } " )
2023-08-16 09:14:46 +00:00
except Exception as e :
2023-09-22 19:22:54 +00:00
self . context . log . error ( f " ls(): C: \\ { path } { e } \n " )
2023-09-22 19:24:22 +00:00
return file_listing
2023-05-05 08:56:24 +00:00
2023-09-22 19:22:54 +00:00
2023-05-05 08:56:24 +00:00
# Comparison operators #
########################
2023-09-22 19:22:54 +00:00
2023-05-05 08:56:24 +00:00
def le ( reg_sz_string , number ) :
2023-08-16 09:14:46 +00:00
return int ( reg_sz_string [ : - 1 ] ) < = number
2023-05-05 08:56:24 +00:00
2023-09-22 19:22:54 +00:00
2023-05-05 08:56:24 +00:00
def in_ ( obj , seq ) :
2023-08-16 09:14:46 +00:00
return obj in seq
2023-05-05 08:56:24 +00:00
2023-09-22 19:22:54 +00:00
2023-05-05 08:56:24 +00:00
def startswith ( string , start ) :
2023-08-16 09:14:46 +00:00
return string . startswith ( start )
2023-05-05 08:56:24 +00:00
2023-09-22 19:22:54 +00:00
2023-05-05 08:56:24 +00:00
def not_ ( boolean_operator ) :
2023-08-16 09:14:46 +00:00
def wrapper ( * args , * * kwargs ) :
return not boolean_operator ( * args , * * kwargs )
2023-09-22 19:22:54 +00:00
wrapper . __name__ = f " not_ { boolean_operator . __name__ } "
2023-08-16 09:14:46 +00:00
return wrapper