Skip to content

Commit f32dd56

Browse files
committed
switch pso module to core feature
1 parent 11d484b commit f32dd56

3 files changed

Lines changed: 93 additions & 92 deletions

File tree

nxc/modules/pso.py

Lines changed: 1 addition & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -21,90 +21,4 @@ def options(self, context, module_options):
2121
"""No options available."""
2222

2323
def on_login(self, context, connection):
24-
# Are there even any FGPPs?
25-
context.log.success("Attempting to enumerate policies...")
26-
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=*)")
27-
if len(resp) > 1:
28-
context.log.highlight(f"{len(resp) - 1} PSO Objects found!")
29-
context.log.highlight("")
30-
context.log.success("Attempting to enumerate objects with an applied policy...")
31-
32-
# Who do they apply to?
33-
resp = connection.search(searchFilter="(objectclass=*)", attributes=["DistinguishedName", "msDS-PSOApplied"])
34-
for attrs in resp:
35-
if isinstance(attrs, ldapasn1_impacket.SearchResultEntry) is not True:
36-
continue
37-
for attr in attrs["attributes"]:
38-
if str(attr["type"]) in "msDS-PSOApplied":
39-
context.log.highlight(f"Object: {attrs['objectName']}")
40-
context.log.highlight("Applied Policy: ")
41-
for value in attr["vals"]:
42-
context.log.highlight(f"\t{value}")
43-
context.log.highlight("")
44-
45-
# Let"s find out even more details!
46-
context.log.success("Attempting to enumerate details...\n")
47-
resp = connection.search(searchFilter="(objectclass=msDS-PasswordSettings)",
48-
attributes=["name", "msds-lockoutthreshold", "msds-psoappliesto", "msds-minimumpasswordlength",
49-
"msds-passwordhistorylength", "msds-lockoutobservationwindow", "msds-lockoutduration",
50-
"msds-passwordsettingsprecedence", "msds-passwordcomplexityenabled", "Description",
51-
"msds-passwordreversibleencryptionenabled", "msds-minimumpasswordage", "msds-maximumpasswordage"])
52-
for attrs in resp:
53-
if not isinstance(attrs, ldapasn1_impacket.SearchResultEntry):
54-
continue
55-
policyName, description, passwordLength, passwordhistorylength, lockoutThreshold, observationWindow, lockoutDuration, complexity, minPassAge, maxPassAge, reverseibleEncryption, precedence, policyApplies = ("",) * 13
56-
for attr in attrs["attributes"]:
57-
if str(attr["type"]) == "name":
58-
policyName = attr["vals"][0]
59-
elif str(attr["type"]) == "msDS-LockoutThreshold":
60-
lockoutThreshold = attr["vals"][0]
61-
elif str(attr["type"]) == "msDS-MinimumPasswordLength":
62-
passwordLength = attr["vals"][0]
63-
elif str(attr["type"]) == "msDS-PasswordHistoryLength":
64-
passwordhistorylength = attr["vals"][0]
65-
elif str(attr["type"]) == "msDS-LockoutObservationWindow":
66-
observationWindow = attr["vals"][0]
67-
elif str(attr["type"]) == "msDS-LockoutDuration":
68-
lockoutDuration = attr["vals"][0]
69-
elif str(attr["type"]) == "msDS-PasswordSettingsPrecedence":
70-
precedence = attr["vals"][0]
71-
elif str(attr["type"]) == "msDS-PasswordComplexityEnabled":
72-
complexity = attr["vals"][0]
73-
elif str(attr["type"]) == "msDS-PasswordReversibleEncryptionEnabled":
74-
reverseibleEncryption = attr["vals"][0]
75-
elif str(attr["type"]) == "msDS-MinimumPasswordAge":
76-
minPassAge = attr["vals"][0]
77-
elif str(attr["type"]) == "msDS-MaximumPasswordAge":
78-
maxPassAge = attr["vals"][0]
79-
elif str(attr["type"]) == "description":
80-
description = attr["vals"][0]
81-
elif str(attr["type"]) == "msDS-PSOAppliesTo":
82-
policyApplies = ""
83-
for value in attr["vals"]:
84-
policyApplies += f"{value};"
85-
context.log.highlight(f"Policy Name: {policyName}")
86-
if description:
87-
context.log.highlight(f"Description: {description}")
88-
context.log.highlight(f"Minimum Password Length: {passwordLength}")
89-
context.log.highlight(f"Minimum Password History Length: {passwordhistorylength}")
90-
context.log.highlight(f"Lockout Threshold: {lockoutThreshold}")
91-
context.log.highlight(f"Observation Window: {mins(observationWindow)}")
92-
context.log.highlight(f"Lockout Duration: {mins(lockoutDuration)}")
93-
context.log.highlight(f"Complexity Enabled: {complexity}")
94-
context.log.highlight(f"Minimum Password Age: {days(minPassAge)}")
95-
context.log.highlight(f"Maximum Password Age: {days(maxPassAge)}")
96-
context.log.highlight(f"Reversible Encryption: {reverseibleEncryption}")
97-
context.log.highlight(f"Precedence: {precedence} (Lower is Higher Priority)")
98-
context.log.highlight("Policy Applies to:")
99-
for value in str(policyApplies)[:-1].split(";"):
100-
if value:
101-
context.log.highlight(f"\t{value}")
102-
context.log.highlight("")
103-
104-
105-
def days(ldap_time):
106-
return f"{rd(seconds=int(abs(int(ldap_time)) / 10000000)).days} days"
107-
108-
109-
def mins(ldap_time):
110-
return f"{rd(seconds=int(abs(int(ldap_time)) / 10000000)).minutes} minutes"
24+
context.log.fail('[REMOVED] This module moved to the core option --pso')

nxc/protocols/ldap.py

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from zipfile import ZipFile
1111
from termcolor import colored
1212
from dns import resolver
13+
from dateutil.relativedelta import relativedelta as rd
1314

1415
from Cryptodome.Hash import MD4
1516
from OpenSSL.SSL import SysCallError
@@ -688,11 +689,6 @@ def check_if_admin(self):
688689
if item:
689690
self.admin_privs = True
690691

691-
def getUnixTime(self, t):
692-
t -= 116444736000000000
693-
t /= 10000000
694-
return t
695-
696692
def search(self, searchFilter, attributes, sizeLimit=0, baseDN=None) -> list:
697693
if baseDN is None and self.args.base_dn is not None:
698694
baseDN = self.args.base_dn
@@ -1396,6 +1392,95 @@ def gmsa_decrypt_lsa(self):
13961392
else:
13971393
self.logger.fail("No string provided :'(")
13981394

1395+
def pso(self):
1396+
"""
1397+
Get the Fine Grained Password Policy/PSOs
1398+
Initial FGPP/PSO script written by @n00py: https://github.com/n00py/GetFGPP
1399+
"""
1400+
# Convert LDAP time to human readable format
1401+
pso_days = lambda ldap_time: f"{rd(seconds=int(abs(int(ldap_time)) / 10000000)).days} days"
1402+
pso_mins = lambda ldap_time: f"{rd(seconds=int(abs(int(ldap_time)) / 10000000)).minutes} minutes"
1403+
1404+
# Are there even any FGPPs?
1405+
self.logger.success("Attempting to enumerate policies...")
1406+
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=*)")
1407+
if len(resp) > 1:
1408+
self.logger.highlight(f"{len(resp) - 1} PSO Objects found!")
1409+
self.logger.highlight("")
1410+
self.logger.success("Attempting to enumerate objects with an applied policy...")
1411+
1412+
# Who do they apply to?
1413+
resp = self.search(searchFilter="(objectclass=*)", attributes=["DistinguishedName", "msDS-PSOApplied"])
1414+
for attrs in resp:
1415+
if isinstance(attrs, ldapasn1_impacket.SearchResultEntry) is not True:
1416+
continue
1417+
for attr in attrs["attributes"]:
1418+
if str(attr["type"]) in "msDS-PSOApplied":
1419+
self.logger.highlight(f"Object: {attrs['objectName']}")
1420+
self.logger.highlight("Applied Policy: ")
1421+
for value in attr["vals"]:
1422+
self.logger.highlight(f"\t{value}")
1423+
self.logger.highlight("")
1424+
1425+
# Let's find out even more details!
1426+
self.logger.success("Attempting to enumerate details...\n")
1427+
resp = self.search(searchFilter="(objectclass=msDS-PasswordSettings)",
1428+
attributes=["name", "msds-lockoutthreshold", "msds-psoappliesto", "msds-minimumpasswordlength",
1429+
"msds-passwordhistorylength", "msds-lockoutobservationwindow", "msds-lockoutduration",
1430+
"msds-passwordsettingsprecedence", "msds-passwordcomplexityenabled", "Description",
1431+
"msds-passwordreversibleencryptionenabled", "msds-minimumpasswordage", "msds-maximumpasswordage"])
1432+
for attrs in resp:
1433+
if not isinstance(attrs, ldapasn1_impacket.SearchResultEntry):
1434+
continue
1435+
policyName, description, passwordLength, passwordhistorylength, lockoutThreshold, observationWindow, lockoutDuration, complexity, minPassAge, maxPassAge, reverseibleEncryption, precedence, policyApplies = ("",) * 13
1436+
for attr in attrs["attributes"]:
1437+
if str(attr["type"]) == "name":
1438+
policyName = attr["vals"][0]
1439+
elif str(attr["type"]) == "msDS-LockoutThreshold":
1440+
lockoutThreshold = attr["vals"][0]
1441+
elif str(attr["type"]) == "msDS-MinimumPasswordLength":
1442+
passwordLength = attr["vals"][0]
1443+
elif str(attr["type"]) == "msDS-PasswordHistoryLength":
1444+
passwordhistorylength = attr["vals"][0]
1445+
elif str(attr["type"]) == "msDS-LockoutObservationWindow":
1446+
observationWindow = attr["vals"][0]
1447+
elif str(attr["type"]) == "msDS-LockoutDuration":
1448+
lockoutDuration = attr["vals"][0]
1449+
elif str(attr["type"]) == "msDS-PasswordSettingsPrecedence":
1450+
precedence = attr["vals"][0]
1451+
elif str(attr["type"]) == "msDS-PasswordComplexityEnabled":
1452+
complexity = attr["vals"][0]
1453+
elif str(attr["type"]) == "msDS-PasswordReversibleEncryptionEnabled":
1454+
reverseibleEncryption = attr["vals"][0]
1455+
elif str(attr["type"]) == "msDS-MinimumPasswordAge":
1456+
minPassAge = attr["vals"][0]
1457+
elif str(attr["type"]) == "msDS-MaximumPasswordAge":
1458+
maxPassAge = attr["vals"][0]
1459+
elif str(attr["type"]) == "description":
1460+
description = attr["vals"][0]
1461+
elif str(attr["type"]) == "msDS-PSOAppliesTo":
1462+
policyApplies = ""
1463+
for value in attr["vals"]:
1464+
policyApplies += f"{value};"
1465+
self.logger.highlight(f"Policy Name: {policyName}")
1466+
if description:
1467+
self.logger.highlight(f"Description: {description}")
1468+
self.logger.highlight(f"Minimum Password Length: {passwordLength}")
1469+
self.logger.highlight(f"Minimum Password History Length: {passwordhistorylength}")
1470+
self.logger.highlight(f"Lockout Threshold: {lockoutThreshold}")
1471+
self.logger.highlight(f"Observation Window: {pso_mins(observationWindow)}")
1472+
self.logger.highlight(f"Lockout Duration: {pso_mins(lockoutDuration)}")
1473+
self.logger.highlight(f"Complexity Enabled: {complexity}")
1474+
self.logger.highlight(f"Minimum Password Age: {pso_days(minPassAge)}")
1475+
self.logger.highlight(f"Maximum Password Age: {pso_days(maxPassAge)}")
1476+
self.logger.highlight(f"Reversible Encryption: {reverseibleEncryption}")
1477+
self.logger.highlight(f"Precedence: {precedence} (Lower is Higher Priority)")
1478+
self.logger.highlight("Policy Applies to:")
1479+
for value in str(policyApplies)[:-1].split(";"):
1480+
if value:
1481+
self.logger.highlight(f"\t{value}")
1482+
self.logger.highlight("")
1483+
13991484
def bloodhound(self):
14001485
# Check which version is desired
14011486
use_bhce = self.config.getboolean("BloodHound-CE", "bhce_enabled", fallback=False)
@@ -1412,6 +1497,7 @@ def bloodhound(self):
14121497
# If using pipx
14131498
self.logger.fail("Or if you installed with pipx:")
14141499
self.logger.fail("pipx runpip netexec uninstall -y bloodhound")
1500+
14151501
self.logger.fail("pipx inject netexec bloodhound-ce --force")
14161502
return False
14171503

nxc/protocols/ldap/proto_args.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def proto_args(parser, parents):
2929
vgroup.add_argument("--dc-list", action="store_true", help="Enumerate Domain Controllers")
3030
vgroup.add_argument("--get-sid", action="store_true", help="Get domain sid")
3131
vgroup.add_argument("--active-users", nargs="*", help="Get Active Domain Users Accounts")
32+
vgroup.add_argument("--pso", action="store_true", help="Get Fine Grained Password Policy/PSOs")
3233

3334
ggroup = ldap_parser.add_argument_group("Retrieve gmsa on the remote DC", "Options to play with gmsa")
3435
ggroup.add_argument("--gmsa", action="store_true", help="Enumerate GMSA passwords")

0 commit comments

Comments
 (0)