View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0003821 | Kali Linux | Kali Package Bug | public | 2017-01-06 18:47 | 2017-12-05 13:37 |
Reporter | TheNaterz | Assigned To | |||
Priority | normal | Severity | tweak | Reproducibility | always |
Status | resolved | Resolution | fixed | ||
Product Version | 2016.2 | ||||
Fixed in Version | 2018.1 | ||||
Summary | 0003821: polenum-0.2 inaccurately reports time values associated with password policies | ||||
Description | polenum is a python script used by enum4linux to report password policy information. We identified and fixed 2 issues related to this report. In the first issue, polenum was using the 'days' variable for 1 hour and 1 minute values. These have been changed to the 'hours' and 'minutes' variables respectively. Polenum was also not correctly concatenating the time string. If a duration is set to 1 day 1 hour 1 minutes (1501 minutes), polenum will report it as 1 minute. This has been fixed as well. The second issue deals with how polenum was evaluating the 8-byte integer representing time values. Polenum was converting this number into 2 4-byte integers, determining if a value is 'Not Set' or 'None', then converting back into an 8-byte integer. If an account lockout period is set to a non-standard value (e.g. 61 minutes), the final 8-byte integer differs from the original and incorrectly reports the lockout period. We've submitted a fix for this so that the original 8-byte integer is preserved while still allowing for the ability to evaluate 'Not Set' and 'None' values. | ||||
Steps To Reproduce | 1st issue:
2nd issue:
| ||||
Additional Information | We're currently hosting a patched version of polenum on our public Github: https://github.com/RiskSense-Ops/polenum. I've also attached the patched polenum.py script. Please review and let us know if any further issues are encountered. | ||||
Attached Files | polenum.py (12,053 bytes)
#!/usr/bin/python """ This Python Script Uses Core's Impacket Library to get the password policy from a windows machine Testing has been limited so Let me know if it works/fails Version 0.2 Usage:./polenum.py [username[:password]@]<address> [protocol list...] Available protocols: ['445/SMB', '139/SMB'] example: polenum aaa:[email protected] Copyright (C) 20/08/2008 - deanx <RID[at]portcullis-secuirty.com> Version 0.2 * "This product includes software developed by * CORE Security Technologies (http://www.coresecurity.com/)." """ import socket import string import sys import types import time from impacket import uuid from impacket.dcerpc import dcerpc_v4, dcerpc, transport, samr from impacket import ImpactPacket from impacket.dcerpc.samr import * from impacket.smb import SessionError def get_obj(name): return eval(name) version = '0.2' def usage(): # Self explanitory print __doc__ def d2b(a): bin = [] while a: bin.append(a%2) a /= 2 return bin[::-1] class ExtendInplace(type): def __new__(self, name, bases, dict): prevclass = get_obj(name) del dict['__module__'] del dict['__metaclass__'] # We can't use prevclass.__dict__.update since __dict__ # isn't a real dict for k,v in dict.iteritems(): setattr(prevclass, k, v) return prevclass def display_time(filetime_high, filetime_low, minutes_utc=0): import __builtins__ d = filetime_low + (filetime_high)*16**8 # convert to 64bit int d *= 1.0e-7 # convert to seconds d -= 11644473600 # remove 3389 years? try: return strftime("%a, %d %b %Y %H:%M:%S +0000ddddd", localtime(d)) # return the standard format day except ValueError,e: return "0" def convert(value): low, high = unpack('<ll', pack("<q", value)) if low == 0 and hex(high) == "-0x80000000": return "Not Set" if low == 0 and high == 0: return "None" tmp = int(1e-7*-value) try: minutes = int(strftime("%M", gmtime(tmp))) # do the conversion to human readable format except ValueError, e: return "BAD TIME:" print(tmp) hours = int(strftime("%H", gmtime(tmp))) print(hours) days = int(strftime("%j", gmtime(tmp)))-1 time = "" if days > 1: time += str(days) + " days " elif days == 1: time += str(days) + " day " if hours > 1: time += str(hours) + " hours " elif hours == 1: time += str(hours) + " hour " if minutes > 1: time += str(minutes) + " minutes" elif minutes == 1: time += str(minutes) + " minute " return time class MSRPCPassInfo: ITEMS = {'Minimum password':0, 'Password history':1, 'Maximum password age (d)':2, 'Password must meet complexity requirements':3, 'Minimum password age (d)':4, 'Forced logoff time (s)':5, 'Locked account time (s)':6, 'Time between failed logon (s)':7, 'Number of invalid logon before locked out (s)':8 } PASSCOMPLEX = { 5:'Domain Password Complex:', 4:'Domain Password No Anon Change:', 3:'Domain Password No Clear Change:', 2:'Domain Password Lockout Admins:', 1:'Domain Password Store Cleartext:', 0:'Domain Refuse Password Change:' } def __init__(self, data = None): self._min_pass_length = 0 self._pass_hist = 0 self._pass_prop= 0 self._min_age_low = 0 self._min_age_high = 0 self._max_age_low = 0 self._max_age_high = 0 self._pwd_can_change_low = 0 self._pwd_can_change_high = 0 self._pwd_must_change_low = 0 self._pwd_must_change_high = 0 self._max_force_low = 0 self._max_force_high = 0 self._role = 0 self._lockout_window_low = 0 self._lockout_window_high = 0 self._lockout_dur_low = 0 self._lockout_dur_high = 0 self._lockout_thresh = 0 if data: self.set_header(data, 1) def set_header(self,data,level): index = 8 if level == 1: self._min_pass_length, self._pass_hist, self._pass_prop, self._max_age, self._min_age = unpack('<HHLqq',data[index:index+24]) bin = d2b(self._pass_prop) if len(bin) != 8: for x in xrange(6 - len(bin)): bin.insert(0,0) self._pass_prop = ''.join([str(g) for g in bin]) if level == 3: self._max_force = unpack('<q',data[index:index+8])[0] if level == 7: self._role = unpack('<L',data[index:index+4]) if level == 12: self._lockout_dur, self._lockout_window, self._lockout_thresh = unpack('<qqH',data[index:index+18]) def print_friendly(self): print "\n\t[+] Minimum password length: " + str(self._min_pass_length or "None") print "\t[+] Password history length: " + str(self._pass_hist or "None" ) print "\t[+] Maximum password age: " + str(convert(self._max_age)) print "\t[+] Password Complexity Flags: " + str(self._pass_prop or "None") + "\n" i = 0 for a in self._pass_prop: #print "BIT " +str(i) + a print "\t\t[+] " + self.PASSCOMPLEX[i] + " " + str(a) i+= 1 print "\n\t[+] Minimum password age: " + str(convert(self._min_age)) print "\t[+] Reset Account Lockout Counter: " + str(convert(self._lockout_window)) print "\t[+] Locked Account Duration: " + str(convert(self._lockout_dur)) print "\t[+] Account Lockout Threshold: " + str(self._lockout_thresh or "None") #print "Server Role: " + str(self._role[0]) print "\t[+] Forced Log off Time: " + str(convert(self._max_force)) return class SAMREnumDomainsPass(ImpactPacket.Header): OP_NUM = 0x2E __SIZE = 22 def __init__(self, aBuffer = None): ImpactPacket.Header.__init__(self, SAMREnumDomainsPass.__SIZE) if aBuffer: self.load_header(aBuffer) def get_context_handle(self): return self.get_bytes().tolist()[:20] def set_context_handle(self, handle): assert 20 == len(handle) self.get_bytes()[:20] = array.array('B', handle) def get_resume_handle(self): return self.get_long(20, '<') def set_resume_handle(self, handle): self.set_long(20, handle, '<') def get_account_control(self): return self.get_long(20, '<') def set_account_control(self, mask): self.set_long(20, mask, '<') def get_pref_max_size(self): return self.get_long(28, '<') def set_pref_max_size(self, size): self.set_long(28, size, '<') def get_header_size(self): return SAMREnumDomainsPass.__SIZE def get_level(self): return self.get_word(20, '<') def set_level(self, level): self.set_word(20, level, '<') class SAMRRespLookupPassPolicy(ImpactPacket.Header): __SIZE = 4 def __init__(self, aBuffer = None): ImpactPacket.Header.__init__(self, SAMRRespLookupPassPolicy.__SIZE) if aBuffer: self.load_header(aBuffer) def get_pass_info(self): return MSRPCPassInfo(self.get_bytes()[:-4].tostring()) def set_pass_info(self, info, level): assert isinstance(info, MSRPCPassInfo) self.get_bytes()[:-4] = array.array('B', info.rawData()) def get_return_code(self): return self.get_long(-4, '<') def set_return_code(self, code): self.set_long(-4, code, '<') def get_context_handle(self): return self.get_bytes().tolist()[:12] def get_header_size(self): var_size = len(self.get_bytes()) - SAMRRespLookupPassPolicy.__SIZE assert var_size > 0 return SAMRRespLookupPassPolicy.__SIZE + var_size class DCERPCSamr: __metaclass__=ExtendInplace def enumPass(self,context_handle): # needs to make 3 requests to get all pass policy enumpas = SAMREnumDomainsPass() enumpas.set_context_handle(context_handle) enumpas.set_level(1) self._dcerpc.send(enumpas) data = self._dcerpc.recv() retVal = SAMRRespLookupPassPolicy(data) pspol = retVal.get_pass_info() enumpas = SAMREnumDomainsPass() enumpas.set_context_handle(context_handle) enumpas.set_level(3) self._dcerpc.send(enumpas) data = self._dcerpc.recv() pspol.set_header(data,3) enumpas = SAMREnumDomainsPass() enumpas.set_context_handle(context_handle) enumpas.set_level(7) self._dcerpc.send(enumpas) data = self._dcerpc.recv() pspol.set_header(data,7) enumpas = SAMREnumDomainsPass() enumpas.set_context_handle(context_handle) enumpas.set_level(12) self._dcerpc.send(enumpas) data = self._dcerpc.recv() pspol.set_header(data,12) #return retVal return pspol def opendomain(self,context_handle,domain_sid): opendom = SAMROpenDomainHeader() opendom.set_access_mask(0x305) opendom.set_context_handle(context_handle) opendom.set_domain_sid(domain_sid) self._dcerpc.send(opendom) data = self._dcerpc.recv() retVal = SAMRRespOpenDomainHeader(data) return retVal class ListUsersException(Exception): pass class SAMRDump: KNOWN_PROTOCOLS = { '139/SMB': (r'ncacn_np:%s[\pipe\samr]', 139), '445/SMB': (r'ncacn_np:%s[\pipe\samr]', 445), } def __init__(self, protocols = None, username = '', password = ''): if not protocols: protocols = SAMRDump.KNOWN_PROTOCOLS.keys() self.__username = username self.__password = password self.__protocols = protocols def dump(self, addr): """Dumps the list of users and shares registered present at addr. Addr is a valid host name or IP address. """ encoding = sys.getdefaultencoding() print if (self.__username and self.__password): print '[+] Attaching to ' + addr + ' using ' + self.__username + ":" + self.__password elif (self.__username): print '[+] Attaching to ' + addr + ' using ' + self.__username else: print '[+] Attaching to ' + addr + ' using a NULL share' # Try all requested protocols until one works. entries = [] for protocol in self.__protocols: try: protodef = SAMRDump.KNOWN_PROTOCOLS[protocol] port = protodef[1] except KeyError,e: print "\n\t[!] Invalid Protocol \'%s\'\n" % protocol usage() sys.exit(1) print "\n\t[+] Trying protocol %s..." % protocol rpctransport = transport.SMBTransport(addr, port, r'\samr', self.__username, self.__password) try: entries = self.__fetchList(rpctransport) except Exception, e: print '\n\t[!] Protocol failed: %s' % e #raise else: # Got a response. No need for further iterations. break def __fetchList(self, rpctransport): dce = dcerpc.DCERPC_v5(rpctransport) #dce.set_auth_level(2) encoding = sys.getdefaultencoding() entries = [] try: dce.connect() #sys.exit() dce.bind(samr.MSRPC_UUID_SAMR) #sys.exit() rpcsamr = samr.DCERPCSamr(dce) resp = rpcsamr.connect() if resp.get_return_code() != 0: raise ListUsersException, 'Connect error' _context_handle = resp.get_context_handle() resp = rpcsamr.enumdomains(_context_handle) if resp.get_return_code() != 0: raise ListUsersException, 'EnumDomain error' domains = resp.get_domains().elements() print '\n[+] Found domain(s):\n' for i in range(0, resp.get_entries_num()): print "\t[+] %s" % domains[i].get_name() print "\n[+] Password Info for Domain: %s" % domains[0].get_name() resp = rpcsamr.lookupdomain(_context_handle, domains[0]) if resp.get_return_code() != 0: raise ListUsersException, 'LookupDomain error' resp = rpcsamr.opendomain(_context_handle, resp.get_domain_sid()) if resp.get_return_code() != 0: raise ListUsersException, 'OpenDomain error' domain_context_handle = resp.get_context_handle() resp = rpcsamr.enumPass(domain_context_handle) resp.print_friendly() except ListUsersException, e: print "Error Getting Password Policy: %s" % e dce.disconnect() return entries __doc__ = '\n polenum ' + version + ' - (C) 2008 deanx\n\n' __doc__ += '\t\t\t RID[at]Portcullis-Security.com\n\n' __doc__ += ' Usage:' + sys.argv[0] + ' [username[:password]@]<address> [protocol list...]' __doc__ += '\n\n\t\tAvailable protocols: ' + str(SAMRDump.KNOWN_PROTOCOLS.keys()) + '\n' # Process command-line arguments. if __name__ == '__main__': if len(sys.argv) <= 1: usage() sys.exit(1) import re username, password, address = re.compile('(?:([^@:]*)(?::([^@]*))?@)?(.*)').match(sys.argv[1]).groups('') if len(sys.argv) > 2: dumper = SAMRDump(sys.argv[2:], username, password) else: dumper = SAMRDump(username = username, password = password) try: dumper.dump(address) print except KeyboardInterrupt: print print "\n\t[!] Ctrl-C Caught, ByeBye\n" sys.exit(2) | ||||
Actually, a much more decent polenum can be found here: https://github.com/Wh1t3Fox/polenum Notably, this version is more regularly maintained and also supports the latest impacket dcerpc v5 library. |
|
fixed in new version 1.4-0kali1 |
|
Date Modified | Username | Field | Change |
---|---|---|---|
2017-01-06 18:47 | TheNaterz | New Issue | |
2017-01-06 18:47 | TheNaterz | File Added: polenum.py | |
2017-01-13 18:35 | TheNaterz | Note Added: 0006245 | |
2017-12-05 13:36 | sbrun | Note Added: 0007664 | |
2017-12-05 13:37 | sbrun | Status | new => resolved |
2017-12-05 13:37 | sbrun | Resolution | open => fixed |
2017-12-05 13:37 | sbrun | Fixed in Version | => 2018.1 |