Commit d4c35fb2 authored by Daniel Black's avatar Daniel Black

MDEV-24207: recognise mysql forms of invalid password for mysql_native_password

The main goal of this patch is to prevent MariaDB's native_password_plugin
from "parsing" the hex (or non hex) authentication_string. Due to how the
current code is written, we convert any string (within native_password_get_salt)
that has the appropriate length to a "binary" representation, that can
potentially match a real password.

More specifically,
"*THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE" produces the same results as
"*d13c3c78dafa52d9bce09bdd1adcb7befced1ebe".

The length indicator is the main indicator of an invalid password. We use
use same trick with "invalid" to change its internal representation.

The "parsing" mentioned is by get_salt_from_password down to char_val()
and because if where it is, its effectively a static plugin API that cannot
change.

In supporting these, we support the SHOW CREATE USER from MySQL may have the
hashed password string: *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE.

Obviously this isn't a hash because it contains non-hex characters.

After this patch we do however recognise the pattern;

 [any char, notionally *]{40 chars not all are hex}

as a pattern for an invalid password. This was determined to be the general
pattern that MySQL used.

Reviewers: Sergei G, Vicentiu
parent 0ea65002
...@@ -11,9 +11,11 @@ create user oldauth@localhost identified with 'mysql_old_password' using '378b24 ...@@ -11,9 +11,11 @@ create user oldauth@localhost identified with 'mysql_old_password' using '378b24
create user oldpass@localhost identified by password '378b243e220ca493'; create user oldpass@localhost identified by password '378b243e220ca493';
create user oldpassold@localhost identified with 'mysql_old_password'; create user oldpassold@localhost identified with 'mysql_old_password';
set password for oldpassold@localhost = '378b243e220ca493'; set password for oldpassold@localhost = '378b243e220ca493';
create user invalidmysql57auth@localhost identified via 'mysql_native_password' using '*THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE';
select user, host, password, plugin, authentication_string from mysql.user where user != 'root'; select user, host, password, plugin, authentication_string from mysql.user where user != 'root';
User Host Password plugin authentication_string User Host Password plugin authentication_string
invalidauth localhost invalid mysql_native_password invalid invalidauth localhost invalid mysql_native_password invalid
invalidmysql57auth localhost *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE mysql_native_password *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE
invalidpass localhost invalid mysql_native_password invalid invalidpass localhost invalid mysql_native_password invalid
invalidpassnat localhost invalid mysql_native_password invalid invalidpassnat localhost invalid mysql_native_password invalid
mariadb.sys localhost mysql_native_password mariadb.sys localhost mysql_native_password
...@@ -95,6 +97,7 @@ set password for oldpassold@localhost = PASSWORD('test2'); ...@@ -95,6 +97,7 @@ set password for oldpassold@localhost = PASSWORD('test2');
select user, host, password, plugin, authentication_string from mysql.user where user != 'root'; select user, host, password, plugin, authentication_string from mysql.user where user != 'root';
User Host Password plugin authentication_string User Host Password plugin authentication_string
invalidauth localhost invalid mysql_native_password invalid invalidauth localhost invalid mysql_native_password invalid
invalidmysql57auth localhost *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE mysql_native_password *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE
invalidpass localhost invalid mysql_native_password invalid invalidpass localhost invalid mysql_native_password invalid
invalidpassnat localhost invalid mysql_native_password invalid invalidpassnat localhost invalid mysql_native_password invalid
mariadb.sys localhost mysql_native_password mariadb.sys localhost mysql_native_password
...@@ -160,6 +163,9 @@ ERROR 28000: Access denied for user 'invalidpass'@'localhost' (using password: Y ...@@ -160,6 +163,9 @@ ERROR 28000: Access denied for user 'invalidpass'@'localhost' (using password: Y
connect(localhost,invalidpassnat,invalid,test,MASTER_PORT,MASTER_SOCKET); connect(localhost,invalidpassnat,invalid,test,MASTER_PORT,MASTER_SOCKET);
connect con,localhost,invalidpassnat,invalid,; connect con,localhost,invalidpassnat,invalid,;
ERROR 28000: Access denied for user 'invalidpassnat'@'localhost' (using password: YES) ERROR 28000: Access denied for user 'invalidpassnat'@'localhost' (using password: YES)
connect(localhost,invalidmysql57auth,invalid,test,MASTER_PORT,MASTER_SOCKET);
connect con,localhost,invalidmysql57auth,invalid,;
ERROR 28000: Access denied for user 'invalidmysql57auth'@'localhost' (using password: YES)
connect con,localhost,oldauth,test2,; connect con,localhost,oldauth,test2,;
select current_user(); select current_user();
current_user() current_user()
...@@ -177,7 +183,7 @@ oldpassold@localhost ...@@ -177,7 +183,7 @@ oldpassold@localhost
disconnect con; disconnect con;
connection default; connection default;
drop user natauth@localhost, newpass@localhost, newpassnat@localhost; drop user natauth@localhost, newpass@localhost, newpassnat@localhost;
drop user invalidauth@localhost, invalidpass@localhost, invalidpassnat@localhost; drop user invalidauth@localhost, invalidpass@localhost, invalidpassnat@localhost,invalidmysql57auth@localhost;
drop user oldauth@localhost, oldpass@localhost, oldpassold@localhost; drop user oldauth@localhost, oldpass@localhost, oldpassold@localhost;
set global secure_auth=default; set global secure_auth=default;
# switching from mysql.global_priv to mysql.user # switching from mysql.global_priv to mysql.user
......
...@@ -31,6 +31,8 @@ create user oldpass@localhost identified by password '378b243e220ca493'; ...@@ -31,6 +31,8 @@ create user oldpass@localhost identified by password '378b243e220ca493';
create user oldpassold@localhost identified with 'mysql_old_password'; create user oldpassold@localhost identified with 'mysql_old_password';
set password for oldpassold@localhost = '378b243e220ca493'; set password for oldpassold@localhost = '378b243e220ca493';
create user invalidmysql57auth@localhost identified via 'mysql_native_password' using '*THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE';
--sorted_result --sorted_result
select user, host, password, plugin, authentication_string from mysql.user where user != 'root'; select user, host, password, plugin, authentication_string from mysql.user where user != 'root';
...@@ -131,6 +133,9 @@ select current_user(); ...@@ -131,6 +133,9 @@ select current_user();
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT --replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
--error ER_ACCESS_DENIED_ERROR --error ER_ACCESS_DENIED_ERROR
--connect(con,localhost,invalidpassnat,invalid,) --connect(con,localhost,invalidpassnat,invalid,)
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
--error ER_ACCESS_DENIED_ERROR
--connect(con,localhost,invalidmysql57auth,invalid,)
--connect(con,localhost,oldauth,test2,) --connect(con,localhost,oldauth,test2,)
select current_user(); select current_user();
...@@ -144,7 +149,7 @@ select current_user(); ...@@ -144,7 +149,7 @@ select current_user();
--connection default --connection default
drop user natauth@localhost, newpass@localhost, newpassnat@localhost; drop user natauth@localhost, newpass@localhost, newpassnat@localhost;
drop user invalidauth@localhost, invalidpass@localhost, invalidpassnat@localhost; drop user invalidauth@localhost, invalidpass@localhost, invalidpassnat@localhost,invalidmysql57auth@localhost;
drop user oldauth@localhost, oldpass@localhost, oldpassold@localhost; drop user oldauth@localhost, oldpass@localhost, oldpassold@localhost;
set global secure_auth=default; set global secure_auth=default;
......
...@@ -14203,6 +14203,7 @@ static int native_password_get_salt(const char *hash, size_t hash_length, ...@@ -14203,6 +14203,7 @@ static int native_password_get_salt(const char *hash, size_t hash_length,
{ {
DBUG_ASSERT(sizeof(invalid_password) > SCRAMBLE_LENGTH); DBUG_ASSERT(sizeof(invalid_password) > SCRAMBLE_LENGTH);
DBUG_ASSERT(*out_length >= SCRAMBLE_LENGTH); DBUG_ASSERT(*out_length >= SCRAMBLE_LENGTH);
DBUG_ASSERT(*out_length >= sizeof(invalid_password));
if (hash_length == 0) if (hash_length == 0)
{ {
*out_length= 0; *out_length= 0;
...@@ -14213,14 +14214,27 @@ static int native_password_get_salt(const char *hash, size_t hash_length, ...@@ -14213,14 +14214,27 @@ static int native_password_get_salt(const char *hash, size_t hash_length,
{ {
if (hash_length == 7 && strcmp(hash, "invalid") == 0) if (hash_length == 7 && strcmp(hash, "invalid") == 0)
{ {
memcpy(out, invalid_password, SCRAMBLED_PASSWORD_CHAR_LENGTH); memcpy(out, invalid_password, sizeof(invalid_password));
*out_length= SCRAMBLED_PASSWORD_CHAR_LENGTH; *out_length= sizeof(invalid_password);
return 0; return 0;
} }
my_error(ER_PASSWD_LENGTH, MYF(0), SCRAMBLED_PASSWORD_CHAR_LENGTH); my_error(ER_PASSWD_LENGTH, MYF(0), SCRAMBLED_PASSWORD_CHAR_LENGTH);
return 1; return 1;
} }
for (const char *c= hash + 1; c < (hash + hash_length); c++)
{
/* If any non-hex characters are found, mark the password as invalid. */
if (!(*c >= '0' && *c <= '9') &&
!(*c >= 'A' && *c <= 'F') &&
!(*c >= 'a' && *c <= 'f'))
{
memcpy(out, invalid_password, sizeof(invalid_password));
*out_length= sizeof(invalid_password);
return 0;
}
}
*out_length= SCRAMBLE_LENGTH; *out_length= SCRAMBLE_LENGTH;
get_salt_from_password(out, hash); get_salt_from_password(out, hash);
return 0; return 0;
......
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