switch pso module to core feature

This commit is contained in:
mpgn
2025-07-15 18:22:30 +02:00
parent 11d484b268
commit f32dd56fb3
3 changed files with 93 additions and 92 deletions
+1 -87
View File
@@ -21,90 +21,4 @@ class NXCModule:
"""No options available."""
def on_login(self, context, connection):
# Are there even any FGPPs?
context.log.success("Attempting to enumerate policies...")
resp = connection.ldap_connection.search(searchBase=f"CN=Password Settings Container,CN=System,{''.join([f'DC={dc},' for dc in connection.domain.split('.')]).rstrip(',')}", searchFilter="(objectclass=*)")
if len(resp) > 1:
context.log.highlight(f"{len(resp) - 1} PSO Objects found!")
context.log.highlight("")
context.log.success("Attempting to enumerate objects with an applied policy...")
# Who do they apply to?
resp = connection.search(searchFilter="(objectclass=*)", attributes=["DistinguishedName", "msDS-PSOApplied"])
for attrs in resp:
if isinstance(attrs, ldapasn1_impacket.SearchResultEntry) is not True:
continue
for attr in attrs["attributes"]:
if str(attr["type"]) in "msDS-PSOApplied":
context.log.highlight(f"Object: {attrs['objectName']}")
context.log.highlight("Applied Policy: ")
for value in attr["vals"]:
context.log.highlight(f"\t{value}")
context.log.highlight("")
# Let"s find out even more details!
context.log.success("Attempting to enumerate details...\n")
resp = connection.search(searchFilter="(objectclass=msDS-PasswordSettings)",
attributes=["name", "msds-lockoutthreshold", "msds-psoappliesto", "msds-minimumpasswordlength",
"msds-passwordhistorylength", "msds-lockoutobservationwindow", "msds-lockoutduration",
"msds-passwordsettingsprecedence", "msds-passwordcomplexityenabled", "Description",
"msds-passwordreversibleencryptionenabled", "msds-minimumpasswordage", "msds-maximumpasswordage"])
for attrs in resp:
if not isinstance(attrs, ldapasn1_impacket.SearchResultEntry):
continue
policyName, description, passwordLength, passwordhistorylength, lockoutThreshold, observationWindow, lockoutDuration, complexity, minPassAge, maxPassAge, reverseibleEncryption, precedence, policyApplies = ("",) * 13
for attr in attrs["attributes"]:
if str(attr["type"]) == "name":
policyName = attr["vals"][0]
elif str(attr["type"]) == "msDS-LockoutThreshold":
lockoutThreshold = attr["vals"][0]
elif str(attr["type"]) == "msDS-MinimumPasswordLength":
passwordLength = attr["vals"][0]
elif str(attr["type"]) == "msDS-PasswordHistoryLength":
passwordhistorylength = attr["vals"][0]
elif str(attr["type"]) == "msDS-LockoutObservationWindow":
observationWindow = attr["vals"][0]
elif str(attr["type"]) == "msDS-LockoutDuration":
lockoutDuration = attr["vals"][0]
elif str(attr["type"]) == "msDS-PasswordSettingsPrecedence":
precedence = attr["vals"][0]
elif str(attr["type"]) == "msDS-PasswordComplexityEnabled":
complexity = attr["vals"][0]
elif str(attr["type"]) == "msDS-PasswordReversibleEncryptionEnabled":
reverseibleEncryption = attr["vals"][0]
elif str(attr["type"]) == "msDS-MinimumPasswordAge":
minPassAge = attr["vals"][0]
elif str(attr["type"]) == "msDS-MaximumPasswordAge":
maxPassAge = attr["vals"][0]
elif str(attr["type"]) == "description":
description = attr["vals"][0]
elif str(attr["type"]) == "msDS-PSOAppliesTo":
policyApplies = ""
for value in attr["vals"]:
policyApplies += f"{value};"
context.log.highlight(f"Policy Name: {policyName}")
if description:
context.log.highlight(f"Description: {description}")
context.log.highlight(f"Minimum Password Length: {passwordLength}")
context.log.highlight(f"Minimum Password History Length: {passwordhistorylength}")
context.log.highlight(f"Lockout Threshold: {lockoutThreshold}")
context.log.highlight(f"Observation Window: {mins(observationWindow)}")
context.log.highlight(f"Lockout Duration: {mins(lockoutDuration)}")
context.log.highlight(f"Complexity Enabled: {complexity}")
context.log.highlight(f"Minimum Password Age: {days(minPassAge)}")
context.log.highlight(f"Maximum Password Age: {days(maxPassAge)}")
context.log.highlight(f"Reversible Encryption: {reverseibleEncryption}")
context.log.highlight(f"Precedence: {precedence} (Lower is Higher Priority)")
context.log.highlight("Policy Applies to:")
for value in str(policyApplies)[:-1].split(";"):
if value:
context.log.highlight(f"\t{value}")
context.log.highlight("")
def days(ldap_time):
return f"{rd(seconds=int(abs(int(ldap_time)) / 10000000)).days} days"
def mins(ldap_time):
return f"{rd(seconds=int(abs(int(ldap_time)) / 10000000)).minutes} minutes"
context.log.fail('[REMOVED] This module moved to the core option --pso')
+91 -5
View File
@@ -10,6 +10,7 @@ from re import sub, IGNORECASE
from zipfile import ZipFile
from termcolor import colored
from dns import resolver
from dateutil.relativedelta import relativedelta as rd
from Cryptodome.Hash import MD4
from OpenSSL.SSL import SysCallError
@@ -688,11 +689,6 @@ class ldap(connection):
if item:
self.admin_privs = True
def getUnixTime(self, t):
t -= 116444736000000000
t /= 10000000
return t
def search(self, searchFilter, attributes, sizeLimit=0, baseDN=None) -> list:
if baseDN is None and self.args.base_dn is not None:
baseDN = self.args.base_dn
@@ -1396,6 +1392,95 @@ class ldap(connection):
else:
self.logger.fail("No string provided :'(")
def pso(self):
"""
Get the Fine Grained Password Policy/PSOs
Initial FGPP/PSO script written by @n00py: https://github.com/n00py/GetFGPP
"""
# Convert LDAP time to human readable format
pso_days = lambda ldap_time: f"{rd(seconds=int(abs(int(ldap_time)) / 10000000)).days} days"
pso_mins = lambda ldap_time: f"{rd(seconds=int(abs(int(ldap_time)) / 10000000)).minutes} minutes"
# Are there even any FGPPs?
self.logger.success("Attempting to enumerate policies...")
resp = self.ldap_connection.search(searchBase=f"CN=Password Settings Container,CN=System,{''.join([f'DC={dc},' for dc in self.domain.split('.')]).rstrip(',')}", searchFilter="(objectclass=*)")
if len(resp) > 1:
self.logger.highlight(f"{len(resp) - 1} PSO Objects found!")
self.logger.highlight("")
self.logger.success("Attempting to enumerate objects with an applied policy...")
# Who do they apply to?
resp = self.search(searchFilter="(objectclass=*)", attributes=["DistinguishedName", "msDS-PSOApplied"])
for attrs in resp:
if isinstance(attrs, ldapasn1_impacket.SearchResultEntry) is not True:
continue
for attr in attrs["attributes"]:
if str(attr["type"]) in "msDS-PSOApplied":
self.logger.highlight(f"Object: {attrs['objectName']}")
self.logger.highlight("Applied Policy: ")
for value in attr["vals"]:
self.logger.highlight(f"\t{value}")
self.logger.highlight("")
# Let's find out even more details!
self.logger.success("Attempting to enumerate details...\n")
resp = self.search(searchFilter="(objectclass=msDS-PasswordSettings)",
attributes=["name", "msds-lockoutthreshold", "msds-psoappliesto", "msds-minimumpasswordlength",
"msds-passwordhistorylength", "msds-lockoutobservationwindow", "msds-lockoutduration",
"msds-passwordsettingsprecedence", "msds-passwordcomplexityenabled", "Description",
"msds-passwordreversibleencryptionenabled", "msds-minimumpasswordage", "msds-maximumpasswordage"])
for attrs in resp:
if not isinstance(attrs, ldapasn1_impacket.SearchResultEntry):
continue
policyName, description, passwordLength, passwordhistorylength, lockoutThreshold, observationWindow, lockoutDuration, complexity, minPassAge, maxPassAge, reverseibleEncryption, precedence, policyApplies = ("",) * 13
for attr in attrs["attributes"]:
if str(attr["type"]) == "name":
policyName = attr["vals"][0]
elif str(attr["type"]) == "msDS-LockoutThreshold":
lockoutThreshold = attr["vals"][0]
elif str(attr["type"]) == "msDS-MinimumPasswordLength":
passwordLength = attr["vals"][0]
elif str(attr["type"]) == "msDS-PasswordHistoryLength":
passwordhistorylength = attr["vals"][0]
elif str(attr["type"]) == "msDS-LockoutObservationWindow":
observationWindow = attr["vals"][0]
elif str(attr["type"]) == "msDS-LockoutDuration":
lockoutDuration = attr["vals"][0]
elif str(attr["type"]) == "msDS-PasswordSettingsPrecedence":
precedence = attr["vals"][0]
elif str(attr["type"]) == "msDS-PasswordComplexityEnabled":
complexity = attr["vals"][0]
elif str(attr["type"]) == "msDS-PasswordReversibleEncryptionEnabled":
reverseibleEncryption = attr["vals"][0]
elif str(attr["type"]) == "msDS-MinimumPasswordAge":
minPassAge = attr["vals"][0]
elif str(attr["type"]) == "msDS-MaximumPasswordAge":
maxPassAge = attr["vals"][0]
elif str(attr["type"]) == "description":
description = attr["vals"][0]
elif str(attr["type"]) == "msDS-PSOAppliesTo":
policyApplies = ""
for value in attr["vals"]:
policyApplies += f"{value};"
self.logger.highlight(f"Policy Name: {policyName}")
if description:
self.logger.highlight(f"Description: {description}")
self.logger.highlight(f"Minimum Password Length: {passwordLength}")
self.logger.highlight(f"Minimum Password History Length: {passwordhistorylength}")
self.logger.highlight(f"Lockout Threshold: {lockoutThreshold}")
self.logger.highlight(f"Observation Window: {pso_mins(observationWindow)}")
self.logger.highlight(f"Lockout Duration: {pso_mins(lockoutDuration)}")
self.logger.highlight(f"Complexity Enabled: {complexity}")
self.logger.highlight(f"Minimum Password Age: {pso_days(minPassAge)}")
self.logger.highlight(f"Maximum Password Age: {pso_days(maxPassAge)}")
self.logger.highlight(f"Reversible Encryption: {reverseibleEncryption}")
self.logger.highlight(f"Precedence: {precedence} (Lower is Higher Priority)")
self.logger.highlight("Policy Applies to:")
for value in str(policyApplies)[:-1].split(";"):
if value:
self.logger.highlight(f"\t{value}")
self.logger.highlight("")
def bloodhound(self):
# Check which version is desired
use_bhce = self.config.getboolean("BloodHound-CE", "bhce_enabled", fallback=False)
@@ -1412,6 +1497,7 @@ class ldap(connection):
# If using pipx
self.logger.fail("Or if you installed with pipx:")
self.logger.fail("pipx runpip netexec uninstall -y bloodhound")
self.logger.fail("pipx inject netexec bloodhound-ce --force")
return False
+1
View File
@@ -29,6 +29,7 @@ def proto_args(parser, parents):
vgroup.add_argument("--dc-list", action="store_true", help="Enumerate Domain Controllers")
vgroup.add_argument("--get-sid", action="store_true", help="Get domain sid")
vgroup.add_argument("--active-users", nargs="*", help="Get Active Domain Users Accounts")
vgroup.add_argument("--pso", action="store_true", help="Get Fine Grained Password Policy/PSOs")
ggroup = ldap_parser.add_argument_group("Retrieve gmsa on the remote DC", "Options to play with gmsa")
ggroup.add_argument("--gmsa", action="store_true", help="Enumerate GMSA passwords")