mysql: Add mysql_auth_plugin, mysql_auth_more_data and mysql_auth_switch_request events

Remove caching_sha2_password parsing/state from the analyzer and implement
the generic events. If we actually want to peak into the authentication
mechanism, we could write a separate analyzer for it. For now, treat it
as opaque values that are exposed to script land.

The added tests show the --get-server-public-key in use where
mysql_auth_more_data contains an RSA public key.
This commit is contained in:
Arne Welzel 2024-07-05 10:24:40 +02:00
parent 8a92945b06
commit 40f1c2cb6d
7 changed files with 151 additions and 62 deletions

View file

@ -87,3 +87,39 @@ event mysql_server_version%(c: connection, ver: string%);
## .. zeek:see:: mysql_command_request mysql_error mysql_ok mysql_server_version ## .. zeek:see:: mysql_command_request mysql_error mysql_ok mysql_server_version
event mysql_handshake%(c: connection, username: string%); 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%);

View file

@ -14,6 +14,17 @@ refine flow MySQL_Flow += {
connection()->zeek_analyzer()->Conn(), connection()->zeek_analyzer()->Conn(),
zeek::make_intrusive<zeek::StringVal>(c_str(${msg.handshake9.server_version}))); zeek::make_intrusive<zeek::StringVal>(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<zeek::StringVal>(c_str(${msg.handshake10.auth_plugin})));
}
}
return true; return true;
%} %}
@ -40,6 +51,18 @@ refine flow MySQL_Flow += {
connection()->zeek_analyzer()->Conn(), connection()->zeek_analyzer()->Conn(),
zeek::make_intrusive<zeek::StringVal>(c_str(${msg.v9_response.username}))); zeek::make_intrusive<zeek::StringVal>(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<zeek::StringVal>(c_str(${msg.v10_response.plain.auth_plugin})));
}
}
return true; return true;
%} %}
@ -112,6 +135,24 @@ refine flow MySQL_Flow += {
return true; 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<zeek::StringVal>(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 { refine typeattr Initial_Handshake_Packet += &let {
@ -141,3 +182,11 @@ refine typeattr EOF_Packet += &let {
refine typeattr Resultset += &let { refine typeattr Resultset += &let {
proc = $context.flow.proc_resultset(this); 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);
};

View file

@ -142,9 +142,7 @@ enum state {
enum ConnectionExpected { enum ConnectionExpected {
EXPECT_HANDSHAKE, EXPECT_HANDSHAKE,
EXPECT_SHA2_AUTH, EXPECT_AUTH_DATA,
EXPECT_PUB_KEY,
EXPECT_AUTH_SWITCH_RESP,
}; };
enum Expected { enum Expected {
@ -157,7 +155,6 @@ enum Expected {
EXPECT_RESULTSET, EXPECT_RESULTSET,
EXPECT_REST_OF_PACKET, EXPECT_REST_OF_PACKET,
EXPECT_AUTH_SWITCH, EXPECT_AUTH_SWITCH,
EXPECT_SHA2_AUTH_RESP,
}; };
enum EOFType { enum EOFType {
@ -176,12 +173,6 @@ enum Client_Capabilities {
CLIENT_QUERY_ATTRIBUTES = 0x08000000, 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 NUL_String = RE/[^\0]*\0/;
type EmptyOrNUL_String = RE/([^\0]*\0)?/; type EmptyOrNUL_String = RE/([^\0]*\0)?/;
@ -304,7 +295,10 @@ type Handshake_Plain_v10(cap_flags: uint32) = record {
} &let { } &let {
update_auth_plugin: bool = $context.connection.set_auth_plugin(auth_plugin) update_auth_plugin: bool = $context.connection.set_auth_plugin(auth_plugin)
&if( cap_flags & CLIENT_PLUGIN_AUTH ); &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 ); &if( cap_flags & CLIENT_PLUGIN_AUTH );
}; };
@ -334,46 +328,11 @@ type Handshake_Response_Packet_v9 = record {
password : bytestring &restofdata; 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 # Connection Phase
type Connection_Phase_Packets = case $context.connection.get_conn_expectation() of { type Connection_Phase_Packets = case $context.connection.get_conn_expectation() of {
EXPECT_HANDSHAKE -> handshake_resp: Handshake_Response_Packet; EXPECT_HANDSHAKE -> handshake_resp: Handshake_Response_Packet;
EXPECT_SHA2_AUTH -> sha2_auth : SHA2_Auth_Packet; EXPECT_AUTH_DATA -> auth_data: AuthMoreData(true);
EXPECT_PUB_KEY -> pub_key : Public_Key_Packet;
EXPECT_AUTH_SWITCH_RESP -> atuh_switch_resp : Auth_Switch_Response_Packet;
default -> unknown : empty;
}; };
# Command Request # 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_REST_OF_PACKET -> rest : bytestring &restofdata;
EXPECT_STATUS -> status : Command_Response_Status; EXPECT_STATUS -> status : Command_Response_Status;
EXPECT_AUTH_SWITCH -> auth_switch : AuthSwitchRequest; 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); EXPECT_EOF_THEN_RESULTSET -> eof : EOFIfLegacyThenResultset(pkt_len);
default -> unknown : empty; default -> unknown : empty;
}; };
@ -429,6 +387,10 @@ type Command_Response_Status = record {
pkt_type: uint8; pkt_type: uint8;
response: case pkt_type of { response: case pkt_type of {
0x00 -> data_ok: OK_Packet; 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); 0xfe -> data_eof: EOF_Packet(EOF_END);
0xff -> data_err: ERR_Packet; 0xff -> data_err: ERR_Packet;
default -> unknown: empty; default -> unknown: empty;
@ -523,13 +485,20 @@ type ColumnDefinition41(first_byte: uint8) = record {
filler : padding[2]; 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 { type AuthSwitchRequest = record {
status: uint8 &enforce(status==254); status: uint8 &enforce(status==254);
name : NUL_String; name : NUL_String;
data : bytestring &restofdata; data : bytestring &restofdata;
} &let { } &let {
update_auth_plugin : bool = $context.connection.set_auth_plugin(name); 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 { type ColumnDefinition320 = record {
@ -638,18 +607,6 @@ refine connection MySQL_Conn += {
return true; 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 function get_deprecate_eof(): bool
%{ %{
return deprecate_eof_; return deprecate_eof_;

View file

@ -1,5 +1,10 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### 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 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 ok, 0
mysql request, 3, select @@version_comment limit 1 mysql request, 3, select @@version_comment limit 1
mysql result row, [MySQL Community Server - GPL] mysql result row, [MySQL Community Server - GPL]

View file

@ -1,7 +1,16 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### 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 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 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 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<B\x0c(\xacR9\xee\xd8xv-\xe1\xb9d\xd2\x1e\xfam\xf9\xf6!f>\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<F}V\xd8`Eb\xcf;\x8e\xd1\xe5\xba\x03\x03\x17q\xba\xbe\xde4\xdc}K\xcc\xc2eWs\x8e\xf5\x87\xa8\x0fL\x8a\xb5a|k\x19\xbc|\xa4\xfb\x16\x8e \xb8\x84]\x87\xc8\xe5m\xf1\xca\xbbD=(I\xd9?\x9d\xea\x0d\xa9\xf7\xd3?\xb1\xad\xf5\x18\x08\x00/s\x10<\xb4\x80\xc7\xa5\xd0\xfa\x11\xe9\xcd\xdf/\xa8\xef\xdeAd\x86k \x92\x8b\x85is\x91!p\o\xa6yZ5\xba\xe5\xaa\x11\xcbt\xbc\x11XV\x8eW\x921x\xa9T\x803)f(S, 256
mysql ok, 0 mysql ok, 0
mysql request, 3, show databases mysql request, 3, show databases
mysql result row, [information_schema] mysql result row, [information_schema]
@ -19,7 +28,10 @@ mysql request, 3, select @@version_comment limit 1
mysql result row, [MySQL Community Server - GPL] mysql result row, [MySQL Community Server - GPL]
mysql ok, 0 mysql ok, 0
mysql request, 1, mysql request, 1,
mysql auth plugin, F, caching_sha2_password, Z\x0cwi\x02Y\x12{#M\x13\x15C7L\x15&m\x1a\x1e\x00, 21
mysql handshake, root mysql handshake, root
mysql auth plugin, T, caching_sha2_password, \x9a\xbec\xdd\xd7\xa1\x83X}\x81\xbf\x06\xe7\xd2\xd8\xb2\x7f\xdbs\xe2\xfd\x0f1\x88\xb1\xf0i}\x94D\x8ds, 32
mysql auth more data, F, \x03, 1
mysql ok, 0 mysql ok, 0
mysql request, 3, show databases mysql request, 3, show databases
mysql result row, [information_schema] mysql result row, [information_schema]

View file

@ -33,3 +33,18 @@ event mysql_handshake(c: connection, username: string)
{ {
print "mysql handshake", username; print "mysql handshake", username;
} }
event mysql_auth_plugin(c: connection, is_orig: bool, name: string, data: string)
{
print "mysql auth plugin", is_orig, name, data, |data|;
}
event mysql_auth_switch_request(c: connection, name: string, data: string)
{
print "mysql auth switch request", name, data, |data|;
}
event mysql_auth_more_data(c: connection, is_orig: bool, data: string)
{
print "mysql auth more data", is_orig, data, |data|;
}

View file

@ -33,3 +33,18 @@ event mysql_handshake(c: connection, username: string)
{ {
print "mysql handshake", username; print "mysql handshake", username;
} }
event mysql_auth_plugin(c: connection, is_orig: bool, name: string, data: string)
{
print "mysql auth plugin", is_orig, name, data, |data|;
}
event mysql_auth_switch_request(c: connection, name: string, data: string)
{
print "mysql auth switch request", name, data, |data|;
}
event mysql_auth_more_data(c: connection, is_orig: bool, data: string)
{
print "mysql auth more data", is_orig, data, |data|;
}