diff --git a/plaso/parsers/text_plugins/syslog.py b/plaso/parsers/text_plugins/syslog.py index 2aca7542ad..dd9e37840f 100644 --- a/plaso/parsers/text_plugins/syslog.py +++ b/plaso/parsers/text_plugins/syslog.py @@ -155,15 +155,35 @@ class BaseSyslogTextPlugin(interface.TextPlugin): _CRON_MESSAGE = pyparsing.Group(_CRON_TASK_RUN).set_results_name("task_run") + # OpenSSH 9.8 split the server into a listener binary, sshd, and a per-session + # binary, sshd-session, which writes the authentication messages. + _SSHD_REPORTERS = frozenset(["sshd", "sshd-session"]) + _SSHD_AUTHENTICATION_METHOD = pyparsing.Keyword("password") | pyparsing.Keyword( "publickey" ) - _SSHD_FINGER_PRINT = pyparsing.Combine( - pyparsing.Literal("RSA ") + pyparsing.Word(":" + pyparsing.hexnums) + # A key fingerprint consists of the key type followed by the digest, where the + # digest is either the hash name and a base64 value, such as + # "ED25519 SHA256:5xyQ+PG1Z3CIiShclJ2iNya5TOdKDgE/HrOXr21IdOo", or the older + # colon separated hexadecimal form, such as "RSA 00:aa:bb:cc". The default of + # the sshd_config FingerprintHash option is sha256. + # Note that the hexadecimal form is matched first, since the hash name and + # base64 pattern would otherwise match "00:aa" of "00:aa:bb:cc" and leave the + # remainder of the value unparsed. + _SSHD_FINGER_PRINT = pyparsing.Regex( + r"[A-Za-z0-9-]+ " + r"(?:[0-9a-fA-F]{2}(?::[0-9a-fA-F]{2})+|[A-Za-z0-9]+:[A-Za-z0-9+/=]+)" ).set_results_name("fingerprint") - _SSH_USERNAME = pyparsing.Word(pyparsing.alphanums).set_results_name("username") + # A user name is determined by the text that precedes " from " since sshd + # logs the user name as provided by the client, which is not limited to the + # characters that useradd would accept. + _SSH_USERNAME = ( + pyparsing.SkipTo(pyparsing.Literal("from")) + .set_parse_action(lambda tokens: tokens[0].strip()) + .set_results_name("username") + ) _SSH_IP_ADDRESS = ( pyparsing.pyparsing_common.ipv4_address @@ -181,6 +201,7 @@ class BaseSyslogTextPlugin(interface.TextPlugin): + _SSH_IP_ADDRESS.set_results_name("ip_address") + pyparsing.Literal("port") + _SSH_PORT + + pyparsing.Optional(pyparsing.Literal("ssh2").set_results_name("protocol")) + pyparsing.StringEnd() ) @@ -548,7 +569,7 @@ def _ParseRecord(self, parser_mediator, key, structure): event_data = None if reporter == "CRON": event_data = self._ParseCronMessageBody(message_body) - elif reporter == "sshd": + elif reporter in self._SSHD_REPORTERS: event_data = self._ParseSshdMessageBody(message_body) if not event_data: @@ -833,7 +854,7 @@ def _ParseRecord(self, parser_mediator, key, structure): event_data = None if reporter == "CRON": event_data = self._ParseCronMessageBody(message_body) - elif reporter == "sshd": + elif reporter in self._SSHD_REPORTERS: event_data = self._ParseSshdMessageBody(message_body) if not event_data: diff --git a/test_data/syslog/syslog_sshd_session.log b/test_data/syslog/syslog_sshd_session.log new file mode 100644 index 0000000000..fd5c131a5c --- /dev/null +++ b/test_data/syslog/syslog_sshd_session.log @@ -0,0 +1,8 @@ +Aug 2 11:41:04 localhost sshd-session[2470]: Failed password for svc-backup from 192.168.1.62 port 60203 ssh2 +Aug 2 11:41:08 localhost sshd-session[2473]: Failed password for john.doe from 192.168.1.62 port 60204 ssh2 +Aug 2 11:42:13 localhost sshd-session[2600]: Accepted password for svc-backup from 192.168.1.62 port 60214 ssh2 +Aug 2 11:42:15 localhost sshd-session[2655]: Accepted password for john.doe from 192.168.1.62 port 60215 ssh2 +Aug 2 11:42:16 localhost sshd-session[2711]: Accepted password for test_user from 192.168.1.62 port 60217 ssh2 +Aug 2 11:42:18 localhost sshd-session[2789]: Received disconnect from 192.168.1.62 port 60218:11: disconnected by user +Aug 2 11:42:18 localhost sshd-session[2789]: Disconnected from user root 192.168.1.62 port 60218 +Aug 2 11:42:48 localhost sshd-session[2842]: Accepted publickey for root from 192.168.1.62 port 60220 ssh2: ED25519 SHA256:a79QfkCiaM8pEpw/wmP0Qfkl3ttsHxPlSKgqilMv9K8 diff --git a/tests/parsers/text_plugins/syslog.py b/tests/parsers/text_plugins/syslog.py index 4d45503566..090acab93f 100644 --- a/tests/parsers/text_plugins/syslog.py +++ b/tests/parsers/text_plugins/syslog.py @@ -622,6 +622,117 @@ def testProcessSshd(self): event_data = storage_writer.GetAttributeContainerByIndex("event_data", 4) self.CheckEventData(event_data, expected_event_values) + expected_event_values = { + "authentication_method": "password", + "data_type": "syslog:ssh:failed_connection", + "ip_address": "188.124.3.41", + "last_written_time": "0000-03-11T22:55:32", + "port": "32889", + "protocol": "ssh2", + "username": "root", + } + event_data = storage_writer.GetAttributeContainerByIndex("event_data", 5) + self.CheckEventData(event_data, expected_event_values) + + expected_event_values = { + "authentication_method": "publickey", + "data_type": "syslog:ssh:login", + "fingerprint": ("RSA SHA256:5xyQ+PG1Z3CIiShclJ2iNya5TOdKDgE/HrOXr21IdOo"), + "ip_address": "192.0.2.60", + "last_written_time": "0000-03-11T22:55:35", + "port": "59915", + "username": "fred", + } + event_data = storage_writer.GetAttributeContainerByIndex("event_data", 8) + self.CheckEventData(event_data, expected_event_values) + + def testProcessSshdSession(self): + """Tests the Process function with a sshd-session syslog file.""" + plugin = syslog.TraditionalSyslogTextPlugin() + storage_writer = self._ParseTextFileWithPlugin( + ["syslog", "syslog_sshd_session.log"], plugin + ) + number_of_event_data = storage_writer.GetNumberOfAttributeContainers( + "event_data" + ) + self.assertEqual(number_of_event_data, 8) + + number_of_warnings = storage_writer.GetNumberOfAttributeContainers( + "extraction_warning" + ) + self.assertEqual(number_of_warnings, 0) + + number_of_warnings = storage_writer.GetNumberOfAttributeContainers( + "recovery_warning" + ) + self.assertEqual(number_of_warnings, 0) + + expected_event_values = { + "authentication_method": "password", + "data_type": "syslog:ssh:failed_connection", + "ip_address": "192.168.1.62", + "last_written_time": "0000-08-02T11:41:04", + "port": "60203", + "protocol": "ssh2", + "reporter": "sshd-session", + "username": "svc-backup", + } + event_data = storage_writer.GetAttributeContainerByIndex("event_data", 0) + self.CheckEventData(event_data, expected_event_values) + + expected_event_values = { + "data_type": "syslog:ssh:failed_connection", + "last_written_time": "0000-08-02T11:41:08", + "reporter": "sshd-session", + "username": "john.doe", + } + event_data = storage_writer.GetAttributeContainerByIndex("event_data", 1) + self.CheckEventData(event_data, expected_event_values) + + expected_event_values = { + "data_type": "syslog:ssh:login", + "last_written_time": "0000-08-02T11:42:13", + "reporter": "sshd-session", + "username": "svc-backup", + } + event_data = storage_writer.GetAttributeContainerByIndex("event_data", 2) + self.CheckEventData(event_data, expected_event_values) + + expected_event_values = { + "data_type": "syslog:ssh:login", + "last_written_time": "0000-08-02T11:42:16", + "reporter": "sshd-session", + "username": "test_user", + } + event_data = storage_writer.GetAttributeContainerByIndex("event_data", 4) + self.CheckEventData(event_data, expected_event_values) + + # A message that the sshd structures do not define, which is retained as + # a syslog:line. + expected_event_values = { + "data_type": "syslog:line", + "last_written_time": "0000-08-02T11:42:18", + "reporter": "sshd-session", + } + event_data = storage_writer.GetAttributeContainerByIndex("event_data", 5) + self.CheckEventData(event_data, expected_event_values) + + expected_event_values = { + "authentication_method": "publickey", + "data_type": "syslog:ssh:login", + "fingerprint": ( + "ED25519 SHA256:a79QfkCiaM8pEpw/wmP0Qfkl3ttsHxPlSKgqilMv9K8" + ), + "ip_address": "192.168.1.62", + "last_written_time": "0000-08-02T11:42:48", + "port": "60220", + "protocol": "ssh2", + "reporter": "sshd-session", + "username": "root", + } + event_data = storage_writer.GetAttributeContainerByIndex("event_data", 7) + self.CheckEventData(event_data, expected_event_values) + if __name__ == "__main__": unittest.main()