diff --git a/src/analyzer/protocol/mysql/events.bif b/src/analyzer/protocol/mysql/events.bif index ec5fa61ae6..68ff0e5720 100644 --- a/src/analyzer/protocol/mysql/events.bif +++ b/src/analyzer/protocol/mysql/events.bif @@ -87,3 +87,39 @@ event mysql_server_version%(c: connection, ver: string%); ## .. zeek:see:: mysql_command_request mysql_error mysql_ok mysql_server_version event mysql_handshake%(c: connection, username: string%); +## Generated for information about plugin authentication within handshake packets. +## +## c: The connection. +## +## is_orig: True if this is from the client, false if from the server. +## +## name: Name of the authentication plugin. +## +## .. zeek:see:: mysql_handshake mysql_auth_switch_request mysql_auth_more_data +event mysql_auth_plugin%(c: connection, is_orig: bool, name: string%); + +## Generated for a server packet with an auth switch request. +## +## c: The connection. +## +## name: The plugin name. +## +## data: Initial authentication data for the plugin. +## +## .. zeek:see:: mysql_handshake mysql_auth_more_data +event mysql_auth_switch_request%(c: connection, name: string, data: string%); + +## Generated for opaque authentication data exchanged between client and server +## after the client's handshake packet, but before the server replied with +## an OK_Packet +## +## Data is specific to the plugin auth mechanism used by client and server. +## +## c: The connection. +## +## is_orig: True if this is from the client, false if from the server. +## +## data: More authentication data. +## +## .. zeek:see:: mysql_handshake mysql_auth_switch_request +event mysql_auth_more_data%(c: connection, is_orig: bool, data: string%); diff --git a/src/analyzer/protocol/mysql/mysql-analyzer.pac b/src/analyzer/protocol/mysql/mysql-analyzer.pac index 5f6782c4ff..738e063fbb 100644 --- a/src/analyzer/protocol/mysql/mysql-analyzer.pac +++ b/src/analyzer/protocol/mysql/mysql-analyzer.pac @@ -14,6 +14,17 @@ refine flow MySQL_Flow += { connection()->zeek_analyzer()->Conn(), zeek::make_intrusive(c_str(${msg.handshake9.server_version}))); } + + if ( mysql_auth_plugin ) + { + if ( ${msg.version} == 10 && (${msg.handshake10.capability_flags_2} << 16) & CLIENT_PLUGIN_AUTH ) + { + zeek::BifEvent::enqueue_mysql_auth_plugin(connection()->zeek_analyzer(), + connection()->zeek_analyzer()->Conn(), + false /*is_orig*/, + zeek::make_intrusive(c_str(${msg.handshake10.auth_plugin}))); + } + } return true; %} @@ -40,6 +51,18 @@ refine flow MySQL_Flow += { connection()->zeek_analyzer()->Conn(), zeek::make_intrusive(c_str(${msg.v9_response.username}))); } + + if ( mysql_auth_plugin ) + { + if ( ${msg.version} == 10 && ${msg.v10_response.plain.cap_flags} & CLIENT_PLUGIN_AUTH ) + { + zeek::BifEvent::enqueue_mysql_auth_plugin(connection()->zeek_analyzer(), + connection()->zeek_analyzer()->Conn(), + true /*is_orig*/, + zeek::make_intrusive(c_str(${msg.v10_response.plain.auth_plugin}))); + } + } + return true; %} @@ -112,6 +135,24 @@ refine flow MySQL_Flow += { return true; %} + function proc_auth_switch_request(msg: AuthSwitchRequest): bool + %{ + zeek::BifEvent::enqueue_mysql_auth_switch_request(connection()->zeek_analyzer(), + connection()->zeek_analyzer()->Conn(), + zeek::make_intrusive(c_str(${msg.name})), + to_stringval(${msg.data})); + return true; + %} + + function proc_auth_more_data(msg: AuthMoreData): bool + %{ + zeek::BifEvent::enqueue_mysql_auth_more_data(connection()->zeek_analyzer(), + connection()->zeek_analyzer()->Conn(), + ${is_orig}, + to_stringval(${msg.data})); + return true; + %} + }; refine typeattr Initial_Handshake_Packet += &let { @@ -141,3 +182,11 @@ refine typeattr EOF_Packet += &let { refine typeattr Resultset += &let { proc = $context.flow.proc_resultset(this); }; + +refine typeattr AuthSwitchRequest += &let { + proc = $context.flow.proc_auth_switch_request(this); +}; + +refine typeattr AuthMoreData += &let { + proc = $context.flow.proc_auth_more_data(this); +}; diff --git a/src/analyzer/protocol/mysql/mysql-protocol.pac b/src/analyzer/protocol/mysql/mysql-protocol.pac index 7d427460ef..d415735fec 100644 --- a/src/analyzer/protocol/mysql/mysql-protocol.pac +++ b/src/analyzer/protocol/mysql/mysql-protocol.pac @@ -142,9 +142,7 @@ enum state { enum ConnectionExpected { EXPECT_HANDSHAKE, - EXPECT_SHA2_AUTH, - EXPECT_PUB_KEY, - EXPECT_AUTH_SWITCH_RESP, + EXPECT_AUTH_DATA, }; enum Expected { @@ -157,7 +155,6 @@ enum Expected { EXPECT_RESULTSET, EXPECT_REST_OF_PACKET, EXPECT_AUTH_SWITCH, - EXPECT_SHA2_AUTH_RESP, }; enum EOFType { @@ -176,12 +173,6 @@ enum Client_Capabilities { CLIENT_QUERY_ATTRIBUTES = 0x08000000, }; -enum SHA2_Auth_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)?/; @@ -304,7 +295,10 @@ type Handshake_Plain_v10(cap_flags: uint32) = record { } &let { update_auth_plugin: bool = $context.connection.set_auth_plugin(auth_plugin) &if( cap_flags & CLIENT_PLUGIN_AUTH ); - update_expected: bool = $context.connection.update_expected_from_auth() + + # Switch client state into expecting more auth data. If the server responds + # with an OK_Packet before, will switch into COMMAND_PHASE. + update_conn_expectation: bool = $context.connection.set_next_conn_expected(EXPECT_AUTH_DATA) &if( cap_flags & CLIENT_PLUGIN_AUTH ); }; @@ -334,46 +328,11 @@ type Handshake_Response_Packet_v9 = record { password : bytestring &restofdata; }; -# SHA2 Auth - -type SHA2_Auth_Packet = record { - state: bytestring &restofdata; -} &let { - update_state : bool = $context.connection.update_state(COMMAND_PHASE) - &if(state[0] == FAST_AUTH_SUCCESS); - update_conn_expectation: bool = $context.connection.set_next_conn_expected(EXPECT_PUB_KEY) - &if(state[1] == REQUEST_PUBLIC_KEY); -}; - -type Public_Key_Packet = record { - pad : uint8; - pub_key: bytestring &restofdata; -} &let { - update_expectation: bool = $context.connection.set_next_expected(EXPECT_SHA2_AUTH_RESP); -}; - -type SHA2_Auth_Response_Packet = record { - data: bytestring &restofdata; -} &let { - update_state: bool = $context.connection.update_state(COMMAND_PHASE); -}; - -# Auth Switch - -type Auth_Switch_Response_Packet = record { - data : bytestring &restofdata; -} &let { - update_expected: bool = $context.connection.update_expected_from_auth(); -}; - # Connection Phase type Connection_Phase_Packets = case $context.connection.get_conn_expectation() of { - EXPECT_HANDSHAKE -> handshake_resp : Handshake_Response_Packet; - EXPECT_SHA2_AUTH -> sha2_auth : SHA2_Auth_Packet; - EXPECT_PUB_KEY -> pub_key : Public_Key_Packet; - EXPECT_AUTH_SWITCH_RESP -> atuh_switch_resp : Auth_Switch_Response_Packet; - default -> unknown : empty; + EXPECT_HANDSHAKE -> handshake_resp: Handshake_Response_Packet; + EXPECT_AUTH_DATA -> auth_data: AuthMoreData(true); }; # Command Request @@ -420,7 +379,6 @@ type Command_Response(pkt_len: uint32) = case $context.connection.get_expectatio EXPECT_REST_OF_PACKET -> rest : bytestring &restofdata; EXPECT_STATUS -> status : Command_Response_Status; EXPECT_AUTH_SWITCH -> auth_switch : AuthSwitchRequest; - EXPECT_SHA2_AUTH_RESP -> sha2_auth_resp: SHA2_Auth_Response_Packet; EXPECT_EOF_THEN_RESULTSET -> eof : EOFIfLegacyThenResultset(pkt_len); default -> unknown : empty; }; @@ -429,6 +387,10 @@ type Command_Response_Status = record { pkt_type: uint8; response: case pkt_type of { 0x00 -> data_ok: OK_Packet; + # When still in the CONNECTION_PHASE, the server can reply + # with AuthMoreData which is 0x01 stuffed opaque payload. + # https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_more_data.html + 0x01 -> auth_more_data: AuthMoreData(false); 0xfe -> data_eof: EOF_Packet(EOF_END); 0xff -> data_err: ERR_Packet; default -> unknown: empty; @@ -523,13 +485,20 @@ type ColumnDefinition41(first_byte: uint8) = record { filler : padding[2]; }; +# Opaque auth data exchanged during the connection phase between client and server. +type AuthMoreData(is_orig: bool) = record { + data : bytestring &restofdata; +}; + type AuthSwitchRequest = record { status: uint8 &enforce(status==254); name : NUL_String; data : bytestring &restofdata; } &let { update_auth_plugin : bool = $context.connection.set_auth_plugin(name); - update_conn_expectation: bool = $context.connection.set_next_conn_expected(EXPECT_AUTH_SWITCH_RESP); + update_conn_expectation: bool = $context.connection.set_next_conn_expected(EXPECT_AUTH_DATA); + # After an AuthSwitchRequest, server replies with OK_Packet, ERR_Packet or AuthMoreData. + update_expectation: bool = $context.connection.set_next_expected(EXPECT_STATUS); }; type ColumnDefinition320 = record { @@ -638,18 +607,6 @@ refine connection MySQL_Conn += { return true; %} - function update_expected_from_auth(): bool - %{ - if ( auth_plugin_ == "caching_sha2_password" ) - { - conn_expected_ = EXPECT_SHA2_AUTH; - if ( expected_ == EXPECT_AUTH_SWITCH ) - expected_ = EXPECT_STATUS; - } - - return true; - %} - function get_deprecate_eof(): bool %{ return deprecate_eof_; diff --git a/testing/btest/Baseline/scripts.base.protocols.mysql.caching_sha2_password-after-auth-switch/out b/testing/btest/Baseline/scripts.base.protocols.mysql.caching_sha2_password-after-auth-switch/out index e8ead41d58..947e3add03 100644 --- a/testing/btest/Baseline/scripts.base.protocols.mysql.caching_sha2_password-after-auth-switch/out +++ b/testing/btest/Baseline/scripts.base.protocols.mysql.caching_sha2_password-after-auth-switch/out @@ -1,5 +1,10 @@ ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +mysql auth plugin, F, caching_sha2_password, Vz\x08w+^\x04p\x02Tv\x01"~\x114\x14RP6\x00, 21 mysql handshake, root +mysql auth plugin, T, mysql_native_password, , 0 +mysql auth switch request, caching_sha2_password, Vz\x08w+^\x04p\x02Tv\x01"~\x114\x14RP6\x00, 21 +mysql auth more data, T, \xf7dS\x9eXe\xc4\xd6\xa9\xa7 \xfbC\xa6p\xaf\xdf\x9dB[B\x80\xa7\x80\xef\x0c\x95BC9#\x82, 32 +mysql auth more data, F, \x03, 1 mysql ok, 0 mysql request, 3, select @@version_comment limit 1 mysql result row, [MySQL Community Server - GPL] 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 index f8855b38fc..a9dd402e6c 100644 --- a/testing/btest/Baseline/scripts.base.protocols.mysql.caching_sha2_password/out +++ b/testing/btest/Baseline/scripts.base.protocols.mysql.caching_sha2_password/out @@ -1,7 +1,16 @@ ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +mysql auth plugin, F, caching_sha2_password, s.\x13\x01>\x05m\x04~Lq)%\x0fLL\x01\x08Xj\x00, 21 mysql handshake, root +mysql auth plugin, T, caching_sha2_password, \x98\xa0Ex\x8a\xeb`\xf3\xc7)\xa6\xaf\xf1\xa4]-\xa0\xdf\x959\xa1\xc5\xd6\xb8\xf3\xd6}\xb2\xa8\x033~, 32 +mysql auth more data, F, \x04, 1 mysql error, 1158, Got an error reading communication packets +mysql auth plugin, F, caching_sha2_password, 4x`?e\x04i'k&-P%LID\x17/\x0f{\x00, 21 mysql handshake, root +mysql auth plugin, T, caching_sha2_password, y.\x91:\x11\x87i\x17\xdfI_\xd2\xec\x9a"\xc2%sB\x10\x90\xbd\x15C\xf4w\xc0\x09p}\x8eE, 32 +mysql auth more data, F, \x04, 1 +mysql auth more data, T, \x02, 1 +mysql auth more data, F, -----BEGIN PUBLIC KEY-----\x0aMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0VACy/bY60MRuPW6aCxZ\x0abi+o0EgCgxzFObbyzDfnTnVJegOXbrdcbu1qIlEjPyn7UMBfjQr+VueiJvPjz2M8\x0ad/6GX1h4fYuwW4bEXBVo4HGxM8N0IyO1BYjafOaoUeL/NI+bLifH70KorIcSUR+h\x0a879DAQ0zlKz5vwpDYN2LVxidjFvy5baSPi/csDMqi2jitBAzbNW992O/v9CPnh5f\x0akdRMa2lMPKxRaPeqAw9U7CAmRqAaHZAfdI5kYnj3vsOFvKL2dkE+ckY8sh5H2uto\x0a37+mg6oll5PsydMbSuvFHLc0JZm++oem5z2WsZBdxmohqJ8Foc43W8IOtxs+YAOw\x0avwIDAQAB\x0a-----END PUBLIC KEY-----\x0a, 451 +mysql auth more data, T, \xca3\x89.M\x9d\xc0\xcb\xd6'2Zo*\xda8\xd2\xba\xb1\xabI\xcb\x1es%R\x1fo\xd0\xa6\xb8\x90\xf56\x0e\xd9\xd8p\x9eX\x84K\xb5\x1a\xe5\xfa\x18\xc1*\xfc\xa9W\xd6p\x1a\xcfv\xe8%\xe0\xb9\xfe\x98\x1b\xb3\x938\x85\xf4O\xf0c2b\xae\x81F\x1e\xb9\x1f\xbd\xdf\x16C\x91\xd5\x08\xa6\x82\xb6y\xf7\xa3u