Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 53 additions & 23 deletions plaso/parsers/text_plugins/selinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,17 @@ class SELinuxLogEventData(events.EventData):
architecture (str): CPU architecture (arch); the resolved name (e.g.
"x86_64") when the record is ENRICHED, otherwise the raw value.
audit_login_identifier (str): audit login identifier (auid), the login
user identifier that is retained across su and sudo.
user identifier that is retained across su and sudo, or None if unset.
audit_rule_keys (list[str]): keys (key) of the audit rule that triggered
the record, where a rule can have multiple keys.
audit_serial (int): audit serial number, used to correlate the records
that belong to a single audited event.
audit_session_identifier (str): audit session identifier (ses).
audit_session_identifier (str): audit session identifier (ses), or None
if unset.
audit_type (str): audit type.
executable (str): path of the executable (exe).
exit_code (str): exit status of the system call (exit).
exit_code (int): exit status of the system call (exit), where a negative
value represents an errno value.
file_mode (int): file mode (mode) of the file, which includes the file type
and the permissions, such as 0o100640 for a regular file that is
readable and writable by its owner and readable by its group.
Expand Down Expand Up @@ -72,7 +74,8 @@ class SELinuxLogEventData(events.EventData):
remote_hostname (str): source hostname (hostname) of a remote event.
security_context (str): security context (subj) of the process, such as a
SELinux or AppArmor label.
success (str): whether the system call succeeded (success).
success (bool): True if the system call was successful. The log format
represents this value as "yes" or "no".
system_call (str): system call (syscall).
terminal (str): controlling terminal (terminal) of the event.
user_identifier (str): user identifier (uid) of the process.
Expand Down Expand Up @@ -396,26 +399,45 @@ def _GetArguments(self, parser_mediator, values):

return " ".join(arguments) or None, corrupted

def _GetFileMode(self, parser_mediator, values):
"""Retrieves the file mode of a PATH record.
def _GetIdentifierValue(self, values, name):
"""Retrieves the value of an identifier field.

Args:
values (dict[str, str]): value per field name.
name (str): field name.

Returns:
str: identifier, or None if the field has no usable value or the
identifier is unset.
"""
value = self._GetStringValue(values, name)
if value == self._UNSET_NUMERIC_VALUE:
return None

return value

def _GetIntegerValue(self, parser_mediator, values, name, base):
"""Retrieves the value of a field as an integer.

Args:
parser_mediator (ParserMediator): mediates interactions between parsers
and other components, such as storage and dfVFS.
values (dict[str, str]): value per field name.
name (str): field name.
base (int): base of the numeric value.

Returns:
tuple[int, bool]: file mode, or None if the record has no file mode, and
tuple[int, bool]: value, or None if the field has no usable value, and
value to indicate the value was corrupted.
"""
file_mode = self._GetStringValue(values, "mode")
if file_mode is None:
value = self._GetStringValue(values, name)
if value is None:
return None, False

try:
return int(file_mode, 8), False
return int(value, base), False
except ValueError:
parser_mediator.ProduceWarning(f"invalid file mode: {file_mode:s}")
parser_mediator.ProduceWarning(f"unsupported {name:s} value: {value:s}")
return None, True

def _ParseRecord(self, parser_mediator, key, structure):
Expand Down Expand Up @@ -470,26 +492,16 @@ def _ParseRecord(self, parser_mediator, key, structure):

if values:
# Fields that are only stored at the top level of the message body.
event_data.audit_login_identifier = self._GetStringValue(
top_level_values, "auid"
)
event_data.audit_session_identifier = self._GetStringValue(
top_level_values, "ses"
)
event_data.executable = self._GetStringValue(top_level_values, "exe")
event_data.exit_code = self._GetStringValue(top_level_values, "exit")
event_data.group_identifier = self._GetStringValue(
top_level_values, "gid"
)
event_data.parent_process_identifier = self._GetStringValue(
top_level_values, "ppid"
)
event_data.pid = self._GetStringValue(top_level_values, "pid")
event_data.process_name = self._GetStringValue(top_level_values, "comm")
event_data.security_context = self._GetStringValue(
top_level_values, "subj"
)
event_data.success = self._GetStringValue(top_level_values, "success")
event_data.system_call = self._GetStringValue(
top_level_values, "syscall"
)
Expand All @@ -498,6 +510,12 @@ def _ParseRecord(self, parser_mediator, key, structure):
)

event_data.architecture = self._GetStringValue(values, "arch")
event_data.audit_login_identifier = self._GetIdentifierValue(
values, "auid"
)
event_data.audit_session_identifier = self._GetIdentifierValue(
values, "ses"
)
event_data.name_type = self._GetStringValue(values, "nametype")
event_data.operation = self._GetStringValue(values, "op")
event_data.owner_group_identifier = self._GetStringValue(values, "ogid")
Expand All @@ -516,8 +534,18 @@ def _ParseRecord(self, parser_mediator, key, structure):
)
corrupted = corrupted or value_corrupted

event_data.file_mode, value_corrupted = self._GetFileMode(
parser_mediator, values
event_data.success, value_corrupted = self._GetResultValue(
parser_mediator, values, "success"
)
corrupted = corrupted or value_corrupted

event_data.exit_code, value_corrupted = self._GetIntegerValue(
parser_mediator, values, "exit", 10
)
corrupted = corrupted or value_corrupted

event_data.file_mode, value_corrupted = self._GetIntegerValue(
parser_mediator, values, "mode", 8
)
corrupted = corrupted or value_corrupted

Expand All @@ -528,7 +556,9 @@ def _ParseRecord(self, parser_mediator, key, structure):

for attribute_name, field_name in (
("account", "acct"),
("executable", "exe"),
("file_path", "name"),
("process_name", "comm"),
("working_directory", "cwd"),
):
value, value_corrupted = self._GetEncodedStringValue(
Expand Down
1 change: 1 addition & 0 deletions test_data/audit_enriched.log
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ type=SYSCALL msg=audit(1785071063.938:371): arch=c000003e syscall=90 success=yes
type=CWD msg=audit(1785071063.938:371): cwd="/home/ubuntu"
type=PATH msg=audit(1785071063.938:371): item=0 name="/tmp/keytest" inode=23 dev=00:29 mode=0100664 ouid=1000 ogid=1000 rdev=00:00 nametype=NORMAL cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0OUID="ubuntu" OGID="ubuntu"
type=PROCTITLE msg=audit(1785071063.938:371): proctitle=63686D6F6400363434002F746D702F6B657974657374
type=SERVICE_START msg=audit(1785232597.053:260): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=unconfined msg='unit=ssh comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'UID="root" AUID="unset"
33 changes: 26 additions & 7 deletions tests/parsers/text_plugins/selinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,13 @@ def testProcess(self):
"audit_type": "SYSCALL",
"data_type": "selinux:line",
"executable": "/bin/ls",
"exit_code": "0",
"exit_code": 0,
"group_identifier": "0",
"parent_process_identifier": "2671",
"pid": "2714",
"process_name": "ls",
"security_context": "system_u:object_r:unlabeled_t:s0",
"success": "yes",
"success": True,
"system_call": "197",
"user_identifier": "0",
}
Expand All @@ -144,7 +144,7 @@ def testProcessEnriched(self):
number_of_event_data = storage_writer.GetNumberOfAttributeContainers(
"event_data"
)
self.assertEqual(number_of_event_data, 33)
self.assertEqual(number_of_event_data, 34)

# A SYSCALL execve record (serial 485): the raw "syscall=59" is surfaced as
# the ENRICHED "SYSCALL=execve" name, and the 0x1d suffix is split off.
Expand All @@ -155,15 +155,15 @@ def testProcessEnriched(self):
"audit_type": "SYSCALL",
"data_type": "selinux:line",
"executable": "/usr/bin/id",
"exit_code": "0",
"exit_code": 0,
"group_identifier": "0",
"parent_process_identifier": "2176",
"pid": "2219",
"process_name": "id",
"security_context": (
"unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023"
),
"success": "yes",
"success": True,
"system_call": "execve",
"user_identifier": "0",
}
Expand Down Expand Up @@ -193,6 +193,20 @@ def testProcessEnriched(self):
event_data = self._FindEventDataByTypeAndSerial(storage_writer, "SYSCALL", 485)
self.CheckEventData(event_data, expected_event_values)

# SERVICE_START (serial 260): the executable and the process name of a
# service record are stored in the nested msg field instead of at the top
# level of the message body.
expected_event_values = {
"audit_type": "SERVICE_START",
"executable": "/usr/lib/systemd/systemd",
"process_name": "systemd",
"operation_result": True,
}
event_data = self._FindEventDataByTypeAndSerial(
storage_writer, "SERVICE_START", 260
)
self.CheckEventData(event_data, expected_event_values)

# SYSCALL (serial 371): an audit rule with multiple keys stores the keys
# in a single hex-encoded field, separated by AUDIT_KEY_SEPARATOR.
expected_event_values = {
Expand Down Expand Up @@ -222,9 +236,12 @@ def testProcessEnriched(self):
event_data = self._FindEventDataByTypeAndSerial(storage_writer, "EXECVE", 487)
self.CheckEventData(event_data, expected_event_values)

# USER_AUTH (serial 441): a failed remote pubkey auth from addr.
# USER_AUTH (serial 441): a failed remote pubkey auth from addr. The
# audit login and session identifiers are unset on this record.
expected_event_values = {
"audit_type": "USER_AUTH",
"audit_login_identifier": None,
"audit_session_identifier": None,
"operation": "pubkey",
"operation_result": False,
"remote_address": "172.23.112.1",
Expand Down Expand Up @@ -319,10 +336,12 @@ def testProcessAudit(self):
self.CheckEventData(event_data, expected_event_values)

# USER_AUTH (serial 520): nested msg='…' fields. terminal/addr/hostname
# are the "?" sentinel here and map to None.
# are the "?" sentinel here and map to None. The executable of a user
# record is stored in the nested msg field instead of at the top level.
expected_event_values = {
"audit_type": "USER_AUTH",
"account": "specimenuser",
"executable": "/usr/bin/su",
"operation": "PAM:authentication",
"operation_result": True,
"terminal": None,
Expand Down
Loading