Skip to content

Commit 74ff6fa

Browse files
committed
Added --pass-pol option to LDAP protocol
1 parent beff3b1 commit 74ff6fa

2 files changed

Lines changed: 92 additions & 0 deletions

File tree

nxc/protocols/ldap.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,6 +1477,97 @@ def pso_mins(ldap_time):
14771477
self.logger.highlight(f"\t{policyApplies}")
14781478
self.logger.highlight("")
14791479

1480+
def pass_pol(self):
1481+
search_filter = "(objectClass=domainDNS)"
1482+
attributes = [
1483+
"minPwdLength",
1484+
"pwdHistoryLength",
1485+
"maxPwdAge",
1486+
"minPwdAge",
1487+
"lockoutThreshold",
1488+
"lockoutDuration",
1489+
"lockOutObservationWindow",
1490+
"forceLogoff",
1491+
"pwdProperties"
1492+
]
1493+
1494+
resp = self.search(search_filter, attributes, sizeLimit=0, baseDN=self.baseDN)
1495+
resp_parsed = parse_result_attributes(resp)
1496+
1497+
if not resp_parsed:
1498+
self.logger.fail("No domain password policy found!")
1499+
return
1500+
1501+
self.logger.highlight("Domain Password Policy:")
1502+
self.logger.highlight("")
1503+
1504+
for policy in resp_parsed:
1505+
# Helper function to convert LDAP time to human readable format
1506+
def ldap_time_to_days(ldap_time):
1507+
if not ldap_time or ldap_time == "0":
1508+
return "Never"
1509+
# LDAP time is in 100-nanosecond intervals, negative for time intervals
1510+
seconds = abs(int(ldap_time)) / 10000000
1511+
days = int(seconds / 86400) # 86400 seconds in a day
1512+
return f"{days} days"
1513+
1514+
def ldap_time_to_minutes(ldap_time):
1515+
if not ldap_time or ldap_time == "0":
1516+
return "Never"
1517+
# LDAP time is in 100-nanosecond intervals
1518+
seconds = abs(int(ldap_time)) / 10000000
1519+
minutes = int(seconds / 60)
1520+
return f"{minutes} minutes"
1521+
1522+
# Display password policy information
1523+
min_pwd_length = policy.get("minPwdLength", "Not set")
1524+
pwd_history_length = policy.get("pwdHistoryLength", "Not set")
1525+
max_pwd_age = ldap_time_to_days(policy.get("maxPwdAge", "0"))
1526+
min_pwd_age = ldap_time_to_days(policy.get("minPwdAge", "0"))
1527+
lockout_threshold = policy.get("lockoutThreshold", "Not set")
1528+
lockout_duration = ldap_time_to_minutes(policy.get("lockoutDuration", "0"))
1529+
lockout_observation_window = ldap_time_to_minutes(policy.get("lockOutObservationWindow", "0"))
1530+
force_logoff = ldap_time_to_minutes(policy.get("forceLogoff", "0"))
1531+
pwd_properties = policy.get("pwdProperties", "0")
1532+
1533+
self.logger.highlight(f"Minimum Password Length: {min_pwd_length}")
1534+
self.logger.highlight(f"Password History Length: {pwd_history_length}")
1535+
self.logger.highlight(f"Maximum Password Age: {max_pwd_age}")
1536+
self.logger.highlight(f"Minimum Password Age: {min_pwd_age}")
1537+
self.logger.highlight(f"Account Lockout Threshold: {lockout_threshold}")
1538+
self.logger.highlight(f"Account Lockout Duration: {lockout_duration}")
1539+
self.logger.highlight(f"Account Lockout Observation Window: {lockout_observation_window}")
1540+
self.logger.highlight(f"Force Logoff: {force_logoff}")
1541+
1542+
# Decode password properties flags
1543+
if pwd_properties and pwd_properties != "0":
1544+
pwd_props_int = int(pwd_properties)
1545+
properties = []
1546+
1547+
if pwd_props_int & 0x1:
1548+
properties.append("Password complexity enabled")
1549+
if pwd_props_int & 0x2:
1550+
properties.append("Store passwords using reversible encryption")
1551+
if pwd_props_int & 0x4:
1552+
properties.append("No anonymous password changes")
1553+
if pwd_props_int & 0x8:
1554+
properties.append("No clear change password")
1555+
if pwd_props_int & 0x10:
1556+
properties.append("Lockout admins")
1557+
if pwd_props_int & 0x20:
1558+
properties.append("Store password with weaker obfuscation")
1559+
if pwd_props_int & 0x40:
1560+
properties.append("Refuse password change")
1561+
1562+
if properties:
1563+
self.logger.highlight("Password Properties:")
1564+
for prop in properties:
1565+
self.logger.highlight(f" - {prop}")
1566+
else:
1567+
self.logger.highlight(f"Password Properties: {pwd_properties} (Unknown flags)")
1568+
else:
1569+
self.logger.highlight("Password Properties: None")
1570+
14801571
def bloodhound(self):
14811572
# Check which version is desired
14821573
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
@@ -30,6 +30,7 @@ def proto_args(parser, parents):
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")
3232
vgroup.add_argument("--pso", action="store_true", help="Get Fine Grained Password Policy/PSOs")
33+
vgroup.add_argument("--pass-pol", action="store_true", help="Dump password policy")
3334

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

0 commit comments

Comments
 (0)