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)
|
|
|
|
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-08-17 17:45:20 +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
|
|
|
"""
|
|
|
|
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-22 19:22:54 +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:
|
|
|
|
for result in self.results[host]['checks']:
|
|
|
|
output.write(f'\n{host}')
|
2023-09-22 19:22:54 +00:00
|
|
|
for field in (result["Check"], result["Description"], result["Status"],
|
|
|
|
" ; ".join(result["Reasons"]).replace("\x00", '')):
|
|
|
|
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
|
|
|
|
self.checks = [
|
2023-09-22 19:22:54 +00:00
|
|
|
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=[[
|
2023-08-16 09:14:46 +00:00
|
|
|
self,
|
|
|
|
(
|
2023-09-22 19:22:54 +00:00
|
|
|
"HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System",
|
|
|
|
"EnableLUA", 1
|
|
|
|
), (
|
|
|
|
"HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System",
|
|
|
|
"LocalAccountTokenFilterPolicy", 0
|
2023-08-16 09:14:46 +00:00
|
|
|
)]]),
|
2023-09-22 19:22:54 +00:00
|
|
|
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\\Services\\NTDS",
|
|
|
|
"LdapEnforceChannelBinding",
|
|
|
|
2
|
|
|
|
)
|
|
|
|
]]),
|
|
|
|
ConfigCheck("SMB encryption", "Checks if SMB encryption is enabled", checker_args=[[self, (
|
|
|
|
"HKLM\\SYSTEM\\CurrentControlSet\\Services\\LanmanServer\\Parameters",
|
|
|
|
"EncryptData", 1
|
|
|
|
)
|
|
|
|
]]),
|
|
|
|
ConfigCheck("RDP authentication",
|
|
|
|
"Checks RDP authentication configuration (NLA auth and restricted admin mode)",
|
|
|
|
checker_args=[[self, (
|
|
|
|
"HKLM\\System\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp\\",
|
|
|
|
"UserAuthentication", 1
|
|
|
|
), (
|
|
|
|
"HKLM\\SYSTEM\\CurrentControlSet\\Control\\LSA",
|
|
|
|
"RestrictedAdminMode", 1
|
|
|
|
)
|
|
|
|
]]),
|
|
|
|
ConfigCheck("BitLocker configuration",
|
|
|
|
"Checks the BitLocker configuration (based on https://www.stigviewer.com/stig/windows_10/2020-06-15/finding/V-94859)",
|
|
|
|
checker_args=[[self, (
|
|
|
|
"HKLM\\SOFTWARE\\Policies\\Microsoft\\FVE",
|
|
|
|
"UseAdvancedStartup", 1
|
|
|
|
), (
|
|
|
|
"HKLM\\SOFTWARE\\Policies\\Microsoft\\FVE",
|
|
|
|
"UseTPMPIN", 1
|
|
|
|
)
|
|
|
|
]]),
|
|
|
|
ConfigCheck("Guest account disabled", "Checks if the guest account is disabled",
|
|
|
|
checkers=[self.check_guest_account_disabled]),
|
|
|
|
ConfigCheck("Automatic session lock",
|
|
|
|
"Checks if the session is automatically locked on after a period of inactivity",
|
|
|
|
checker_args=[[self, (
|
|
|
|
"HKCU\\Control Panel\\Desktop",
|
|
|
|
"ScreenSaverIsSecure", 1
|
|
|
|
), (
|
|
|
|
"HKCU\\Control Panel\\Desktop",
|
|
|
|
"ScreenSaveTimeOut", 300, le
|
|
|
|
)
|
|
|
|
]]),
|
|
|
|
ConfigCheck("Powershell Execution Policy",
|
|
|
|
"Checks if the Powershell execution policy is set to \"Restricted\"", checker_args=[[self, (
|
|
|
|
"HKLM\\SOFTWARE\\Microsoft\\PowerShell\\1\ShellIds\Microsoft.Powershell",
|
|
|
|
"ExecutionPolicy", "Restricted\x00"
|
|
|
|
), (
|
|
|
|
"HKCU\\SOFTWARE\\Microsoft\\PowerShell\\1\ShellIds\Microsoft.Powershell",
|
|
|
|
"ExecutionPolicy",
|
|
|
|
"Restricted\x00"
|
|
|
|
)
|
|
|
|
]],
|
|
|
|
checker_kwargs=[{"options": {"KOIfMissing": False, "lastWins": True}}])
|
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()
|
|
|
|
if db_check['name'].strip().lower() == check.name.strip().lower():
|
|
|
|
check_id = db_check['id']
|
|
|
|
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-22 19:22:54 +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
|
|
|
|
"""
|
|
|
|
default_options = {
|
2023-09-22 19:22:54 +00:00
|
|
|
"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-22 19:22:54 +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-22 19:22:54 +00:00
|
|
|
value = self.reg_query_value(self.dce, self.connection, lapsv1_key_name + '\\' + subkey, "DllName")
|
|
|
|
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
|
|
|
|
laps_path = '\\'.join(value.split('\\')[1:-1])
|
|
|
|
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
|
|
|
|
|
|
|
l = self.ls(self.connection, laps_path)
|
|
|
|
if l:
|
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:22:54 +00:00
|
|
|
l = self.ls(self.connection, laps_path + "\\AdmPwd.dll")
|
2023-08-16 09:14:46 +00:00
|
|
|
if l:
|
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
|
|
|
|
reasons.append(f'{laps_path}\\AdmPwd.dll not found')
|
|
|
|
|
|
|
|
return success, reasons
|
|
|
|
|
|
|
|
def check_last_successful_update(self):
|
2023-09-22 19:22:54 +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-08-16 09:14:46 +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:
|
|
|
|
reasons = ['Spooler service enabled']
|
|
|
|
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-22 19:22:54 +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:
|
|
|
|
collection_key_name = key_name + '\\' + collection
|
|
|
|
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
|
|
|
|
opener = {
|
2023-09-22 19:22:54 +00:00
|
|
|
"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-22 19:22:54 +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-22 19:22:54 +00:00
|
|
|
self.context.log.error(
|
|
|
|
f"HostChecker._open_root_key():{connection.host}: Error while trying to open {root_key.upper()}: {e}")
|
2023-08-16 09:14:46 +00:00
|
|
|
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):
|
|
|
|
root_key, subkey = key_name.split('\\', 1)
|
|
|
|
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-22 19:22:54 +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-22 19:22:54 +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
|
|
|
|
if value_type in (
|
2023-09-22 19:22:54 +00:00
|
|
|
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:
|
|
|
|
value_data = b''.join(value_data)
|
|
|
|
if value_type in (
|
2023-09-22 19:22:54 +00:00
|
|
|
REG_VALUE_TYPE_32BIT_LE,
|
|
|
|
REG_VALUE_TYPE_64BIT_LE):
|
|
|
|
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:
|
|
|
|
root_key, subkey = keyName.split('\\', 1)
|
|
|
|
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-08-16 09:14:46 +00:00
|
|
|
l = []
|
|
|
|
try:
|
|
|
|
l = smb.conn.listPath(share, path)
|
|
|
|
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-08-16 09:14:46 +00:00
|
|
|
return l
|
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
|