-
Notifications
You must be signed in to change notification settings - Fork 620
New module to list active user accounts with Password Never Expires #1036
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…nabled Signed-off-by: Daahtk <61582785+DaahtKing@users.noreply.github.com>
|
These are two differents things @NeffIsBack :
|
Thanks for pointing it out, i can't read apparently. Should have compared the bits that are queried. @DaahtKing please substitute the bits with the constants from impacket. E.g. see: Line 1001 in 69c87d2
|
NeffIsBack
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quick review :)
I think a formatted output such as --users has would be nice as well, see:
Lines 781 to 788 in 69c87d2
| self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<9}{'-Description-':<60}") | |
| for user in resp_parsed: | |
| pwd_last_set = user.get("pwdLastSet", "") | |
| if pwd_last_set: | |
| pwd_last_set = "<never>" if pwd_last_set == "0" else datetime.fromtimestamp(self.getUnixTime(int(pwd_last_set))).strftime("%Y-%m-%d %H:%M:%S") | |
| # We default attributes to blank strings if they don't exist in the dict | |
| self.logger.highlight(f"{user.get('sAMAccountName', ''):<30}{pwd_last_set:<20}{user.get('badPwdCount', ''):<9}{user.get('description', ''):<60}") |
| def ldap_time_to_datetime(self, ldap_time): | ||
| """Convert an LDAP timestamp to a datetime object.""" | ||
| if ldap_time == "0": | ||
| return "Never" | ||
| try: | ||
| epoch = datetime(1601, 1, 1) + timedelta(seconds=int(ldap_time) / 10000000) | ||
| return epoch.strftime("%Y-%m-%d %H:%M:%S") | ||
| except Exception: | ||
| return "Conversion Error" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use the connection.getUnixTime() function for conversion. E.g.:
Line 785 in 69c87d2
| pwd_last_set = "<never>" if pwd_last_set == "0" else datetime.fromtimestamp(self.getUnixTime(int(pwd_last_set))).strftime("%Y-%m-%d %H:%M:%S") |
| context.log.debug(f"Search Filter={search_filter}") | ||
|
|
||
| # Executing the LDAP query | ||
| resp = connection.ldap_connection.search( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use the internal connection.search function. Debug logging for attributes, error handling etc is also handled in there.
|
|
||
| if not resp: | ||
| context.log.display("No accounts found with Password Never Expires") | ||
| return True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just do a normal return, nothing checks if the module has executed "successfully" or not.
| for item in resp: | ||
| if "attributes" not in item: | ||
| continue | ||
|
|
||
| account_data = {} | ||
|
|
||
| for attribute in item["attributes"]: | ||
| attr_type = str(attribute["type"]) | ||
|
|
||
| if attr_type == "sAMAccountName": | ||
| account_data['username'] = str(attribute["vals"][0]) | ||
| elif attr_type == "distinguishedName": | ||
| account_data['dn'] = str(attribute["vals"][0]) | ||
| elif attr_type == "userAccountControl": | ||
| account_data['uac'] = int(attribute["vals"][0]) | ||
| elif attr_type == "whenCreated": | ||
| account_data['created'] = str(attribute["vals"][0]) | ||
| elif attr_type == "pwdLastSet" and attribute["vals"]: | ||
| pwd_last_set = str(attribute["vals"][0]) | ||
| account_data['pwdLastSet'] = self.ldap_time_to_datetime(pwd_last_set) | ||
| elif attr_type == "description" and attribute["vals"]: | ||
| account_data['description'] = str(attribute["vals"][0]) | ||
|
|
||
| if 'username' in account_data: | ||
| accounts.append(account_data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use the parse_result_attributes function.
| # Saving and displaying results | ||
| if accounts: | ||
| account_count = len(accounts) | ||
| filename = f"{NXC_PATH}/logs/{connection.domain}.pwd_never_expires.txt" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use the "NXC_PATH/modules/<module_name>" path
| return False | ||
|
|
||
| return True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These returns can be removed
| "(!(userAccountControl:1.2.840.113556.1.4.803:=2))" | ||
| "(userAccountControl:1.2.840.113556.1.4.803:=65536))") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use constants form impacket, e.g. "UF_ACCOUNTDISABLE"

New module to list active user accounts with Password Never Expires :