From 5e0dc81e5e16b3b1b8f3a1e2937b066c6ed4bf94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Thu, 1 Jul 2021 18:35:19 +0200 Subject: [PATCH] Added support for 'Auth Switch Request' for 'COM_CHANGE_USER' #3504 --- include/MySQL_Session.h | 10 +++++++++ lib/MySQL_Protocol.cpp | 49 ++++++++++++++++++++++++++++++++++++++--- lib/MySQL_Session.cpp | 20 +++++++++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/include/MySQL_Session.h b/include/MySQL_Session.h index f4e5c5c61..65045ed64 100644 --- a/include/MySQL_Session.h +++ b/include/MySQL_Session.h @@ -238,6 +238,16 @@ class MySQL_Session bool session_fast_forward; bool started_sending_data_to_client; // this status variable tracks if some result set was sent to the client, or if proxysql is still buffering everything bool use_ssl; + /** + * @brief This status variable tracks whether the session is performing an + * 'Auth Switch' due to a 'COM_CHANGE_USER' packet. + * @details It becomes 'true' when the packet is detected and processed by: + * - 'MySQL_Protocol::process_pkt_COM_CHANGE_USER' + * It's reset before sending the final response for 'Auth Switch' to the client by: + * - 'MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE' + * This flag was introduced for issue #3504. + */ + bool change_user_auth_switch; bool with_gtid; diff --git a/lib/MySQL_Protocol.cpp b/lib/MySQL_Protocol.cpp index a687480c9..e291cf864 100644 --- a/lib/MySQL_Protocol.cpp +++ b/lib/MySQL_Protocol.cpp @@ -1259,6 +1259,11 @@ bool MySQL_Protocol::generate_pkt_auth_switch_request(bool send, void **ptr, uns myhdr.pkt_id++; } + // Check if a 'COM_CHANGE_USER' Auth Switch is being performed in session + if ((*myds)->sess->change_user_auth_switch) { + myhdr.pkt_id=1; + } + switch((*myds)->switching_auth_type) { case 1: myhdr.pkt_length=1 // fe @@ -1601,6 +1606,23 @@ bool MySQL_Protocol::process_pkt_COM_CHANGE_USER(unsigned char *pkt, unsigned in reply[SHA_DIGEST_LENGTH]='\0'; cur+=pass_len; db=(char *)pkt+cur; + // Move to field after 'database' + cur += strlen(db) + 1; + // Skipt field 'character-set' (size 2) + cur += 2; + // Check and get 'Client Auth Plugin' if capability is supported + char* client_auth_plugin = nullptr; + if (pkt + len > pkt + cur) { + int capabilities = (*myds)->sess->client_myds->myconn->options.client_flag; + if (capabilities & CLIENT_PLUGIN_AUTH) { + client_auth_plugin = reinterpret_cast(pkt + cur); + } + } + // Default to 'mysql_native_password' in case 'auth_plugin' is not found. + if (client_auth_plugin == nullptr) { + client_auth_plugin = const_cast("mysql_native_password"); + } + void *sha1_pass=NULL; enum proxysql_session_type session_type = (*myds)->sess->session_type; if (session_type == PROXYSQL_SESSION_CLICKHOUSE) { @@ -1619,10 +1641,31 @@ bool MySQL_Protocol::process_pkt_COM_CHANGE_USER(unsigned char *pkt, unsigned in if (pass_len==0 && strlen(password)==0) { ret=true; } else { + // If pass not sent within 'COM_CHANGE_USER' packet, an 'Auth Switch Request' + // is required. We default to 'mysql_native_password'. See #3504 for more context. + if (pass_len == 0) { + // mysql_native_password + (*myds)->switching_auth_type = 1; + // started 'Auth Switch Request' for 'CHANGE_USER' in MySQL_Session. + (*myds)->sess->change_user_auth_switch = true; + + generate_pkt_auth_switch_request(true, NULL, NULL); + (*myds)->myconn->userinfo->set((char *)user, NULL, db, NULL); + ret = false; + } + + // If pass is sent with 'COM_CHANGE_USER', we proceed trying to use + // it to authenticate the user. See #3504 for more context. if (password[0]!='*') { // clear text password - proxy_scramble(reply, (*myds)->myconn->scramble_buff, password); - if (memcmp(reply, pass, SHA_DIGEST_LENGTH)==0) { - ret=true; + if (strcmp(client_auth_plugin, "mysql_native_password") == 0) { // mysql_native_password + proxy_scramble(reply, (*myds)->myconn->scramble_buff, password); + if (memcmp(reply, pass, SHA_DIGEST_LENGTH)==0) { + ret=true; + } + } else { // mysql_clear_password + if (strncmp(password,(char *)pass,strlen(password))==0) { + ret=true; + } } } else { if (session_type == PROXYSQL_SESSION_MYSQL || session_type == PROXYSQL_SESSION_SQLITE) { diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 7a7a97b96..bb712666f 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -492,6 +492,7 @@ MySQL_Session::MySQL_Session() { with_gtid = false; use_ssl = false; + change_user_auth_switch = false; //gtid_trxid = 0; gtid_hid = -1; @@ -985,6 +986,7 @@ void MySQL_Session::generate_proxysql_internal_session_json(json &j) { } } j["client"]["DSS"] = client_myds->DSS; + j["client"]["switching_auth_type"] = client_myds->switching_auth_type; j["default_schema"] = ( default_schema ? default_schema : "" ); j["user_attributes"] = ( user_attributes ? user_attributes : "" ); j["transaction_persistent"] = transaction_persistent; @@ -4963,6 +4965,15 @@ void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE( uint8_t _pid = 2; if (client_myds->switching_auth_stage) _pid+=2; if (is_encrypted) _pid++; + // If this condition is met, it means that the + // 'STATE_SERVER_HANDSHAKE' being performed isn't from the start of a + // connection, but as a consequence of a 'COM_USER_CHANGE' which + // requires an 'Auth Switch'. Thus, we impose a 'pid' of '3' for the + // response 'OK' packet. See #3504 for more context. + if (change_user_auth_switch) { + _pid = 3; + change_user_auth_switch = 0; + } if (use_ssl == true && is_encrypted == false) { *wrong_pass=true; GloMyLogger->log_audit_entry(PROXYSQL_MYSQL_AUTH_ERR, this, NULL); @@ -6150,6 +6161,15 @@ void MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C // FIXME: max_connections is not handled for CHANGE_USER } else { l_free(pkt->size,pkt->ptr); + // 'COM_CHANGE_USER' didn't supply a password, and an 'Auth Switch Response' is + // required, going back to 'STATE_SERVER_HANDSHAKE' to perform the regular + // 'Auth Switch Response' for a connection is required. See #3504 for more context. + if (change_user_auth_switch) { + client_myds->DSS = STATE_SERVER_HANDSHAKE; + status = CONNECTING_CLIENT; + return; + } + proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Wrong credentials for frontend: disconnecting\n"); *wrong_pass=true; // FIXME: this should become close connection