diff --git a/NEWS b/NEWS index a63a367d1f5..15202e9ad73 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,9 @@ PHP NEWS - MySQLnd: . Fixed bug GH-11440 (authentication to a sha256_password account fails over SSL). (nielsdos) + . Fixed bug GH-11438 (mysqlnd fails to authenticate with sha256_password + accounts using passwords longer than 19 characters). + (nielsdos, Kamil Tekiela) - Opcache: . Fixed bug GH-11715 (opcache.interned_strings_buffer either has no effect or diff --git a/ext/mysqli/tests/gh11438.phpt b/ext/mysqli/tests/gh11438.phpt new file mode 100644 index 00000000000..49c715e72c0 --- /dev/null +++ b/ext/mysqli/tests/gh11438.phpt @@ -0,0 +1,84 @@ +--TEST-- +GH-11438 (mysqlnd fails to authenticate with sha256_password accounts using passwords longer than 19 characters) +--EXTENSIONS-- +mysqli +--SKIPIF-- +query("SHOW PLUGINS"))) { + die(sprintf("skip [%d] %s\n", $link->errno, $link->error)); +} + +$found = false; +while ($row = $res->fetch_assoc()) { + if (($row['Name'] == 'sha256_password') && ($row['Status'] == 'ACTIVE')) { + $found = true; + break; + } +} +if (!$found) + die("skip SHA-256 server plugin unavailable"); + +// Ignore errors because this variable exists only in MySQL 5.6 and 5.7 +$link->query("SET @@session.old_passwords=2"); + +$link->query('DROP USER shatest'); +$link->query("DROP USER shatest@localhost"); + +if (!$link->query('CREATE USER shatest@"%" IDENTIFIED WITH sha256_password') || + !$link->query('CREATE USER shatest@"localhost" IDENTIFIED WITH sha256_password')) { + die(sprintf("skip CREATE USER failed [%d] %s", $link->errno, $link->error)); +} + +// Password of length 52, more than twice the length of the scramble data to ensure scramble is repeated correctly +if (!$link->query('SET PASSWORD FOR shatest@"%" = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"') || + !$link->query('SET PASSWORD FOR shatest@"localhost" = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"')) { + die(sprintf("skip SET PASSWORD failed [%d] %s", $link->errno, $link->error)); +} + +echo "nocache"; +?> +--FILE-- +connect_errno) { + printf("[001] [%d] %s\n", $link->connect_errno, $link->connect_error); +} else { + if (!$res = $link->query("SELECT USER()")) + printf("[002] [%d] %s\n", $link->errno, $link->error); + + if (!$row = mysqli_fetch_assoc($res)) { + printf("[003] [%d] %s\n", $link->errno, $link->error); + } + + if (!is_string($row['USER()']) || !str_starts_with($row['USER()'], 'shatest')) { + printf("[004] Expecting 1 got %s/'%s'", gettype($row['USER()']), $row['USER()']); + } +} + +print "done!"; +?> +--CLEAN-- +query('DROP USER shatest'); +$link->query('DROP USER shatest@localhost'); +?> +--EXPECTF-- +done! diff --git a/ext/mysqlnd/mysqlnd_auth.c b/ext/mysqlnd/mysqlnd_auth.c index 6956440abdc..ea9755c982e 100644 --- a/ext/mysqlnd/mysqlnd_auth.c +++ b/ext/mysqlnd/mysqlnd_auth.c @@ -927,7 +927,10 @@ mysqlnd_sha256_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self char *xor_str = do_alloca(passwd_len + 1, use_heap); memcpy(xor_str, passwd, passwd_len); xor_str[passwd_len] = '\0'; - mysqlnd_xor_string(xor_str, passwd_len, (char *) auth_plugin_data, auth_plugin_data_len); + /* https://dev.mysql.com/doc/dev/mysql-server/latest/page_caching_sha2_authentication_exchanges.html + * This tells us that the nonce is 20 (==SCRAMBLE_LENGTH) bytes long. + * In a 5.5+ server we might get additional scramble data in php_mysqlnd_greet_read, not used by this authentication method. */ + mysqlnd_xor_string(xor_str, passwd_len, (char *) auth_plugin_data, SCRAMBLE_LENGTH); ret = mysqlnd_sha256_public_encrypt(conn, server_public_key, passwd_len, auth_data_len, xor_str); free_alloca(xor_str, use_heap); }