From 9cb618c71819911b6c3aadf0a5b1448751a6ec1d Mon Sep 17 00:00:00 2001 From: Fupeng Zhao Date: Sun, 30 Jun 2024 15:59:13 +0800 Subject: [PATCH] Add support for parsing the "caching_sha2_password" auth plugin --- .../protocol/mysql/mysql-analyzer.pac | 6 +- .../protocol/mysql/mysql-protocol.pac | 97 +++++++++++++++++- .../mysql.log | 23 +++++ .../out | 39 +++++++ .../Traces/mysql/caching_sha2_password.trace | Bin 0 -> 7561 bytes .../mysql/caching_sha2_password.test | 35 +++++++ 6 files changed, 192 insertions(+), 8 deletions(-) create mode 100644 testing/btest/Baseline/scripts.base.protocols.mysql.caching_sha2_password/mysql.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.mysql.caching_sha2_password/out create mode 100644 testing/btest/Traces/mysql/caching_sha2_password.trace create mode 100644 testing/btest/scripts/base/protocols/mysql/caching_sha2_password.test diff --git a/src/analyzer/protocol/mysql/mysql-analyzer.pac b/src/analyzer/protocol/mysql/mysql-analyzer.pac index 31addd2518..ebc964a793 100644 --- a/src/analyzer/protocol/mysql/mysql-analyzer.pac +++ b/src/analyzer/protocol/mysql/mysql-analyzer.pac @@ -23,7 +23,7 @@ refine flow MySQL_Flow += { connection()->zeek_analyzer()->AnalyzerConfirmation(); // If the client requested SSL and didn't provide credentials, switch to SSL - if ( ${msg.version} == 10 && ( ${msg.v10_response.cap_flags} & CLIENT_SSL ) && ${msg.v10_response.credentials}->empty() ) + if ( ${msg.version} == 10 && ( ${msg.v10_response.cap_flags} & CLIENT_SSL )) { connection()->zeek_analyzer()->StartTLS(); return true; @@ -31,10 +31,10 @@ refine flow MySQL_Flow += { if ( mysql_handshake ) { - if ( ${msg.version} == 10 && ${msg.v10_response.credentials}->size() > 0 ) + if ( ${msg.version} == 10 ) zeek::BifEvent::enqueue_mysql_handshake(connection()->zeek_analyzer(), connection()->zeek_analyzer()->Conn(), - zeek::make_intrusive(c_str(${msg.v10_response.credentials[0].username}))); + zeek::make_intrusive(c_str(${msg.v10_response.plain.credentials.username}))); if ( ${msg.version} == 9 ) zeek::BifEvent::enqueue_mysql_handshake(connection()->zeek_analyzer(), connection()->zeek_analyzer()->Conn(), diff --git a/src/analyzer/protocol/mysql/mysql-protocol.pac b/src/analyzer/protocol/mysql/mysql-protocol.pac index e8415e3de0..ffc0c1fbc5 100644 --- a/src/analyzer/protocol/mysql/mysql-protocol.pac +++ b/src/analyzer/protocol/mysql/mysql-protocol.pac @@ -96,6 +96,11 @@ type LengthEncodedStringArg(first_byte: uint8) = record { }; %} +%code{ + const char* PLUGIN_CACHING_SHA2_PASSWORD = "caching_sha2_password"; +%} + +extern type PLUGIN_CACHING_SHA2_PASSWORD; extern type to_int; # Enums @@ -138,6 +143,9 @@ enum command_consts { enum state { CONNECTION_PHASE = 0, COMMAND_PHASE = 1, + SHA2_AUTH_PHASE = 2, + PUB_KEY_PHASE = 3, + SHA2_AUTH_RESP_PHASE = 4, }; enum Expected { @@ -158,12 +166,23 @@ enum EOFType { }; enum Client_Capabilities { + CLIENT_CONNECT_WITH_DB = 0x00000008, CLIENT_SSL = 0x00000800, + CLIENT_PLUGIN_AUTH = 0x00080000, + CLIENT_CONNECT_ATTRS = 0x00100000, # Expects an OK (instead of EOF) after the resultset rows of a Text Resultset. CLIENT_DEPRECATE_EOF = 0x01000000, + CLIENT_ZSTD_COMPRESSION_ALGORITHM = 0x04000000, +}; + +enum SHA2_Atuh_State { + REQUEST_PUBLIC_KEY = 2, + FAST_AUTH_SUCCESS = 3, + PERFORM_FULL_AUTHENTICATION = 4, }; type NUL_String = RE/[^\0]*\0/; +type EmptyOrNUL_String = RE/([^\0]*\0)?/; # MySQL PDU @@ -195,6 +214,9 @@ type Server_Message(seq_id: uint8, pkt_len: uint32) = case is_initial of { type Client_Message(state: int) = case state of { CONNECTION_PHASE -> connection_phase: Handshake_Response_Packet; COMMAND_PHASE -> command_phase : Command_Request_Packet; + SHA2_AUTH_PHASE -> sha2_auth_phase : SHA2_Auth_Packet; + PUB_KEY_PHASE -> pub_key_phase : Public_Key_Packet; + SHA2_AUTH_RESP_PHASE -> sha2_auth_resp_phase : SHA2_Auth_Response_Packet; }; # Handshake Request @@ -220,7 +242,12 @@ type Handshake_v10 = record { status_flags : uint16; capability_flags_2 : uint16; auth_plugin_data_len : uint8; - auth_plugin_name : NUL_String; + reserved : padding[10]; + auth_plugin_data_part_2: bytestring &length=13; + have_plugin : case ( ( capability_flags_2 << 4 ) & CLIENT_PLUGIN_AUTH ) of { + CLIENT_PLUGIN_AUTH -> auth_plugin_name: NUL_String; + 0x0 -> none : empty; + }; }; type Handshake_v9 = record { @@ -240,7 +267,40 @@ type Handshake_Response_Packet = case $context.connection.get_version() of { type Handshake_Credentials_v10 = record { username : NUL_String; - password : bytestring &restofdata; + password : LengthEncodedString; +}; + +type Connection_Attribute = record { + name : LengthEncodedString; + value : LengthEncodedString; +}; + +type Handshake_Connection_Attributes = record { + length : uint8; + attrs : Connection_Attribute[] &until($input.length() == 0); +} &length = length+1; + +type Handshake_Plain_v10(cap_flags: uint32) = record { + credentials: Handshake_Credentials_v10; + have_db : case ( cap_flags & CLIENT_CONNECT_WITH_DB ) of { + CLIENT_CONNECT_WITH_DB -> database: NUL_String; + 0x0 -> none_1 : empty; + }; + have_plugin : case ( cap_flags & CLIENT_PLUGIN_AUTH ) of { + CLIENT_PLUGIN_AUTH -> auth_plugin_name: EmptyOrNUL_String; + 0x0 -> none_2 : empty; + }; + have_attrs : case ( cap_flags & CLIENT_CONNECT_ATTRS ) of { + CLIENT_CONNECT_ATTRS -> conn_attrs: Handshake_Connection_Attributes; + 0x0 -> none_3 : empty; + }; + have_zstd : case ( cap_flags & CLIENT_ZSTD_COMPRESSION_ALGORITHM ) of { + CLIENT_ZSTD_COMPRESSION_ALGORITHM -> zstd_compression_level: uint8; + 0x0 -> none_4 : empty; + }; +} &let { + update_state: bool = $context.connection.update_state(SHA2_AUTH_PHASE) + &if(( cap_flags & CLIENT_PLUGIN_AUTH ) && auth_plugin_name==PLUGIN_CACHING_SHA2_PASSWORD); }; type Handshake_Response_Packet_v10 = record { @@ -248,7 +308,10 @@ type Handshake_Response_Packet_v10 = record { max_pkt_size: uint32; char_set : uint8; pad : padding[23]; - credentials : Handshake_Credentials_v10[] &until($input.length() == 0); + use_ssl : case ( cap_flags & CLIENT_SSL ) of { + CLIENT_SSL -> none : empty; + default -> plain: Handshake_Plain_v10(cap_flags); + }; } &let { deprecate_eof: bool = $context.connection.set_deprecate_eof(cap_flags & CLIENT_DEPRECATE_EOF); }; @@ -258,13 +321,37 @@ type Handshake_Response_Packet_v9 = record { max_pkt_size : uint24le; username : NUL_String; auth_response: NUL_String; - have_db : case ( cap_flags & 0x8 ) of { - 0x8 -> database: NUL_String; + have_db : case ( cap_flags & CLIENT_CONNECT_WITH_DB ) of { + CLIENT_CONNECT_WITH_DB -> database: NUL_String; 0x0 -> none : empty; }; password : bytestring &restofdata; }; +# SHA2 Auth + +type SHA2_Auth_Packet = record { + state: bytestring &restofdata; +} &let { + update_state_1: bool = $context.connection.update_state(COMMAND_PHASE) + &if(state[0] == FAST_AUTH_SUCCESS); + update_state_2: bool = $context.connection.update_state(PUB_KEY_PHASE) + &if(state[1] == REQUEST_PUBLIC_KEY); +}; + +type Public_Key_Packet = record { + pad : uint8; + pub_key: bytestring &restofdata; +} &let { + update_state: bool = $context.connection.update_state(SHA2_AUTH_RESP_PHASE); +}; + +type SHA2_Auth_Response_Packet = record { + data: bytestring &restofdata; +} &let { + update_state: bool = $context.connection.update_state(COMMAND_PHASE); +}; + # Command Request type Command_Request_Packet = record { diff --git a/testing/btest/Baseline/scripts.base.protocols.mysql.caching_sha2_password/mysql.log b/testing/btest/Baseline/scripts.base.protocols.mysql.caching_sha2_password/mysql.log new file mode 100644 index 0000000000..53fb4143f2 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.mysql.caching_sha2_password/mysql.log @@ -0,0 +1,23 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path mysql +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p cmd arg success rows response +#types time string addr port addr port string string bool count string +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 56494 127.0.0.1 3306 login root F - Got an error reading communication packets +XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h 127.0.0.1 49352 127.0.0.1 3306 login root T 0 - +XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h 127.0.0.1 49352 127.0.0.1 3306 query \x00\x01show databases T 0 - +XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h 127.0.0.1 49352 127.0.0.1 3306 query \x00\x01show tables T 0 - +XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h 127.0.0.1 49352 127.0.0.1 3306 field_list t T 0 - +XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h 127.0.0.1 49352 127.0.0.1 3306 query \x00\x01select @@version_comment limit 1 T 0 - +XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h 127.0.0.1 49352 127.0.0.1 3306 quit (empty) - - - +XXXXXXXXXX.XXXXXX C4J4Th3PJpwUYZZ6gc 127.0.0.1 40950 127.0.0.1 3306 login root T 0 - +XXXXXXXXXX.XXXXXX C4J4Th3PJpwUYZZ6gc 127.0.0.1 40950 127.0.0.1 3306 query \x00\x01show databases T 0 - +XXXXXXXXXX.XXXXXX C4J4Th3PJpwUYZZ6gc 127.0.0.1 40950 127.0.0.1 3306 query \x00\x01show tables T 0 - +XXXXXXXXXX.XXXXXX C4J4Th3PJpwUYZZ6gc 127.0.0.1 40950 127.0.0.1 3306 field_list t T 0 - +XXXXXXXXXX.XXXXXX C4J4Th3PJpwUYZZ6gc 127.0.0.1 40950 127.0.0.1 3306 query \x00\x01select @@version_comment limit 1 T 0 - +XXXXXXXXXX.XXXXXX C4J4Th3PJpwUYZZ6gc 127.0.0.1 40950 127.0.0.1 3306 quit (empty) - - - +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.mysql.caching_sha2_password/out b/testing/btest/Baseline/scripts.base.protocols.mysql.caching_sha2_password/out new file mode 100644 index 0000000000..6dd3801eba --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.mysql.caching_sha2_password/out @@ -0,0 +1,39 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +mysql handshake, root +mysql error, 1158, Got an error reading communication packets +mysql handshake, root +mysql ok, 0 +mysql request, 3, \x00\x01show databases +mysql result row, [information_schema] +mysql result row, [mysql] +mysql result row, [performance_schema] +mysql result row, [sys] +mysql result row, [test] +mysql ok, 0 +mysql request, 3, \x00\x01show tables +mysql result row, [t] +mysql ok, 0 +mysql request, 4, t\x00 +mysql ok, 0 +mysql request, 3, \x00\x01select @@version_comment limit 1 +mysql result row, [MySQL Community Server - GPL] +mysql ok, 0 +mysql request, 1, +mysql handshake, root +mysql ok, 0 +mysql request, 3, \x00\x01show databases +mysql result row, [information_schema] +mysql result row, [mysql] +mysql result row, [performance_schema] +mysql result row, [sys] +mysql result row, [test] +mysql ok, 0 +mysql request, 3, \x00\x01show tables +mysql result row, [t] +mysql ok, 0 +mysql request, 4, t\x00 +mysql ok, 0 +mysql request, 3, \x00\x01select @@version_comment limit 1 +mysql result row, [MySQL Community Server - GPL] +mysql ok, 0 +mysql request, 1, diff --git a/testing/btest/Traces/mysql/caching_sha2_password.trace b/testing/btest/Traces/mysql/caching_sha2_password.trace new file mode 100644 index 0000000000000000000000000000000000000000..184ed83254a02fee3dd5614307debefdc7faa93b GIT binary patch literal 7561 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 H5!L?z3qPk) literal 0 HcmV?d00001 diff --git a/testing/btest/scripts/base/protocols/mysql/caching_sha2_password.test b/testing/btest/scripts/base/protocols/mysql/caching_sha2_password.test new file mode 100644 index 0000000000..ba5c8a90b0 --- /dev/null +++ b/testing/btest/scripts/base/protocols/mysql/caching_sha2_password.test @@ -0,0 +1,35 @@ +# @TEST-EXEC: zeek -b -C -r $TRACES/mysql/caching_sha2_password.trace %INPUT >out +# @TEST-EXEC: btest-diff out +# @TEST-EXEC: btest-diff mysql.log + +@load base/protocols/mysql + +event mysql_ok(c: connection, affected_rows: count) + { + print "mysql ok", affected_rows; + } + +event mysql_eof(c: connection, is_intermediate: bool) + { + print "mysql eof", is_intermediate; + } + +event mysql_result_row(c: connection, row: string_vec) + { + print "mysql result row", row; + } + +event mysql_error(c: connection, code: count, msg: string) + { + print "mysql error", code, msg; + } + +event mysql_command_request(c: connection, command: count, arg: string) + { + print "mysql request", command, arg; + } + +event mysql_handshake(c: connection, username: string) + { + print "mysql handshake", username; + }