Commit 994ae8e7 authored by Sergei Golubchik's avatar Sergei Golubchik

MDEV-3915 COM_CHANGE_USER allows fast password brute-forcing

  
allow only three failed change_user per connection.
successful change_user do NOT reset the counter

tests/mysql_client_test.c:
  make --error to work for --change_user errors
parent 2ffdb815
...@@ -3908,7 +3908,10 @@ void do_change_user(struct st_command *command) ...@@ -3908,7 +3908,10 @@ void do_change_user(struct st_command *command)
cur_con->name, ds_user.str, ds_passwd.str, ds_db.str)); cur_con->name, ds_user.str, ds_passwd.str, ds_db.str));
if (mysql_change_user(mysql, ds_user.str, ds_passwd.str, ds_db.str)) if (mysql_change_user(mysql, ds_user.str, ds_passwd.str, ds_db.str))
die("change user failed: %s", mysql_error(mysql)); handle_error(command, mysql_errno(mysql), mysql_error(mysql),
mysql_sqlstate(mysql), &ds_res);
else
handle_no_error(command);
dynstr_free(&ds_user); dynstr_free(&ds_user);
dynstr_free(&ds_passwd); dynstr_free(&ds_passwd);
......
ERROR 28000: Access denied for user 'foo'@'localhost' (using password: NO)
ERROR 28000: Access denied for user 'foo'@'localhost' (using password: NO)
ERROR 28000: Access denied for user 'foo'@'localhost' (using password: NO)
ERROR 08S01: Unknown command
ERROR 08S01: Unknown command
...@@ -847,9 +847,9 @@ a int(11) YES NULL ...@@ -847,9 +847,9 @@ a int(11) YES NULL
b varchar(255) YES NULL b varchar(255) YES NULL
c datetime YES NULL c datetime YES NULL
drop table t1; drop table t1;
mysqltest: At line 1: change user failed: Unknown database 'inexistent' mysqltest: At line 1: query 'change_user root,,inexistent' failed: 1049: Unknown database 'inexistent'
mysqltest: At line 1: change user failed: Access denied for user 'inexistent'@'localhost' (using password: NO) mysqltest: At line 1: query 'change_user inexistent,,test' failed: 1045: Access denied for user 'inexistent'@'localhost' (using password: NO)
mysqltest: At line 1: change user failed: Access denied for user 'root'@'localhost' (using password: YES) mysqltest: At line 1: query 'change_user root,inexistent,test' failed: 1045: Access denied for user 'root'@'localhost' (using password: YES)
REPLACED_FILE1.txt REPLACED_FILE1.txt
file1.txt file1.txt
file2.txt file2.txt
......
source include/not_embedded.inc;
#
# MDEV-3915 COM_CHANGE_USER allows fast password brute-forcing
#
# only three failed change_user per connection.
# successful change_user do NOT reset the counter
#
connect (test,localhost,root,,);
connection test;
--error 1045
change_user foo,bar;
--error 1045
change_user foo;
change_user;
--error 1045
change_user foo,bar;
--error 1047
change_user foo,bar;
--error 1047
change_user;
disconnect test;
connection default;
...@@ -675,6 +675,7 @@ THD::THD() ...@@ -675,6 +675,7 @@ THD::THD()
stmt_depends_on_first_successful_insert_id_in_prev_stmt(FALSE), stmt_depends_on_first_successful_insert_id_in_prev_stmt(FALSE),
examined_row_count(0), examined_row_count(0),
global_read_lock(0), global_read_lock(0),
failed_com_change_user(0),
is_fatal_error(0), is_fatal_error(0),
transaction_rollback_request(0), transaction_rollback_request(0),
is_fatal_sub_stmt_error(0), is_fatal_sub_stmt_error(0),
......
...@@ -1865,6 +1865,7 @@ class THD :public Statement, ...@@ -1865,6 +1865,7 @@ class THD :public Statement,
bool no_errors, password; bool no_errors, password;
bool extra_port; /* If extra connection */ bool extra_port; /* If extra connection */
uint8 failed_com_change_user;
/** /**
Set to TRUE if execution of the current compound statement Set to TRUE if execution of the current compound statement
can not continue. In particular, disables activation of can not continue. In particular, disables activation of
......
...@@ -1144,6 +1144,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, ...@@ -1144,6 +1144,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
uint save_db_length= thd->db_length; uint save_db_length= thd->db_length;
char *save_db= thd->db; char *save_db= thd->db;
int rc;
USER_CONN *save_user_connect= thd->user_connect; USER_CONN *save_user_connect= thd->user_connect;
Security_context save_security_ctx= *thd->security_ctx; Security_context save_security_ctx= *thd->security_ctx;
CHARSET_INFO *save_character_set_client= CHARSET_INFO *save_character_set_client=
...@@ -1157,7 +1158,19 @@ bool dispatch_command(enum enum_server_command command, THD *thd, ...@@ -1157,7 +1158,19 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
thd->security_ctx->user= 0; thd->security_ctx->user= 0;
thd->user_connect= 0; thd->user_connect= 0;
if (acl_authenticate(thd, 0, packet_length)) /*
to limit COM_CHANGE_USER ability to brute-force passwords,
we only allow three unsuccessful COM_CHANGE_USER per connection.
*/
if (thd->failed_com_change_user >= 3)
{
my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0));
rc= 1;
}
else
rc= acl_authenticate(thd, 0, packet_length);
if (rc)
{ {
/* Free user if allocated by acl_authenticate */ /* Free user if allocated by acl_authenticate */
x_free(thd->security_ctx->user); x_free(thd->security_ctx->user);
...@@ -1170,6 +1183,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd, ...@@ -1170,6 +1183,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
thd->variables.collation_connection= save_collation_connection; thd->variables.collation_connection= save_collation_connection;
thd->variables.character_set_results= save_character_set_results; thd->variables.character_set_results= save_character_set_results;
thd->update_charset(); thd->update_charset();
thd->failed_com_change_user++;
my_sleep(1000000);
} }
else else
{ {
......
...@@ -15386,6 +15386,7 @@ static void test_change_user() ...@@ -15386,6 +15386,7 @@ static void test_change_user()
const char *pw= "password"; const char *pw= "password";
const char *db= "mysqltest_user_test_database"; const char *db= "mysqltest_user_test_database";
int rc; int rc;
MYSQL* conn;
DBUG_ENTER("test_change_user"); DBUG_ENTER("test_change_user");
myheader("test_change_user"); myheader("test_change_user");
...@@ -15429,149 +15430,173 @@ static void test_change_user() ...@@ -15429,149 +15430,173 @@ static void test_change_user()
rc= mysql_query(mysql, buff); rc= mysql_query(mysql, buff);
myquery(rc); myquery(rc);
conn= client_connect(0, MYSQL_PROTOCOL_TCP, 0);
/* Try some combinations */ /* Try some combinations */
rc= mysql_change_user(mysql, NULL, NULL, NULL); rc= mysql_change_user(conn, NULL, NULL, NULL);
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, "", NULL, NULL); rc= mysql_change_user(conn, "", NULL, NULL);
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, "", "", NULL); rc= mysql_change_user(conn, "", "", NULL);
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, "", "", ""); mysql_close(conn);
conn= client_connect(0, MYSQL_PROTOCOL_TCP, 0);
rc= mysql_change_user(conn, "", "", "");
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, NULL, "", ""); rc= mysql_change_user(conn, NULL, "", "");
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, NULL, NULL, ""); rc= mysql_change_user(conn, NULL, NULL, "");
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
mysql_close(conn);
conn= client_connect(0, MYSQL_PROTOCOL_TCP, 0);
rc= mysql_change_user(mysql, "", NULL, ""); rc= mysql_change_user(conn, "", NULL, "");
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, user_pw, NULL, ""); rc= mysql_change_user(conn, user_pw, NULL, "");
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, user_pw, "", ""); rc= mysql_change_user(conn, user_pw, "", "");
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, user_pw, "", NULL); mysql_close(conn);
conn= client_connect(0, MYSQL_PROTOCOL_TCP, 0);
rc= mysql_change_user(conn, user_pw, "", NULL);
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, user_pw, NULL, NULL); rc= mysql_change_user(conn, user_pw, NULL, NULL);
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, user_pw, "", db); rc= mysql_change_user(conn, user_pw, "", db);
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
mysql_close(conn);
conn= client_connect(0, MYSQL_PROTOCOL_TCP, 0);
rc= mysql_change_user(mysql, user_pw, NULL, db); rc= mysql_change_user(conn, user_pw, NULL, db);
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, user_pw, pw, db); rc= mysql_change_user(conn, user_pw, pw, db);
myquery(rc); myquery(rc);
rc= mysql_change_user(mysql, user_pw, pw, NULL); rc= mysql_change_user(conn, user_pw, pw, NULL);
myquery(rc); myquery(rc);
rc= mysql_change_user(mysql, user_pw, pw, ""); rc= mysql_change_user(conn, user_pw, pw, "");
myquery(rc); myquery(rc);
rc= mysql_change_user(mysql, user_no_pw, pw, db); rc= mysql_change_user(conn, user_no_pw, pw, db);
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, user_no_pw, pw, ""); rc= mysql_change_user(conn, user_no_pw, pw, "");
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, user_no_pw, pw, NULL); mysql_close(conn);
conn= client_connect(0, MYSQL_PROTOCOL_TCP, 0);
rc= mysql_change_user(conn, user_no_pw, pw, NULL);
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, user_no_pw, "", NULL); rc= mysql_change_user(conn, user_no_pw, "", NULL);
myquery(rc); myquery(rc);
rc= mysql_change_user(mysql, user_no_pw, "", ""); rc= mysql_change_user(conn, user_no_pw, "", "");
myquery(rc); myquery(rc);
rc= mysql_change_user(mysql, user_no_pw, "", db); rc= mysql_change_user(conn, user_no_pw, "", db);
myquery(rc); myquery(rc);
rc= mysql_change_user(mysql, user_no_pw, NULL, db); rc= mysql_change_user(conn, user_no_pw, NULL, db);
myquery(rc); myquery(rc);
rc= mysql_change_user(mysql, "", pw, db); rc= mysql_change_user(conn, "", pw, db);
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, "", pw, ""); rc= mysql_change_user(conn, "", pw, "");
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
mysql_close(conn);
conn= client_connect(0, MYSQL_PROTOCOL_TCP, 0);
rc= mysql_change_user(mysql, "", pw, NULL); rc= mysql_change_user(conn, "", pw, NULL);
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, NULL, pw, NULL); rc= mysql_change_user(conn, NULL, pw, NULL);
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, NULL, NULL, db); rc= mysql_change_user(conn, NULL, NULL, db);
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, NULL, "", db); mysql_close(conn);
conn= client_connect(0, MYSQL_PROTOCOL_TCP, 0);
rc= mysql_change_user(conn, NULL, "", db);
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
rc= mysql_change_user(mysql, "", "", db); rc= mysql_change_user(conn, "", "", db);
DIE_UNLESS(rc); DIE_UNLESS(rc);
if (! opt_silent) if (! opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql)); printf("Got error (as expected): %s\n", mysql_error(conn));
/* Cleanup the environment */ /* Cleanup the environment */
mysql_change_user(mysql, opt_user, opt_password, current_db); mysql_change_user(conn, opt_user, opt_password, current_db);
mysql_close(conn);
sprintf(buff, "drop database %s", db); sprintf(buff, "drop database %s", db);
rc= mysql_query(mysql, buff); rc= mysql_query(mysql, buff);
...@@ -16234,29 +16259,35 @@ static void test_bug31669() ...@@ -16234,29 +16259,35 @@ static void test_bug31669()
static char db[NAME_CHAR_LEN+1]; static char db[NAME_CHAR_LEN+1];
static char query[LARGE_BUFFER_SIZE*2]; static char query[LARGE_BUFFER_SIZE*2];
#endif #endif
MYSQL* conn;
DBUG_ENTER("test_bug31669"); DBUG_ENTER("test_bug31669");
myheader("test_bug31669"); myheader("test_bug31669");
rc= mysql_change_user(mysql, NULL, NULL, NULL); conn= client_connect(0, MYSQL_PROTOCOL_TCP, 0);
rc= mysql_change_user(conn, NULL, NULL, NULL);
DIE_UNLESS(rc); DIE_UNLESS(rc);
rc= mysql_change_user(mysql, "", "", ""); rc= mysql_change_user(conn, "", "", "");
DIE_UNLESS(rc); DIE_UNLESS(rc);
memset(buff, 'a', sizeof(buff)); memset(buff, 'a', sizeof(buff));
rc= mysql_change_user(mysql, buff, buff, buff); mysql_close(conn);
conn= client_connect(0, MYSQL_PROTOCOL_TCP, 0);
rc= mysql_change_user(conn, buff, buff, buff);
DIE_UNLESS(rc); DIE_UNLESS(rc);
rc = mysql_change_user(mysql, opt_user, opt_password, current_db); rc = mysql_change_user(conn, opt_user, opt_password, current_db);
DIE_UNLESS(!rc); DIE_UNLESS(!rc);
#ifndef EMBEDDED_LIBRARY #ifndef EMBEDDED_LIBRARY
memset(db, 'a', sizeof(db)); memset(db, 'a', sizeof(db));
db[NAME_CHAR_LEN]= 0; db[NAME_CHAR_LEN]= 0;
strxmov(query, "CREATE DATABASE IF NOT EXISTS ", db, NullS); strxmov(query, "CREATE DATABASE IF NOT EXISTS ", db, NullS);
rc= mysql_query(mysql, query); rc= mysql_query(conn, query);
myquery(rc); myquery(rc);
memset(user, 'b', sizeof(user)); memset(user, 'b', sizeof(user));
...@@ -16265,54 +16296,59 @@ static void test_bug31669() ...@@ -16265,54 +16296,59 @@ static void test_bug31669()
buff[LARGE_BUFFER_SIZE]= 0; buff[LARGE_BUFFER_SIZE]= 0;
strxmov(query, "GRANT ALL PRIVILEGES ON *.* TO '", user, "'@'%' IDENTIFIED BY " strxmov(query, "GRANT ALL PRIVILEGES ON *.* TO '", user, "'@'%' IDENTIFIED BY "
"'", buff, "' WITH GRANT OPTION", NullS); "'", buff, "' WITH GRANT OPTION", NullS);
rc= mysql_query(mysql, query); rc= mysql_query(conn, query);
myquery(rc); myquery(rc);
strxmov(query, "GRANT ALL PRIVILEGES ON *.* TO '", user, "'@'localhost' IDENTIFIED BY " strxmov(query, "GRANT ALL PRIVILEGES ON *.* TO '", user, "'@'localhost' IDENTIFIED BY "
"'", buff, "' WITH GRANT OPTION", NullS); "'", buff, "' WITH GRANT OPTION", NullS);
rc= mysql_query(mysql, query); rc= mysql_query(conn, query);
myquery(rc); myquery(rc);
rc= mysql_query(mysql, "FLUSH PRIVILEGES"); rc= mysql_query(conn, "FLUSH PRIVILEGES");
myquery(rc); myquery(rc);
rc= mysql_change_user(mysql, user, buff, db); rc= mysql_change_user(conn, user, buff, db);
DIE_UNLESS(!rc); DIE_UNLESS(!rc);
user[USERNAME_CHAR_LENGTH-1]= 'a'; user[USERNAME_CHAR_LENGTH-1]= 'a';
rc= mysql_change_user(mysql, user, buff, db); rc= mysql_change_user(conn, user, buff, db);
DIE_UNLESS(rc); DIE_UNLESS(rc);
user[USERNAME_CHAR_LENGTH-1]= 'b'; user[USERNAME_CHAR_LENGTH-1]= 'b';
buff[LARGE_BUFFER_SIZE-1]= 'd'; buff[LARGE_BUFFER_SIZE-1]= 'd';
rc= mysql_change_user(mysql, user, buff, db); rc= mysql_change_user(conn, user, buff, db);
DIE_UNLESS(rc); DIE_UNLESS(rc);
buff[LARGE_BUFFER_SIZE-1]= 'c'; buff[LARGE_BUFFER_SIZE-1]= 'c';
db[NAME_CHAR_LEN-1]= 'e'; db[NAME_CHAR_LEN-1]= 'e';
rc= mysql_change_user(mysql, user, buff, db); rc= mysql_change_user(conn, user, buff, db);
DIE_UNLESS(rc); DIE_UNLESS(rc);
mysql_close(conn);
conn= client_connect(0, MYSQL_PROTOCOL_TCP, 0);
db[NAME_CHAR_LEN-1]= 'a'; db[NAME_CHAR_LEN-1]= 'a';
rc= mysql_change_user(mysql, user, buff, db); rc= mysql_change_user(conn, user, buff, db);
DIE_UNLESS(!rc); DIE_UNLESS(!rc);
rc= mysql_change_user(mysql, user + 1, buff + 1, db + 1); rc= mysql_change_user(conn, user + 1, buff + 1, db + 1);
DIE_UNLESS(rc); DIE_UNLESS(rc);
rc = mysql_change_user(mysql, opt_user, opt_password, current_db); rc = mysql_change_user(conn, opt_user, opt_password, current_db);
DIE_UNLESS(!rc); DIE_UNLESS(!rc);
strxmov(query, "DROP DATABASE ", db, NullS); strxmov(query, "DROP DATABASE ", db, NullS);
rc= mysql_query(mysql, query); rc= mysql_query(conn, query);
myquery(rc); myquery(rc);
strxmov(query, "DELETE FROM mysql.user WHERE User='", user, "'", NullS); strxmov(query, "DELETE FROM mysql.user WHERE User='", user, "'", NullS);
rc= mysql_query(mysql, query); rc= mysql_query(conn, query);
myquery(rc); myquery(rc);
DIE_UNLESS(mysql_affected_rows(mysql) == 2); DIE_UNLESS(mysql_affected_rows(conn) == 2);
#endif #endif
mysql_close(conn);
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment