def GetEntries(self, match, **unused_kwargs): """Extracts relevant user timestamp entries. Args: match: A dictionary containing keys extracted from PLIST_KEYS. Yields: EventObject objects extracted from the plist. """ root = '/' account = match['name'][0] uid = match['uid'][0] cocoa_zero = ( timelib.Timestamp.COCOA_TIME_TO_POSIX_BASE * timelib.Timestamp.MICRO_SECONDS_PER_SECOND) # INFO: binplist return a string with the Plist XML. for policy in match['passwordpolicyoptions']: xml_policy = ElementTree.fromstring(policy) for dict_elements in xml_policy.iterfind('dict'): key_values = [value.text for value in dict_elements.getchildren()] policy_dict = dict(zip(key_values[0::2], key_values[1::2])) if policy_dict.get('passwordLastSetTime', 0): timestamp = timelib.Timestamp.FromTimeString( policy_dict.get('passwordLastSetTime', '0')) if timestamp > cocoa_zero: # Extract the hash password information. # It is store in the attribure ShadowHasData which is # a binary plist data; However binplist only extract one # level of binary plist, then it returns this information # as a string. fake_file = interface.FakeFile(match['ShadowHashData'][0]) try: plist_file = binplist.BinaryPlist(file_obj=fake_file) top_level = plist_file.Parse() except binplist.FormatError: top_level = dict() salted_hash = top_level.get('SALTED-SHA512-PBKDF2', None) if salted_hash: password_hash = u'$ml${}${}${}'.format( salted_hash['iterations'], binascii.hexlify(salted_hash['salt']), binascii.hexlify(salted_hash['entropy'])) else: password_hash = u'N/A' description = u'Last time {} ({}) changed the password: {}'.format( account, uid, password_hash) yield plist_event.PlistTimeEvent( root, u'passwordLastSetTime', timestamp, description) if policy_dict.get('lastLoginTimestamp', 0): timestamp = timelib.Timestamp.FromTimeString( policy_dict.get('lastLoginTimestamp', '0')) description = u'Last login from {} ({})'.format( account, uid) if timestamp > cocoa_zero: yield plist_event.PlistTimeEvent( root, u'lastLoginTimestamp', timestamp, description) if policy_dict.get('failedLoginTimestamp', 0): timestamp = timelib.Timestamp.FromTimeString( policy_dict.get('failedLoginTimestamp', '0')) description = u'Last failed login from {} ({}) ({} times)'.format( account, uid, policy_dict['failedLoginCount']) if timestamp > cocoa_zero: yield plist_event.PlistTimeEvent( root, u'failedLoginTimestamp', timestamp, description)
def GetEntries(self, parser_mediator, match=None, **unused_kwargs): """Extracts relevant user timestamp entries. Args: parser_mediator: A parser mediator object (instance of ParserMediator). match: Optional dictionary containing keys extracted from PLIST_KEYS. The default is None. """ if u'name' not in match or u'uid' not in match: return account = match[u'name'][0] uid = match[u'uid'][0] cocoa_zero = (timelib.Timestamp.COCOA_TIME_TO_POSIX_BASE * timelib.Timestamp.MICRO_SECONDS_PER_SECOND) # INFO: binplist return a string with the Plist XML. for policy in match.get(u'passwordpolicyoptions', []): try: xml_policy = ElementTree.fromstring(policy) except (ElementTree.ParseError, LookupError) as exception: logging.error(( u'Unable to parse XML structure for an user policy, account: ' u'{0:s} and uid: {1!s}, with error: {2:s}').format( account, uid, exception)) continue for dict_elements in xml_policy.iterfind(u'dict'): key_values = [ value.text for value in dict_elements.getchildren() ] # Taking a list and converting it to a dict, using every other item # as the key and the other one as the value. policy_dict = dict(zip(key_values[0::2], key_values[1::2])) time_string = policy_dict.get(u'passwordLastSetTime', None) if time_string: try: timestamp = timelib.Timestamp.FromTimeString(time_string) except errors.TimestampError: parser_mediator.ProduceParseError( u'Unable to parse time string: {0:s}'.format( time_string)) timestamp = 0 shadow_hash_data = match.get(u'ShadowHashData', None) if timestamp > cocoa_zero and isinstance( shadow_hash_data, (list, tuple)): # Extract the hash password information. # It is store in the attribute ShadowHasData which is # a binary plist data; However binplist only extract one # level of binary plist, then it returns this information # as a string. # TODO: change this into a DataRange instead. For this we # need the file offset and size of the ShadowHashData value data. shadow_hash_data = shadow_hash_data[0] resolver_context = context.Context() fake_file = fake_file_io.FakeFile(resolver_context, shadow_hash_data) fake_file.open(path_spec=fake_path_spec.FakePathSpec( location=u'ShadowHashData')) try: plist_file = binplist.BinaryPlist(file_obj=fake_file) top_level = plist_file.Parse() except binplist.FormatError: top_level = dict() salted_hash = top_level.get(u'SALTED-SHA512-PBKDF2', None) if salted_hash: password_hash = u'$ml${0:d}${1:s}${2:s}'.format( salted_hash[u'iterations'], binascii.hexlify(salted_hash[u'salt']), binascii.hexlify(salted_hash[u'entropy'])) else: password_hash = u'N/A' description = ( u'Last time {0:s} ({1!s}) changed the password: {2!s}' ).format(account, uid, password_hash) event_object = plist_event.PlistTimeEvent( self._ROOT, u'passwordLastSetTime', timestamp, description) parser_mediator.ProduceEvent(event_object) time_string = policy_dict.get(u'lastLoginTimestamp', None) if time_string: try: timestamp = timelib.Timestamp.FromTimeString(time_string) except errors.TimestampError: parser_mediator.ProduceParseError( u'Unable to parse time string: {0:s}'.format( time_string)) timestamp = 0 description = u'Last login from {0:s} ({1!s})'.format( account, uid) if timestamp > cocoa_zero: event_object = plist_event.PlistTimeEvent( self._ROOT, u'lastLoginTimestamp', timestamp, description) parser_mediator.ProduceEvent(event_object) time_string = policy_dict.get(u'failedLoginTimestamp', None) if time_string: try: timestamp = timelib.Timestamp.FromTimeString(time_string) except errors.TimestampError: parser_mediator.ProduceParseError( u'Unable to parse time string: {0:s}'.format( time_string)) timestamp = 0 description = ( u'Last failed login from {0:s} ({1!s}) ({2!s} times)' ).format(account, uid, policy_dict.get(u'failedLoginCount', 0)) if timestamp > cocoa_zero: event_object = plist_event.PlistTimeEvent( self._ROOT, u'failedLoginTimestamp', timestamp, description) parser_mediator.ProduceEvent(event_object)
def GetEntries(self, parser_context, match=None, **unused_kwargs): """Extracts relevant user timestamp entries. Args: parser_context: A parser context object (instance of ParserContext). match: Optional dictionary containing keys extracted from PLIST_KEYS. The default is None. """ account = match['name'][0] uid = match['uid'][0] cocoa_zero = ( timelib.Timestamp.COCOA_TIME_TO_POSIX_BASE * timelib.Timestamp.MICRO_SECONDS_PER_SECOND) # INFO: binplist return a string with the Plist XML. for policy in match['passwordpolicyoptions']: xml_policy = ElementTree.fromstring(policy) for dict_elements in xml_policy.iterfind('dict'): key_values = [value.text for value in dict_elements.getchildren()] policy_dict = dict(zip(key_values[0::2], key_values[1::2])) if policy_dict.get('passwordLastSetTime', 0): timestamp = timelib.Timestamp.FromTimeString( policy_dict.get('passwordLastSetTime', '0')) if timestamp > cocoa_zero: # Extract the hash password information. # It is store in the attribure ShadowHasData which is # a binary plist data; However binplist only extract one # level of binary plist, then it returns this information # as a string. # TODO: change this into a DataRange instead. For this we # need the file offset and size of the ShadowHashData value data. resolver_context = context.Context() fake_file = fake_file_io.FakeFile( resolver_context, match['ShadowHashData'][0]) fake_file.open(path_spec=fake_path_spec.FakePathSpec( location=u'ShadowHashData')) try: plist_file = binplist.BinaryPlist(file_obj=fake_file) top_level = plist_file.Parse() except binplist.FormatError: top_level = dict() salted_hash = top_level.get('SALTED-SHA512-PBKDF2', None) if salted_hash: password_hash = u'$ml${0:d}${1:s}${2:s}'.format( salted_hash['iterations'], binascii.hexlify(salted_hash['salt']), binascii.hexlify(salted_hash['entropy'])) else: password_hash = u'N/A' description = ( u'Last time {0:s} ({1!s}) changed the password: {2!s}').format( account, uid, password_hash) event_object = plist_event.PlistTimeEvent( self._ROOT, u'passwordLastSetTime', timestamp, description) parser_context.ProduceEvent(event_object, plugin_name=self.NAME) if policy_dict.get('lastLoginTimestamp', 0): timestamp = timelib.Timestamp.FromTimeString( policy_dict.get('lastLoginTimestamp', '0')) description = u'Last login from {0:s} ({1!s})'.format(account, uid) if timestamp > cocoa_zero: event_object = plist_event.PlistTimeEvent( self._ROOT, u'lastLoginTimestamp', timestamp, description) parser_context.ProduceEvent(event_object, plugin_name=self.NAME) if policy_dict.get('failedLoginTimestamp', 0): timestamp = timelib.Timestamp.FromTimeString( policy_dict.get('failedLoginTimestamp', '0')) description = ( u'Last failed login from {0:s} ({1!s}) ({2!s} times)').format( account, uid, policy_dict['failedLoginCount']) if timestamp > cocoa_zero: event_object = plist_event.PlistTimeEvent( self._ROOT, u'failedLoginTimestamp', timestamp, description) parser_context.ProduceEvent(event_object, plugin_name=self.NAME)