1<?php 2 3function my_mysqli_data_fields(): array 4{ 5 return [ 6 'intval' => [ 7 'type' => '03', 8 'charset' => '3f00', 9 'length' => '0b000000', 10 'flags' => '0110', 11 'decimal' => '00', 12 'query_data_packet_length' => '080000', 13 'query_data_value' => '023134', 14 'stmt_data_packet_length' => '0b0000', 15 'stmt_data_value' => '0e000000' 16 ], 17 'fltval' => [ 18 'type' => '04', 19 'charset' => '3f00', 20 'length' => '0c000000', 21 'flags' => '0110', 22 'decimal' => '1f', 23 'query_data_packet_length' => '090000', 24 'query_data_value' => '03322e33', 25 'stmt_data_packet_length' => '0b0000', 26 'stmt_data_value' => '33331340', 27 ], 28 'dblval' => [ 29 'type' => '05', 30 'charset' => '3f00', 31 'length' => '16000000', 32 'flags' => '0110', 33 'decimal' => '1f', 34 'query_data_packet_length' => '090000', 35 'query_data_value' => '03312e32', 36 'stmt_data_packet_length' => '0f0000', 37 'stmt_data_value' => '333333333333f33f' 38 ], 39 'datval' => [ 40 'type' => '0a', 41 'charset' => '3f00', 42 'length' => '0a000000', 43 'flags' => '8110', 44 'decimal' => '00', 45 'query_data_packet_length' => '100000', 46 'query_data_value' => '0a323031342d31322d3135', 47 'stmt_data_packet_length' => '0c0000', 48 'stmt_data_value' => '04de070c0f' 49 ], 50 'timval' => [ 51 'type' => '0b', 52 'charset' => '3f00', 53 'length' => '0a000000', 54 'flags' => '8110', 55 'decimal' => '00', 56 'query_data_packet_length' => '0e0000', 57 'query_data_value' => '0831333a30303a3032', 58 'stmt_data_packet_length' => '100000', 59 'stmt_data_value' => '080000000000150801' 60 ], 61 'dtival' => [ 62 'type' => '0c', 63 'charset' => '3f00', 64 'length' => '13000000', 65 'flags' => '8110', 66 'decimal' => '00', 67 'query_data_packet_length' => '190000', 68 'query_data_value' => '13323031342d31322d31362031333a30303a3031', 69 'stmt_data_packet_length' => '0f0000', 70 'stmt_data_value' => '07de070c100d0001' 71 ], 72 'bitval' => [ 73 'type' => '10', 74 'charset' => '3f00', 75 'length' => '40000000', 76 'flags' => '2110', 77 'decimal' => '00', 78 'query_data_packet_length' => '0e0000', 79 'query_data_value' => '080808080808080808', 80 'stmt_data_packet_length' => '100000', 81 'stmt_data_value' => '080808080808080808' 82 ], 83 'strval' => [ 84 'type' => 'fd', 85 'charset' => 'e000', 86 'length' => 'c8000000', 87 'flags' => '0110', 88 'decimal' => '00', 89 'query_data_packet_length' => '0a0000', 90 'query_data_value' => '0474657374', 91 'stmt_data_packet_length' => '0c0000', 92 'stmt_data_value' => '0474657374' 93 ], 94 ]; 95} 96 97function my_mysqli_data_field(string $field): array 98{ 99 $fields = my_mysqli_data_fields(); 100 if (!isset($fields[$field])) { 101 throw new Exception("Unknown field $field"); 102 } 103 return $fields[$field]; 104} 105 106 107 108class my_mysqli_fake_packet_item 109{ 110 public function __construct(public string|null $name, public string $value, public bool $is_hex = true) 111 { 112 } 113} 114 115class my_mysqli_fake_packet 116{ 117 private array $data = array(); 118 119 public function __get(string $name) 120 { 121 foreach ($this->data as $item) { 122 if ($item->name === $name) { 123 return $item->value; 124 } 125 } 126 return null; 127 } 128 129 public function __set(string $name, string|my_mysqli_fake_packet_item $value) 130 { 131 if ($value instanceof my_mysqli_fake_packet_item) { 132 if ($value->name === null) { 133 $value->name = $name; 134 } 135 } else { 136 $value = new my_mysqli_fake_packet_item($name, $value, true); 137 } 138 139 for ($i = 0; $i < count($this->data); $i++) { 140 if ($this->data[$i]->name === $name) { 141 $this->data[$i] = $value; 142 return; 143 } 144 } 145 146 $this->data[] = $value; 147 } 148 149 public function to_bytes(): string 150 { 151 $bytes = ''; 152 foreach ($this->data as $item) { 153 $bytes .= $item->is_hex ? hex2bin($item->value) : $item->value; 154 } 155 return $bytes; 156 } 157} 158 159class my_mysqli_fake_packet_generator 160{ 161 public static function create_packet_item(int|string $value, bool $is_hex = false, string $format = 'v'): my_mysqli_fake_packet_item 162 { 163 if (is_string($value)) { 164 $packed_value = $value; 165 } else { 166 $packed_value = pack($format, $value); 167 } 168 return new my_mysqli_fake_packet_item(null, $packed_value, $is_hex); 169 } 170 171 public function server_ok(): my_mysqli_fake_packet 172 { 173 $packet = new my_mysqli_fake_packet(); 174 $packet->packet_length = "070000"; 175 $packet->packet_number = "02"; 176 $packet->header = "00"; // OK 177 $packet->affected_rows = "00"; 178 $packet->last_insert_id = "00"; 179 $packet->server_status = "0200"; 180 $packet->warning_count = "0000"; 181 return $packet; 182 } 183 184 public function server_greetings(): my_mysqli_fake_packet 185 { 186 $packet = new my_mysqli_fake_packet(); 187 $packet->packet_length = "580000"; 188 $packet->packet_number = "00"; 189 $packet->proto_version = "0a"; 190 $packet->version = self::create_packet_item('5.5.5-10.5.18-MariaDB' . chr(0)); 191 $packet->thread_id = "03000000"; 192 $packet->salt = "473e3f6047257c67"; 193 $packet->filler = "00"; 194 $packet->server_capabilities = self::create_packet_item(0b1111011111111110); 195 $packet->server_character_set = "08"; 196 $packet->server_status = self::create_packet_item(0b000000000000010); 197 $packet->extended_server_capabilities = self::create_packet_item(0b1000000111111111); 198 $packet->auth_plugin = "15"; 199 $packet->unused = "000000000000"; 200 $packet->mariadb_extended_server_capabilities = self::create_packet_item(0b1111, false, 'V'); 201 $packet->mariadb_extended_server_capabilities_salt = "6c6b55463f49335f686c643100"; 202 $packet->mariadb_extended_server_capabilities_auth_plugin = self::create_packet_item('mysql_native_password'); 203 204 return $packet; 205 } 206 207 public function server_tabular_query_response(): array 208 { 209 $qr1 = new my_mysqli_fake_packet(); 210 $qr1->packet_length = "010000"; 211 $qr1->packet_number = "01"; 212 $qr1->field_count = "01"; 213 214 $qr2 = new my_mysqli_fake_packet(); 215 $qr2->packet_length = "190000"; 216 $qr2->packet_number = "02"; 217 $qr2->catalog_length_plus_name = "0164"; 218 $qr2->db_length_plus_name = "0164"; 219 $qr2->table_length_plus_name = "0164"; 220 $qr2->original_t = "0164"; 221 $qr2->name_length_plus_name = "0164"; 222 $qr2->original_n = "0164"; 223 $qr2->canary = "0c"; 224 $qr2->charset = "3f00"; 225 $qr2->length = "0b000000"; 226 $qr2->type = "03"; 227 $qr2->flags = "0350"; 228 $qr2->decimals = "000000"; 229 230 $qr3 = new my_mysqli_fake_packet(); 231 $qr3->full = "05000003fe00002200"; 232 233 $qr4 = new my_mysqli_fake_packet(); 234 $qr4->full = "0400000401350174"; 235 236 $qr5 = new my_mysqli_fake_packet(); 237 $qr5->full = "05000005fe00002200"; 238 239 return [$qr1, $qr2, $qr3, $qr4, $qr5]; 240 } 241 242 public function server_upsert_query_response(): array 243 { 244 $qr1 = new my_mysqli_fake_packet(); 245 $qr1->packet_length = "010000"; 246 $qr1->packet_number = "01"; 247 $qr1->field_count = "00"; // UPSERT 248 $qr1->affected_rows = "00"; 249 $qr1->affected_rows = "00"; 250 $qr1->last_insert_id = "00"; 251 $qr1->server_status = "0000"; 252 $qr1->warning_count = "0000"; 253 $qr1->len = "01"; 254 $qr1->filename = "65"; 255 $qr1->packet_length = sprintf("%02x0000", strlen($qr1->to_bytes())-4); 256 257 return [$qr1]; 258 } 259 260 public function server_stmt_prepare_response_start($num_field): my_mysqli_fake_packet 261 { 262 $pr1 = new my_mysqli_fake_packet(); 263 $pr1->packet_length = "0c0000"; 264 $pr1->packet_number = "01"; 265 $pr1->response_code = '00'; // OK 266 $pr1->statement_id = '01000000'; 267 $pr1->num_fields = $num_field; 268 $pr1->num_params = '0000'; 269 $pr1->filler = '00'; 270 $pr1->warnings = '0000'; 271 272 return $pr1; 273 } 274 275 public function server_stmt_prepare_response_end($packer_number): my_mysqli_fake_packet 276 { 277 $pr3 = new my_mysqli_fake_packet(); 278 $pr3->packet_length = "050000"; 279 $pr3->packet_number = $packer_number; 280 $pr3->packet_type = 'fe'; // EOF 281 $pr3->warnings = '0000'; 282 $pr3->server_status = '0200'; 283 284 return $pr3; 285 } 286 287 public function server_stmt_prepare_items_response(): array 288 { 289 $pr1 = $this->server_stmt_prepare_response_start('0100'); 290 291 $pr2 = new my_mysqli_fake_packet(); 292 $pr2->packet_length = "300000"; 293 $pr2->packet_number = "02"; 294 $pr2->catalogue_len = '03'; 295 $pr2->catalogue = '646566'; // def 296 $pr2->db_len = '08'; 297 $pr2->db = '7068705f74657374'; // php_test 298 $pr2->table_len = '05'; 299 $pr2->table = '6974656d73'; // items 300 $pr2->orig_table_len = '05'; 301 $pr2->orig_table = '6974656d73'; // items 302 $pr2->name_len = '04'; 303 $pr2->name = '6974656d'; 304 $pr2->orig_name_len = '04'; 305 $pr2->orig_name = '6974656d'; 306 $pr2->something = '0c'; 307 $pr2->charset = 'e000'; 308 $pr2->length = 'c8000000'; 309 $pr2->field_type = 'fd'; // FIELD_TYPE_VAR_STRING 310 $pr2->flags = '0110'; 311 $pr2->decimal = '00'; 312 $pr2->padding = '0000'; 313 314 $pr3 = $this->server_stmt_prepare_response_end('03'); 315 316 return [$pr1, $pr2, $pr3]; 317 } 318 319 public function server_stmt_prepare_data_response_field($packet_number, $field_name): my_mysqli_fake_packet 320 { 321 if (strlen($field_name) != 6) { 322 throw new Exception("Invalid field length - only 6 is allowed"); 323 } 324 325 $field = my_mysqli_data_field($field_name); 326 327 $pr = new my_mysqli_fake_packet(); 328 $pr->packet_length = "320000"; 329 $pr->packet_number = $packet_number; 330 $pr->catalogue_len = '03'; 331 $pr->catalogue = bin2hex('def'); 332 $pr->db_len = '08'; 333 $pr->db = bin2hex('php_test'); 334 $pr->table_len = '04'; 335 $pr->table = bin2hex('data'); 336 $pr->orig_table_len = '04'; 337 $pr->orig_table = bin2hex('data'); 338 $pr->name_len = '06'; 339 $pr->name = bin2hex($field_name); 340 $pr->orig_name_len = '06'; 341 $pr->orig_name = bin2hex($field_name); 342 $pr->something = '0c'; 343 $pr->charset = $field['charset']; 344 $pr->length = $field['length']; 345 $pr->field_type = $field['type']; 346 $pr->flags = $field['flags']; 347 $pr->decimal = $field['decimal']; 348 $pr->padding = '0000'; 349 350 return $pr; 351 } 352 353 public function server_stmt_prepare_data_response(string $field_name): array 354 { 355 $pr1 = $this->server_stmt_prepare_response_start('0200'); 356 357 $pr2 = $this->server_stmt_prepare_data_response_field('02', 'strval'); 358 $pr3 = $this->server_stmt_prepare_data_response_field('03', $field_name); 359 360 $pr4 = $this->server_stmt_prepare_response_end('04'); 361 362 return [$pr1, $pr2, $pr3, $pr4]; 363 } 364 365 public function server_stmt_execute_items_response(): array 366 { 367 $pr1 = new my_mysqli_fake_packet(); 368 $pr1->packet_length = "010000"; 369 $pr1->packet_number = "01"; 370 $pr1->num_fields = '01'; 371 372 $pr2 = new my_mysqli_fake_packet(); 373 $pr2->packet_length = "300000"; 374 $pr2->packet_number = "02"; 375 $pr2->catalogue_len = '03'; 376 $pr2->catalogue = '646566'; // def 377 $pr2->db_len = '08'; 378 $pr2->db = '7068705f74657374'; // php_test 379 $pr2->table_len = '05'; 380 $pr2->table = '6974656d73'; // items 381 $pr2->orig_table_len = '05'; 382 $pr2->orig_table = '6974656d73'; // items 383 $pr2->name_len = '04'; 384 $pr2->name = '6974656d'; 385 $pr2->orig_name_len = '04'; 386 $pr2->orig_name = '6974656d'; 387 $pr2->something = '0c'; 388 $pr2->charset = 'e000'; 389 $pr2->length = 'c8000000'; 390 $pr2->field_type = 'fd'; // FIELD_TYPE_VAR_STRING 391 $pr2->flags = '0110'; 392 $pr2->decimal = '00'; 393 $pr2->padding = '0000'; 394 395 $pr3 = new my_mysqli_fake_packet(); 396 $pr3->packet_length = "050000"; 397 $pr3->packet_number = "03"; 398 $pr3->packet_type = 'fe'; // EOF 399 $pr3->warnings = '0000'; 400 $pr3->server_status = '2200'; 401 402 $pr4 = new my_mysqli_fake_packet(); 403 $pr4->packet_length = "070000"; 404 $pr4->packet_number = "04"; 405 $pr4->packet_type = '00'; // OK 406 $pr4->affected_rows = '00'; 407 $pr4->row_data_len = '04'; 408 $pr4->row_data = '74657374'; // item 409 410 $pr5 = new my_mysqli_fake_packet(); 411 $pr5->full = '05000005fe00002200'; 412 413 return [$pr1, $pr2, $pr3, $pr4, $pr5]; 414 } 415 416 private function server_execute_data_response_start(string $field_name): array 417 { 418 $pr1 = new my_mysqli_fake_packet(); 419 $pr1->packet_length = "010000"; 420 $pr1->packet_number = "01"; 421 $pr1->num_fields = '02'; 422 423 $pr2 = new my_mysqli_fake_packet(); 424 $pr2->packet_length = "320000"; 425 $pr2->packet_number = "02"; 426 $pr2->catalogue_len = '03'; 427 $pr2->catalogue = '646566'; // def 428 $pr2->db_len = '08'; 429 $pr2->db = '7068705f74657374'; // php_test 430 $pr2->table_len = '04'; 431 $pr2->table = bin2hex('data'); 432 $pr2->orig_table_len = '04'; 433 $pr2->orig_table = bin2hex('data'); 434 $pr2->name_len = '06'; 435 $pr2->name = bin2hex('strval'); 436 $pr2->orig_name_len = '06'; 437 $pr2->orig_name = bin2hex('strval'); 438 $pr2->something = '0c'; 439 $pr2->charset = 'e000'; 440 $pr2->length = 'c8000000'; 441 $pr2->field_type = 'fd'; // FIELD_TYPE_VAR_STRING 442 $pr2->flags = '0110'; 443 $pr2->decimal = '00'; 444 $pr2->padding = '0000'; 445 446 $field = my_mysqli_data_field($field_name); 447 448 $pr3 = new my_mysqli_fake_packet(); 449 $pr3->packet_length = "320000"; 450 $pr3->packet_number = "03"; 451 $pr3->catalogue_len = '03'; 452 $pr3->catalogue = '646566'; // def 453 $pr3->db_len = '08'; 454 $pr3->db = '7068705f74657374'; // php_test 455 $pr3->table_len = '04'; 456 $pr3->table = bin2hex('data'); 457 $pr3->orig_table_len = '04'; 458 $pr3->orig_table = bin2hex('data'); 459 $pr3->name_len = '06'; 460 $pr3->name = bin2hex($field_name); 461 $pr3->orig_name_len = '06'; 462 $pr3->orig_name = bin2hex($field_name); 463 $pr3->something = '0c'; 464 $pr3->charset = $field['charset']; 465 $pr3->length = $field['length']; 466 $pr3->field_type = $field['type']; 467 $pr3->flags = $field['flags']; 468 $pr3->decimal = $field['decimal']; 469 $pr3->padding = '0000'; 470 471 $pr4 = new my_mysqli_fake_packet(); 472 $pr4->packet_length = "050000"; 473 $pr4->packet_number = "04"; 474 $pr4->packet_type = 'fe'; // EOF 475 $pr4->warnings = '0000'; 476 $pr4->server_status = '2200'; 477 478 return [$field, $pr1, $pr2, $pr3, $pr4]; 479 } 480 481 private function server_execute_data_response_end(): my_mysqli_fake_packet 482 { 483 $pr6 = new my_mysqli_fake_packet(); 484 $pr6->packet_length = '050000'; 485 $pr6->packet_number = "06"; 486 $pr6->packet_type = 'fe'; // EOF 487 $pr6->warnings = '0000'; 488 $pr6->server_status = '2200'; 489 490 return $pr6; 491 } 492 493 public function server_stmt_execute_data_response(string $field_name): array 494 { 495 [$field, $pr1, $pr2, $pr3, $pr4] = $this->server_execute_data_response_start($field_name); 496 497 $pr5 = new my_mysqli_fake_packet(); 498 $pr5->packet_length = $field['stmt_data_packet_length']; 499 $pr5->packet_number = "05"; 500 $pr5->packet_type = '00'; // OK 501 $pr5->affected_rows = '00'; 502 $pr5->row_field1_len = '04'; 503 $pr5->row_field1_data = '74657374'; // test 504 $pr5->row_field2 = $field['stmt_data_value']; 505 506 return [$pr1, $pr2, $pr3, $pr4, $pr5, $this->server_execute_data_response_end()]; 507 } 508 509 public function server_query_execute_data_response(string $field_name): array 510 { 511 [$field, $pr1, $pr2, $pr3, $pr4] = $this->server_execute_data_response_start($field_name); 512 513 $pr5 = new my_mysqli_fake_packet(); 514 $pr5->packet_length = $field['query_data_packet_length']; 515 $pr5->packet_number = "05"; 516 $pr5->row_field1_len = '04'; 517 $pr5->row_field1_data = '74657374'; // test 518 $pr5->row_field2 = $field['query_data_value']; 519 520 return [$pr1, $pr2, $pr3, $pr4, $pr5, $this->server_execute_data_response_end()]; 521 } 522} 523 524class my_mysqli_fake_server_conn 525{ 526 private $conn; 527 public $packet_generator; 528 529 public function __construct($socket) 530 { 531 $this->packet_generator = new my_mysqli_fake_packet_generator(); 532 $this->conn = stream_socket_accept($socket); 533 if ($this->conn) { 534 fprintf(STDERR, "[*] Connection established\n"); 535 } else { 536 fprintf(STDERR, "[*] Failed to establish connection\n"); 537 } 538 } 539 540 public function packets_to_bytes(array $packets): string 541 { 542 return implode('', array_map(fn($s) => $s->to_bytes(), $packets)); 543 } 544 545 public function send($payload, $message = null): void 546 { 547 if ($message) { 548 fprintf(STDERR, "[*] Sending - %s: %s\n", $message, bin2hex($payload)); 549 } 550 fwrite($this->conn, $payload); 551 } 552 553 public function read($bytes_len = 1024) 554 { 555 // wait 10ms to fill the buffer 556 usleep(10000); 557 $data = fread($this->conn, $bytes_len); 558 if ($data) { 559 fprintf(STDERR, "[*] Received: %s\n", bin2hex($data)); 560 } 561 } 562 563 public function close() 564 { 565 fclose($this->conn); 566 } 567 568 public function send_server_greetings() 569 { 570 $this->send($this->packet_generator->server_greetings()->to_bytes(), "Server Greeting"); 571 } 572 573 public function send_server_ok() 574 { 575 $this->send($this->packet_generator->server_ok()->to_bytes(), "Server OK"); 576 } 577 578 public function send_server_tabular_query_response(): void 579 { 580 $packets = $this->packet_generator->server_tabular_query_response(); 581 $this->send($this->packets_to_bytes($packets), "Tabular response"); 582 } 583 584 public function send_server_stmt_prepare_items_response(): void 585 { 586 $packets = $this->packet_generator->server_stmt_prepare_items_response(); 587 $this->send($this->packets_to_bytes($packets), "Stmt prepare items"); 588 } 589 590 591 public function send_server_stmt_prepare_data_response(string $field_name): void 592 { 593 $packets = $this->packet_generator->server_stmt_prepare_data_response($field_name); 594 $this->send($this->packets_to_bytes($packets), "Stmt prepare data $field_name"); 595 } 596 597 public function send_server_stmt_execute_items_response(): void 598 { 599 $packets = $this->packet_generator->server_stmt_execute_items_response(); 600 $this->send($this->packets_to_bytes($packets), "Stmt execute items"); 601 } 602 603 public function send_server_stmt_execute_data_response(string $field_name): void 604 { 605 $packets = $this->packet_generator->server_stmt_execute_data_response($field_name); 606 $this->send($this->packets_to_bytes($packets), "Stmt execute data $field_name"); 607 } 608 609 public function send_server_query_execute_data_response(string $field_name): void 610 { 611 $packets = $this->packet_generator->server_query_execute_data_response($field_name); 612 $this->send($this->packets_to_bytes($packets), "Query execute data $field_name"); 613 } 614} 615 616class my_mysqli_fake_server_process 617{ 618 public function __construct(private $process, private array $pipes) {} 619 620 public function terminate(bool $wait = false) 621 { 622 if ($wait) { 623 $this->wait(); 624 } 625 proc_terminate($this->process); 626 } 627 628 public function wait() 629 { 630 echo fgets($this->pipes[1]); 631 } 632} 633 634function my_mysqli_test_tabular_response_def_over_read(my_mysqli_fake_server_conn $conn): void 635{ 636 $rh = $conn->packet_generator->server_tabular_query_response(); 637 638 // Length of the packet is modified to include the next added data 639 $rh[1]->packet_length = "1e0000"; 640 641 // We add a length field encoded on 4 bytes which evaluates to 65536. If the process crashes because 642 // the heap has been overread, lower this value. 643 $rh[1]->extra_def_size = "fd000001"; # 65536 644 645 // Filler 646 $rh[1]->extra_def_data = "aa"; 647 648 $trrh = $conn->packets_to_bytes($rh); 649 650 $conn->send_server_greetings(); 651 $conn->read(); 652 $conn->send_server_ok(); 653 $conn->read(); 654 $conn->send($trrh, "Malicious Tabular Response [Extract heap through buffer over-read]"); 655 $conn->read(65536); 656} 657 658function my_mysqli_test_upsert_response_filename_over_read(my_mysqli_fake_server_conn $conn): void 659{ 660 $rh = $conn->packet_generator->server_upsert_query_response(); 661 662 // Set extra length to overread 663 $rh[0]->len = "fa"; 664 665 $trrh = $conn->packets_to_bytes($rh); 666 667 $conn->send_server_greetings(); 668 $conn->read(); 669 $conn->send_server_ok(); 670 $conn->read(); 671 $conn->send($trrh, "Malicious Tabular Response [Extract heap through buffer over-read]"); 672 $conn->read(65536); 673} 674 675function my_mysqli_test_auth_response_message_over_read(my_mysqli_fake_server_conn $conn): void 676{ 677 $p = $conn->packet_generator->server_ok(); 678 $p->packet_length = "090000"; 679 $p->message_len = "fcff"; 680 681 $conn->send_server_greetings(); 682 $conn->read(); 683 $conn->send($p->to_bytes(), "Malicious OK Auth Response [Extract heap through buffer over-read]"); 684 $conn->read(); 685} 686 687function my_mysqli_test_stmt_response_row_over_read_string(my_mysqli_fake_server_conn $conn): void 688{ 689 $rh = $conn->packet_generator->server_stmt_execute_items_response(); 690 691 // Set extra length to overread 692 $rh[3]->row_data_len = "fa"; 693 694 $conn->send_server_greetings(); 695 $conn->read(); 696 $conn->send_server_ok(); 697 $conn->read(); 698 $conn->send_server_stmt_prepare_items_response(); 699 $conn->read(); 700 $conn->send($conn->packets_to_bytes($rh), "Malicious Stmt Response for items [Extract heap through buffer over-read]"); 701 $conn->read(65536); 702} 703 704function my_mysqli_test_stmt_response_row_over_read_two_fields( 705 my_mysqli_fake_server_conn $conn, 706 string $field_name, 707 string $row_field1_len = '06' 708): void { 709 $rh = $conn->packet_generator->server_stmt_execute_data_response($field_name); 710 711 // Set extra length to overread by two bytes 712 $rh[4]->row_field1_len = $row_field1_len; 713 714 $conn->send_server_greetings(); 715 $conn->read(); 716 $conn->send_server_ok(); 717 $conn->read(); 718 $conn->send_server_stmt_prepare_data_response($field_name); 719 $conn->read(); 720 $conn->send( 721 $conn->packets_to_bytes($rh), 722 "Malicious Stmt Response for data $field_name [Extract heap through buffer over-read]" 723 ); 724 $conn->read(65536); 725} 726 727function my_mysqli_test_stmt_response_row_over_read_int(my_mysqli_fake_server_conn $conn): void 728{ 729 my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'intval'); 730} 731 732function my_mysqli_test_stmt_response_row_over_read_float(my_mysqli_fake_server_conn $conn): void 733{ 734 my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'fltval'); 735} 736 737function my_mysqli_test_stmt_response_row_over_read_double(my_mysqli_fake_server_conn $conn): void 738{ 739 my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'dblval'); 740} 741 742function my_mysqli_test_stmt_response_row_over_read_date(my_mysqli_fake_server_conn $conn): void 743{ 744 my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'datval'); 745} 746 747function my_mysqli_test_stmt_response_row_over_read_time(my_mysqli_fake_server_conn $conn): void 748{ 749 my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'timval', '0c'); 750} 751 752function my_mysqli_test_stmt_response_row_over_read_datetime(my_mysqli_fake_server_conn $conn): void 753{ 754 my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'dtival'); 755} 756 757function my_mysqli_test_stmt_response_row_no_space(my_mysqli_fake_server_conn $conn): void 758{ 759 my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'strval', '09'); 760} 761 762function my_mysqli_test_stmt_response_row_over_read_bit(my_mysqli_fake_server_conn $conn): void 763{ 764 my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'bitval'); 765} 766 767function my_mysqli_test_stmt_response_row_read_two_fields(my_mysqli_fake_server_conn $conn): void 768{ 769 $conn->send_server_greetings(); 770 $conn->read(); 771 $conn->send_server_ok(); 772 $conn->read(); 773 $field_names = array_keys(my_mysqli_data_fields()); 774 foreach ($field_names as $field_name) { 775 $conn->send_server_stmt_prepare_data_response($field_name); 776 $conn->read(65536); 777 $conn->send_server_stmt_execute_data_response($field_name); 778 $conn->read(65536); 779 } 780} 781 782function my_mysqli_test_query_response_row_length_overflow(my_mysqli_fake_server_conn $conn): void 783{ 784 $rh = $conn->packet_generator->server_query_execute_data_response('strval'); 785 786 // Set extra length to overread by two bytes 787 $rh[4]->row_field2 = 'fefefefefe'; 788 789 $conn->send_server_greetings(); 790 $conn->read(); 791 $conn->send_server_ok(); 792 $conn->read(); 793 $conn->send($conn->packets_to_bytes($rh), "Malicious Query Response for data strval field [length overflow]"); 794 $conn->read(65536); 795} 796 797function my_mysqli_test_query_response_row_read_two_fields(my_mysqli_fake_server_conn $conn): void 798{ 799 $conn->send_server_greetings(); 800 $conn->read(); 801 $conn->send_server_ok(); 802 $conn->read(); 803 $field_names = array_keys(my_mysqli_data_fields()); 804 foreach ($field_names as $field_name) { 805 $conn->send_server_query_execute_data_response($field_name); 806 $conn->read(); 807 } 808} 809 810function run_fake_server(string $test_function, $port = 33305): void 811{ 812 $address = '127.0.0.1'; 813 814 $socket = @stream_socket_server("tcp://$address:$port", $errno, $errstr); 815 if (!$socket) { 816 die("Failed to create socket: $errstr ($errno)\n"); 817 } 818 echo "[*] Server started\n"; 819 820 try { 821 $conn = new my_mysqli_fake_server_conn($socket); 822 $test_function_name = 'my_mysqli_test_' . $test_function; 823 call_user_func($test_function_name, $conn); 824 $conn->close(); 825 } catch (Exception $e) { 826 fprintf(STDERR, "[!] Exception: " . $e->getMessage() . "\n"); 827 } 828 829 fclose($socket); 830 831 echo "[*] Server finished\n"; 832} 833 834 835function run_fake_server_in_background($test_function, $port = 33305): my_mysqli_fake_server_process 836{ 837 $command = [PHP_BINARY, '-n', __FILE__, 'mysqli_fake_server', $test_function, $port]; 838 839 $descriptorspec = array( 840 0 => array("pipe", "r"), 841 1 => array("pipe", "w"), 842 2 => STDERR, 843 ); 844 845 $process = proc_open($command, $descriptorspec, $pipes); 846 847 if (is_resource($process)) { 848 return new my_mysqli_fake_server_process($process, $pipes); 849 } else { 850 throw new Exception("Failed to start server process"); 851 } 852} 853 854if (isset($argv) && $argc > 2 && $argv[1] == 'mysqli_fake_server') { 855 run_fake_server($argv[2], $argv[3] ?? '33305'); 856} 857