Skip to content

Commit c910401

Browse files
committed
Guard against short server scramble in sha256_password auth
mysqlnd_sha256_auth_get_auth_data() XORs SCRAMBLE_LENGTH bytes of the server-supplied scramble into the password without checking the scramble is at least that long, unlike the native and caching_sha2 plugins which reject a short scramble with CR_MALFORMED_PACKET. A server reporting a scramble shorter than 20 bytes shrinks the heap buffer the scramble is copied into, so the XOR reads past it. Add the same length guard the sibling plugins use.
1 parent d8e7418 commit c910401

3 files changed

Lines changed: 45 additions & 0 deletions

File tree

ext/mysqli/tests/fake_server.inc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,20 @@ function my_mysqli_test_query_response_row_read_two_fields(my_mysqli_fake_server
818818
}
819819
}
820820

821+
function my_mysqli_test_sha256_password_short_scramble(my_mysqli_fake_server_conn $conn): void
822+
{
823+
$conn->send_server_greetings();
824+
$conn->read();
825+
// AuthSwitchRequest (0xfe) to sha256_password carrying a scramble shorter
826+
// than SCRAMBLE_LENGTH (20). mnd_emalloc sizes the heap copy to this
827+
// length, so the sha256 plugin's 20-byte XOR over-reads it without the
828+
// length guard.
829+
$payload = "\xfe" . "sha256_password\x00" . str_repeat("\x41", 8);
830+
$header = substr(pack('V', strlen($payload)), 0, 3) . "\x02";
831+
$conn->send($header . $payload, "Auth Switch Request [short sha256 scramble]");
832+
$conn->read();
833+
}
834+
821835
function run_fake_server(string $test_function, int|string $port = 0): int
822836
{
823837
$host = '127.0.0.1';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
mysqlnd sha256_password: reject server scramble shorter than 20 bytes
3+
--EXTENSIONS--
4+
mysqli
5+
mysqlnd
6+
openssl
7+
--FILE--
8+
<?php
9+
require_once 'fake_server.inc';
10+
11+
mysqli_report(MYSQLI_REPORT_OFF);
12+
13+
$process = run_fake_server_in_background('sha256_password_short_scramble');
14+
$process->wait();
15+
16+
$link = @new mysqli("127.0.0.1", "root", "", "", $process->getPort());
17+
printf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error());
18+
19+
$process->terminate();
20+
print "done!\n";
21+
?>
22+
--EXPECTF--
23+
[*] Server started on 127.0.0.1:%d
24+
%A
25+
[2027] The server sent wrong length for scramble
26+
done!

ext/mysqlnd/mysqlnd_auth.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,11 @@ mysqlnd_sha256_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self
908908
DBG_ENTER("mysqlnd_sha256_auth_get_auth_data");
909909
DBG_INF_FMT("salt(%zu)=[%.*s]", auth_plugin_data_len, (int) auth_plugin_data_len, auth_plugin_data);
910910

911+
if (auth_plugin_data_len < SCRAMBLE_LENGTH) {
912+
SET_CLIENT_ERROR(conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE, "The server sent wrong length for scramble");
913+
DBG_ERR_FMT("The server sent wrong length for scramble %zu. Expected %u", auth_plugin_data_len, SCRAMBLE_LENGTH);
914+
DBG_RETURN(NULL);
915+
}
911916

912917
if (conn->vio->data->ssl) {
913918
DBG_INF("simple clear text under SSL");

0 commit comments

Comments
 (0)