From 3bba29a3970f8ef05b85cec49dc7caead20e759b Mon Sep 17 00:00:00 2001 From: Venkata Sidagam Date: Mon, 30 Jun 2014 19:24:25 +0530 Subject: [PATCH] Bug #17357528 BACKPORT BUG#16513435 TO 5.5 AND 5.6 Description: Backporting BUG#16513435 to 5.5 and 5.6 This is a fix for REMOTE PREAUTH USER ENUMERATION FLAW bug --- mysql-test/r/plugin_auth_qa_3.result | 2 +- sql/sql_acl.cc | 70 +++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/mysql-test/r/plugin_auth_qa_3.result b/mysql-test/r/plugin_auth_qa_3.result index d94d8879e7d..c1b8446277f 100644 --- a/mysql-test/r/plugin_auth_qa_3.result +++ b/mysql-test/r/plugin_auth_qa_3.result @@ -6,6 +6,6 @@ exec MYSQL PLUGIN_AUTH_OPT --default_auth=qa_auth_client -h localhost -P MASTER_ current_user() user() @@local.proxy_user @@local.external_user qa_test_11_dest@% qa_test_11_user@localhost 'qa_test_11_user'@'%' 'qa_test_11_user'@'%' exec MYSQL PLUGIN_AUTH_OPT --default_auth=qa_auth_client -h localhost -P MASTER_MYPORT -u qa_test_2_user --password=qa_test_11_dest test_user_db -e "SELECT current_user(),user(),@@local.proxy_user,@@local.external_user;" 2>&1 -ERROR 1045 (28000): Access denied for user 'qa_test_2_user'@'localhost' (using password: NO) +ERROR 1045 (28000): Access denied for user 'qa_test_2_user'@'localhost' (using password: YES) DROP USER qa_test_11_user, qa_test_11_dest; DROP DATABASE test_user_db; diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index ff2871f7f1d..2bb074da1f7 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -222,6 +222,7 @@ public: const char *ssl_cipher, *x509_issuer, *x509_subject; LEX_STRING plugin; LEX_STRING auth_string; + bool can_authenticate; ACL_USER *copy(MEM_ROOT *root) { @@ -811,6 +812,16 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) { ACL_USER user; bzero(&user, sizeof(user)); + + /* + All accounts can authenticate per default. This will change when + we add a new field to the user table. + + Currently this flag is only set to false when authentication is attempted + using an unknown user name. + */ + user.can_authenticate= true; + update_hostname(&user.host, get_field(&mem, table->field[0])); user.user= get_field(&mem, table->field[1]); if (check_no_resolve && hostname_requires_resolving(user.host.hostname)) @@ -1465,6 +1476,14 @@ static void acl_insert_user(const char *user, const char *host, ACL_USER acl_user; mysql_mutex_assert_owner(&acl_cache->lock); + /* + All accounts can authenticate per default. This will change when + we add a new field to the user table. + + Currently this flag is only set to false when authentication is attempted + using an unknown user name. + */ + acl_user.can_authenticate= true; acl_user.user=*user ? strdup_root(&mem,user) : 0; update_hostname(&acl_user.host, *host ? strdup_root(&mem, host): 0); @@ -8000,6 +8019,10 @@ struct MPVIO_EXT :public MYSQL_PLUGIN_VIO char *host; Thd_charset_adapter *charset_adapter; LEX_STRING acl_user_plugin; + bool can_authenticate() + { + return (acl_user && acl_user->can_authenticate); + } }; /** @@ -8301,6 +8324,34 @@ static bool send_plugin_request_packet(MPVIO_EXT *mpvio, } #ifndef NO_EMBEDDED_ACCESS_CHECKS + +/** + When authentication is attempted using an unknown username a dummy user + account with no authentication capabilites is assigned to the connection. + This is done increase the cost of enumerating user accounts based on + authentication protocol. +*/ + +ACL_USER *decoy_user(const LEX_STRING &username, + MEM_ROOT *mem) +{ + ACL_USER *user= (ACL_USER *) alloc_root(mem, sizeof(ACL_USER)); + user->can_authenticate= false; + user->user= strmake_root(mem, username.str, username.length); + user->auth_string= empty_lex_str; + user->ssl_cipher= empty_c_string; + user->x509_issuer= empty_c_string; + user->x509_subject= empty_c_string; + user->salt_len= 0; + + /* + For now the common default account is used. Improvements might involve + mapping a consistent hash of a username to a range of plugins. + */ + user->plugin= *default_auth_plugin_name; + return user; +} + /** Finds acl entry in user database for authentication purposes. @@ -8342,8 +8393,14 @@ static bool find_mpvio_user(MPVIO_EXT *mpvio) if (!mpvio->acl_user) { - login_failed_error(mpvio, mpvio->auth_info.password_used); - DBUG_RETURN (1); + /* + Pretend the user exists; let the plugin decide how to handle + bad credentials. + */ + LEX_STRING usr= { mpvio->auth_info.user_name, + mpvio->auth_info.user_name_length }; + mpvio->acl_user= decoy_user(usr, mpvio->mem_root); + mpvio->acl_user_plugin= mpvio->acl_user->plugin; } /* user account requires non-default plugin and the client is too old */ @@ -8465,7 +8522,9 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) #ifndef NO_EMBEDDED_ACCESS_CHECKS if (find_mpvio_user(mpvio)) + { DBUG_RETURN(1); + } char *client_plugin; if (mpvio->client_capabilities & CLIENT_PLUGIN_AUTH) @@ -9450,6 +9509,8 @@ acl_authenticate(THD *thd, uint connect_errors, uint com_change_user_pkt_len) if (parse_com_change_user_packet(&mpvio, com_change_user_pkt_len)) { + if (!thd->is_error()) + login_failed_error(&mpvio, mpvio.auth_info.password_used); server_mpvio_update_thd(thd, &mpvio); DBUG_RETURN(1); } @@ -9515,6 +9576,11 @@ acl_authenticate(THD *thd, uint connect_errors, uint com_change_user_pkt_len) mpvio.db.str ? mpvio.db.str : (char*) ""); } + if (res == CR_OK && !mpvio.can_authenticate()) + { + res= CR_ERROR; + } + if (res > CR_OK && mpvio.status != MPVIO_EXT::SUCCESS) { DBUG_ASSERT(mpvio.status == MPVIO_EXT::FAILURE);