Source code for plaso.parsers.esedb_plugins.user_access_logging

# -*- coding: utf-8 -*-
"""Parser for the Microsoft User Access Logging (UAL) ESE database.

User Access Logging (UAL) is present in Windows Server editions starting with
Window Server 2012.

Also see:
  https://www.crowdstrike.com/blog/user-access-logging-ual-overview/
"""

import ipaddress
import uuid

from dfvfs.resolver import resolver as path_spec_resolver
from dfvfs.path import factory as path_spec_factory

from plaso.containers import events
from plaso.parsers import esedb
from plaso.parsers.esedb_plugins import interface


[docs] class UserAccessLoggingClientsEventsData(events.EventData): """Windows User Access Logging CLIENTS table event data. Attributes: access_time (dfdatetime.DateTimeValues): last access date and time. authenticated_username (str): domain/user account name performing the access. client_name (str): client name, use unknown. insert_time (dfdatetime.DateTimeValues): date and time the entry was first inserted into the table. role_identifier (str): identifier of the service accessed. role_name (str): Name of the service accessed. source_ip_address (str): source IP address. tenant_identifier (str): unique identifier of a tenant client. total_accesses (int): Count of accesses for the year. """ DATA_TYPE = 'windows:user_access_logging:clients'
[docs] def __init__(self): """Initializes event data.""" super(UserAccessLoggingClientsEventsData, self).__init__( data_type=self.DATA_TYPE) self.access_time = None self.authenticated_username = None self.client_name = None self.insert_time = None self.role_identifier = None self.role_name = None self.source_ip_address = None self.tenant_identifier = None self.total_accesses = None
[docs] class UserAccessLoggingDNSEventData(events.EventData): """Windows User Access Logging DNS table event data. Attributes: hostname (str): hostname. ip_address (str): IP address. last_seen_time (dfdatetime.DateTimeValues): date and time the hostname to IP address mapping was last observed. """ DATA_TYPE = 'windows:user_access_logging:dns'
[docs] def __init__(self): """Initializes event data.""" super(UserAccessLoggingDNSEventData, self).__init__( data_type=self.DATA_TYPE) self.hostname = None self.ip_address = None self.last_seen_time = None
[docs] class UserAccessLoggingRoleAccessEventsData(events.EventData): """Windows User Access Logging ROLE_ACCESS table event data. Attributes: first_seen_time (dfdatetime.DateTimeValues): date and time the role was first observed to be used. last_seen_time (dfdatetime.DateTimeValues): date and time the role was last observed to be used. role_identifier (str): identifier of the role. role_name (str): name of the role. """ DATA_TYPE = 'windows:user_access_logging:role_access'
[docs] def __init__(self): """Initializes event data.""" super(UserAccessLoggingRoleAccessEventsData, self).__init__( data_type=self.DATA_TYPE) self.first_seen_time = None self.last_seen_time = None self.role_identifier = None self.role_name = None
[docs] class UserAccessLoggingSystemIdentityEventdata(events.EventData): """Windows User Access Logging SYSTEM_IDENTITY table event data. Attributes: creation_time (dfdatetime.DateTimeValues): date and time the system identity was created. operating_system_build (int): operating system build. system_dns_hostname (str): System hostname. system_domain_name (str): System domain name. """ DATA_TYPE = 'windows:user_access_logging:system_identity'
[docs] def __init__(self): """Initializes event data.""" super(UserAccessLoggingSystemIdentityEventdata, self).__init__( data_type=self.DATA_TYPE) self.creation_time = None self.operating_system_build = None self.system_dns_hostname = None self.system_domain_name = None
[docs] class UserAccessLoggingVirtualMachinesEventData(events.EventData): """Windows User Access Logging VIRTUALMACHINES table event data. Attributes: bios_identifier (str): BIOS identifier. creation_time (dfdatetime.DateTimeValues): date and time the virtual machine was created. last_active_time (dfdatetime.DateTimeValues): date and time the virtual machine was last observed to be active. serial_number (str): Serial number. vm_identifier (str): identifier of the virtual machine. """ DATA_TYPE = 'windows:user_access_logging:virtual_machines'
[docs] def __init__(self): """Initializes event data.""" super(UserAccessLoggingVirtualMachinesEventData, self).__init__( data_type=self.DATA_TYPE) self.bios_identifier = None self.creation_time = None self.last_active_time = None self.serial_number = None self.vm_identifier = None
[docs] class UserAccessLoggingESEDBPlugin(interface.ESEDBPlugin): """Parses Windows User Access Logging ESE database file.""" NAME = 'user_access_logging' DATA_FORMAT = 'Windows User Access Logging ESE database file' REQUIRED_TABLES = { 'CLIENTS': 'ParseClientsTable', 'DNS': 'ParseDNSTable', 'ROLE_ACCESS': 'ParseRoleAccessTable', 'VIRTUALMACHINES': 'ParseVirtualMachinesTable'} _CLIENTS_TABLE_VALUE_MAPPINGS = { 'Address': '_ConvertIPAddressValue', 'RoleGuid': '_ConvertGUIDToString', 'TenantId': '_ConvertGUIDToString'} _DNS_TABLE_VALUE_MAPPINGS = { 'Address': '_ConvertDNSAddressValue'} _VIRTUALMACHINES_TABLE_VALUE_MAPPINGS = { 'BIOSGuid': '_ConvertGUIDToString', 'VmGuid': '_ConvertGUIDToString'} _ROLE_ACCESS_TABLE_VALUE_MAPPINGS = { 'RoleGuid': '_ConvertGUIDToString'} _ROLE_IDS_TABLE_VALUE_MAPPINGS = { 'RoleGuid': '_ConvertGUIDToString'}
[docs] def __init__(self): """Initializes an UAL ESE database file parser plugin.""" super(UserAccessLoggingESEDBPlugin, self).__init__() self._role_mappings = {}
def _ConvertDNSAddressValue(self, value): """Converts the address column value of a DNS table into a string. Args: value (bytes): DNS address. Returns: str: string representation of the DNS address. """ return value.replace(b'\x00', b'').decode('utf-8') def _ConvertGUIDToString(self, value): """Converts a GUID into a string representation. Args: value (bytes): a little-endian GUID value. Returns: str: string representation of the GUID. """ guid_value = uuid.UUID(bytes_le=value) return '{{{0!s}}}'.format(guid_value).lower() def _ConvertIPAddressValue(self, value): """Converts bytes representation of an IP to a string. Args: value (bytes): IP address. Returns: str: string representation of the IP address. """ return str(ipaddress.ip_address(value))
[docs] def ParseClientsTable( self, parser_mediator, database=None, table=None, **unused_kwargs): """Parses a CLIENTS table. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfVFS. database (Optional[ESEDatabase]): ESE database. table (Optional[pyesedb.table]): table. Raises: ValueError: if the database or table value is missing. """ if database is None: raise ValueError('Missing database value.') if table is None: raise ValueError('Missing table value.') if not self._role_mappings: system_identity_file_entry = self._GetSystemIdentityDatabase( parser_mediator) if system_identity_file_entry: self._ProcessSystemInformationDatabase( parser_mediator, system_identity_file_entry) for record_index, esedb_record in enumerate(table.records): if parser_mediator.abort: break try: record_values = self._GetRecordValues( parser_mediator, table.name, record_index, esedb_record, value_mappings=self._CLIENTS_TABLE_VALUE_MAPPINGS) except (UnicodeDecodeError, ValueError): parser_mediator.ProduceExtractionWarning(( 'Unable to retrieve record values from record: {0:d} ' 'in table: {1:s}').format(record_index, table.name)) continue event_data = UserAccessLoggingClientsEventsData() event_data.access_time = self._GetFiletimeRecordValue( record_values, 'LastAccess') event_data.authenticated_username = record_values.get( 'AuthenticatedUserName', None) event_data.client_name = record_values.get('ClientName', None) event_data.role_identifier = record_values.get('RoleGuid', None) event_data.insert_time = self._GetFiletimeRecordValue( record_values, 'InsertDate') event_data.role_name = self._role_mappings.get( event_data.role_identifier, 'Unknown') event_data.source_ip_address = record_values.get('Address', None) event_data.tenant_identifier = record_values.get('TenantId', None) event_data.total_accesses = record_values.get('TotalAccesses', None) parser_mediator.ProduceEventData(event_data)
[docs] def ParseRoleAccessTable( self, parser_mediator, database=None, table=None, **unused_kwargs): """Parses a ROLE_ACCESS table. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfVFS. database (Optional[ESEDatabase]): ESE database. table (Optional[pyesedb.table]): table. Raises: ValueError: if the database or table value is missing. """ if database is None: raise ValueError('Missing database value.') if table is None: raise ValueError('Missing table value.') if not self._role_mappings: system_identity_file_object = self._GetSystemIdentityDatabase( parser_mediator) if system_identity_file_object: self._ProcessSystemInformationDatabase( parser_mediator, system_identity_file_object) for record_index, esedb_record in enumerate(table.records): if parser_mediator.abort: break try: record_values = self._GetRecordValues( parser_mediator, table.name, record_index, esedb_record, value_mappings=self._ROLE_ACCESS_TABLE_VALUE_MAPPINGS) except (UnicodeDecodeError, ValueError): parser_mediator.ProduceExtractionWarning(( 'Unable to retrieve record values from record: {0:d} ' 'in table: {1:s}').format(record_index, table.name)) continue event_data = UserAccessLoggingRoleAccessEventsData() event_data.first_seen_time = self._GetFiletimeRecordValue( record_values, 'FirstSeen') event_data.last_seen_time = self._GetFiletimeRecordValue( record_values, 'LastSeen') event_data.role_identifier = record_values.get('RoleGuid', None) event_data.role_name = self._role_mappings.get( event_data.role_identifier, 'Unknown') parser_mediator.ProduceEventData(event_data)
[docs] def ParseDNSTable( self, parser_mediator, database=None, table=None, **unused_kwargs): """Parses a DNS table. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfVFS. database (Optional[ESEDatabase]): ESE database. table (Optional[pyesedb.table]): table. Raises: ValueError: if the database or table value is missing. """ if database is None: raise ValueError('Missing database value.') if table is None: raise ValueError('Missing table value.') for record_index, esedb_record in enumerate(table.records): if parser_mediator.abort: break try: record_values = self._GetRecordValues( parser_mediator, table.name, record_index, esedb_record, value_mappings=self._DNS_TABLE_VALUE_MAPPINGS) except (UnicodeDecodeError, ValueError): parser_mediator.ProduceExtractionWarning(( 'Unable to retrieve record values from record: {0:d} ' 'in table: {1:s}').format(record_index, table.name)) continue event_data = UserAccessLoggingDNSEventData() event_data.hostname = record_values.get('HostName', None) event_data.ip_address = record_values.get('Address', None) event_data.last_seen_time = self._GetFiletimeRecordValue( record_values, 'LastSeen') parser_mediator.ProduceEventData(event_data)
[docs] def ParseVirtualMachinesTable( self, parser_mediator, database=None, table=None, **unused_kwargs): """Parses a VIRTUALMACHINES table. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfVFS. database (Optional[ESEDatabase]): ESE database. table (Optional[pyesedb.table]): table. Raises: ValueError: if the database or table value is missing. """ if database is None: raise ValueError('Missing database value.') if table is None: raise ValueError('Missing table value.') for record_index, esedb_record in enumerate(table.records): if parser_mediator.abort: break try: record_values = self._GetRecordValues( parser_mediator, table.name, record_index, esedb_record, value_mappings=self._VIRTUALMACHINES_TABLE_VALUE_MAPPINGS) except (UnicodeDecodeError, ValueError): parser_mediator.ProduceExtractionWarning(( 'Unable to retrieve record values from record: {0:d} ' 'in table: {1:s}').format(record_index, table.name)) continue event_data = UserAccessLoggingVirtualMachinesEventData() event_data.bios_identifier = record_values.get('BIOSGuid', None) event_data.creation_time = self._GetFiletimeRecordValue( record_values, 'CreationTime') event_data.last_active_time = self._GetFiletimeRecordValue( record_values, 'LastSeenActive') event_data.serial_number = record_values.get('SerialNumber', None) event_data.vm_identifier = record_values.get('VMGuid', None) parser_mediator.ProduceEventData(event_data)
def _GetSystemIdentityDatabase(self, parser_mediator): """Locate SystemIdentity.mdb. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfVFS. Returns: dfvfs.FileEntry: a file entry or None if the database cannot be located. """ file_entry = parser_mediator.GetFileEntry() file_system = file_entry.GetFileSystem() path_segments = file_system.SplitPath(file_entry.path_spec.location) path_segments.pop() path_segments.append('SystemIdentity.mdb') kwargs = {} if file_entry.path_spec.parent: kwargs['parent'] = file_entry.path_spec.parent kwargs['location'] = file_system.JoinPath(path_segments) system_identity_file_path_spec = path_spec_factory.Factory.NewPathSpec( file_entry.path_spec.TYPE_INDICATOR, **kwargs) system_identity_file_entry = None try: system_identity_file_entry = path_spec_resolver.Resolver.OpenFileEntry( system_identity_file_path_spec) except RuntimeError as exception: message = ( 'Unable to open SystemIdentity.mdb file: {0:s} with error: ' '{1!s}'.format(kwargs['location'], exception)) parser_mediator.ProduceExtractionWarning(message) return system_identity_file_entry def _ProcessSystemInformationDatabase(self, parser_mediator, file_entry): """Process SystemIdentity.mdb and extract Role GUID -> Role name mappings. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfVFS. file_entry (dfvfs.FileEntry): a file entry """ file_object = file_entry.GetFileObject() database = esedb.ESEDatabase() try: database.Open(file_object) except (IOError, ValueError) as exception: parser_mediator.ProduceExtractionWarning( 'unable to open SystemInformation.mdb with error: {0!s}'.format( exception)) return role_ids_table = database.GetTableByName('ROLE_IDS') if not role_ids_table: parser_mediator.ProduceExtractionWarning( 'unable to get ROLE_IDS table in SystemInformation.mdb') else: self._ParseRoleIDsTable( parser_mediator, database=database, table=role_ids_table) system_identity_table = database.GetTableByName('SYSTEM_IDENTITY') if not system_identity_table: parser_mediator.ProduceExtractionWarning( 'unable to get SYSTEM_IDENTITY table in SystemInformation.mdb') else: self._ParseSystemIdentityTable( parser_mediator, database=database, table=system_identity_table) database.Close() def _ParseRoleIDsTable( self, parser_mediator, database=None, table=None, **unused_kwargs): """Parses a SystemIdentity.mdb ROLE_IDS table. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfVFS. database (Optional[ESEDatabase]): ESE database. table (Optional[pyesedb.table]): table. Raises: ValueError: if the database or table value is missing. """ if database is None: raise ValueError('Missing database value.') if table is None: raise ValueError('Missing table value.') for record_index, esedb_record in enumerate(table.records): if parser_mediator.abort: break try: record_values = self._GetRecordValues( parser_mediator, table.name, record_index, esedb_record, value_mappings=self._ROLE_IDS_TABLE_VALUE_MAPPINGS) except (UnicodeDecodeError, ValueError): parser_mediator.ProduceExtractionWarning(( 'Unable to retrieve record values from record: {0:d} ' 'in table: {1:s}').format(record_index, table.name)) continue role_identifier = record_values.get('RoleGuid', None) role_name = record_values.get('RoleName', None) if role_identifier and role_name: self._role_mappings[role_identifier] = role_name def _ParseSystemIdentityTable( self, parser_mediator, database=None, table=None, **unused_kwargs): """Parses a SystemIdentity.mdb SYSTEM_IDENTITY table. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfVFS. database (Optional[ESEDatabase]): ESE database. table (Optional[pyesedb.table]): table. Raises: ValueError: if the database or table value is missing. """ if database is None: raise ValueError('Missing database value.') if table is None: raise ValueError('Missing table value.') for record_index, esedb_record in enumerate(table.records): if parser_mediator.abort: break try: record_values = self._GetRecordValues( parser_mediator, table.name, record_index, esedb_record) except (UnicodeDecodeError, ValueError): parser_mediator.ProduceExtractionWarning(( 'Unable to retrieve record values from record: {0:d} ' 'in table: {1:s}').format(record_index, table.name)) continue event_data = UserAccessLoggingSystemIdentityEventdata() event_data.creation_time = self._GetFiletimeRecordValue( record_values, 'CreationTime') event_data.operating_system_build = record_values.get( 'OSBuildNumber', None) event_data.system_dns_hostname = record_values.get( 'SystemDNSHostName', None) event_data.system_domain_name = record_values.get( 'SystemDomainName', None) parser_mediator.ProduceEventData(event_data)
esedb.ESEDBParser.RegisterPlugin(UserAccessLoggingESEDBPlugin)