Skip to content

Commit 95cd4f3

Browse files
authored
Merge pull request Pennyw0rth#798 from Pennyw0rth/pso
Switch pso module to core feature
2 parents 8d53bb5 + 2d0bd8e commit 95cd4f3

4 files changed

Lines changed: 84 additions & 91 deletions

File tree

nxc/modules/pso.py

Lines changed: 1 addition & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
from dateutil.relativedelta import relativedelta as rd
2-
from impacket.ldap import ldapasn1 as ldapasn1_impacket
3-
41

52
class NXCModule:
63
"""
@@ -21,90 +18,4 @@ def options(self, context, module_options):
2118
"""No options available."""
2219

2320
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"
21+
context.log.fail("[REMOVED] This module moved to the core option --pso")

nxc/protocols/ldap.py

Lines changed: 81 additions & 0 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
@@ -1396,6 +1397,86 @@ def gmsa_decrypt_lsa(self):
13961397
else:
13971398
self.logger.fail("No string provided :'(")
13981399

1400+
def pso(self):
1401+
"""
1402+
Get the Fine Grained Password Policy/PSOs
1403+
Initial FGPP/PSO script written by @n00py: https://github.com/n00py/GetFGPP
1404+
"""
1405+
# Convert LDAP time to human readable format
1406+
def pso_days(ldap_time):
1407+
return f"{rd(seconds=int(abs(int(ldap_time)) / 10000000)).days} days"
1408+
1409+
def pso_mins(ldap_time):
1410+
return f"{rd(seconds=int(abs(int(ldap_time)) / 10000000)).minutes} minutes"
1411+
1412+
# Are there even any FGPPs?
1413+
self.logger.info("Attempting to enumerate policies...")
1414+
resp = self.search(searchFilter="(objectclass=*)", baseDN=f"CN=Password Settings Container,CN=System,{self.baseDN}", attributes=[])
1415+
if len(resp) > 1:
1416+
self.logger.highlight(f"{len(resp) - 1} PSO Objects found!")
1417+
self.logger.highlight("")
1418+
self.logger.success("Attempting to enumerate objects with an applied policy...")
1419+
1420+
# Who do they apply to?
1421+
resp = self.search(searchFilter="(objectclass=*)", attributes=["DistinguishedName", "msDS-PSOApplied"])
1422+
resp_parsed = parse_result_attributes(resp)
1423+
for attrs in resp_parsed:
1424+
if "msDS-PSOApplied" in attrs:
1425+
# Get the distinguished name from the original response for objectName
1426+
for orig_resp in resp:
1427+
if isinstance(orig_resp, ldapasn1_impacket.SearchResultEntry):
1428+
self.logger.highlight(f"Object: {orig_resp['objectName']}")
1429+
break
1430+
self.logger.highlight("Applied Policy: ")
1431+
pso_applied = attrs["msDS-PSOApplied"]
1432+
self.logger.highlight(f"\t{pso_applied}")
1433+
self.logger.highlight("")
1434+
1435+
# Let's find out even more details!
1436+
self.logger.info("Attempting to enumerate details...\n")
1437+
resp = self.search(searchFilter="(objectclass=msDS-PasswordSettings)",
1438+
attributes=["name", "msds-lockoutthreshold", "msds-psoappliesto", "msds-minimumpasswordlength",
1439+
"msds-passwordhistorylength", "msds-lockoutobservationwindow", "msds-lockoutduration",
1440+
"msds-passwordsettingsprecedence", "msds-passwordcomplexityenabled", "Description",
1441+
"msds-passwordreversibleencryptionenabled", "msds-minimumpasswordage", "msds-maximumpasswordage"])
1442+
resp_parsed = parse_result_attributes(resp)
1443+
for attrs in resp_parsed:
1444+
policyName = attrs.get("name", "")
1445+
description = attrs.get("description", "")
1446+
passwordLength = attrs.get("msDS-MinimumPasswordLength", "")
1447+
passwordhistorylength = attrs.get("msDS-PasswordHistoryLength", "")
1448+
lockoutThreshold = attrs.get("msDS-LockoutThreshold", "")
1449+
observationWindow = attrs.get("msDS-LockoutObservationWindow", "")
1450+
lockoutDuration = attrs.get("msDS-LockoutDuration", "")
1451+
complexity = attrs.get("msDS-PasswordComplexityEnabled", "")
1452+
minPassAge = attrs.get("msDS-MinimumPasswordAge", "")
1453+
maxPassAge = attrs.get("msDS-MaximumPasswordAge", "")
1454+
reverseibleEncryption = attrs.get("msDS-PasswordReversibleEncryptionEnabled", "")
1455+
precedence = attrs.get("msDS-PasswordSettingsPrecedence", "")
1456+
policyApplies = attrs.get("msDS-PSOAppliesTo", "")
1457+
1458+
self.logger.highlight(f"Policy Name: {policyName}")
1459+
if description:
1460+
self.logger.highlight(f"Description: {description}")
1461+
self.logger.highlight(f"Minimum Password Length: {passwordLength}")
1462+
self.logger.highlight(f"Minimum Password History Length: {passwordhistorylength}")
1463+
self.logger.highlight(f"Lockout Threshold: {lockoutThreshold}")
1464+
self.logger.highlight(f"Observation Window: {pso_mins(observationWindow)}")
1465+
self.logger.highlight(f"Lockout Duration: {pso_mins(lockoutDuration)}")
1466+
self.logger.highlight(f"Complexity Enabled: {complexity}")
1467+
self.logger.highlight(f"Minimum Password Age: {pso_days(minPassAge)}")
1468+
self.logger.highlight(f"Maximum Password Age: {pso_days(maxPassAge)}")
1469+
self.logger.highlight(f"Reversible Encryption: {reverseibleEncryption}")
1470+
self.logger.highlight(f"Precedence: {precedence} (Lower is Higher Priority)")
1471+
self.logger.highlight("Policy Applies to:")
1472+
if isinstance(policyApplies, list):
1473+
for value in policyApplies:
1474+
if value:
1475+
self.logger.highlight(f"\t{value}")
1476+
elif policyApplies:
1477+
self.logger.highlight(f"\t{policyApplies}")
1478+
self.logger.highlight("")
1479+
13991480
def bloodhound(self):
14001481
# Check which version is desired
14011482
use_bhce = self.config.getboolean("BloodHound-CE", "bhce_enabled", fallback=False)

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")

tests/e2e_commands.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --kerberoa
196196
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --trusted-for-delegation
197197
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --admin-count
198198
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --gmsa
199+
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --pso
199200
##### LDAP Modules
200201
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -L
201202
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M adcs
@@ -209,7 +210,6 @@ netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M maq
209210
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M subnets
210211
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M user-desc
211212
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M whoami
212-
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M pso
213213
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M dump-computers
214214
##### WINRM
215215
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex

0 commit comments

Comments
 (0)