Skip to content

MySQLi: SSL certificate verification fails (port doubled) #8978

Open
@killerbees19

Description

@killerbees19

Obviously it's possible to use a format like example.net:3306 as host argument for mysqli_real_connect(). This works, but it has some serious issues with SSL certificate verification.

Maybe this bug is not limited to MySQLi.

Prerequisites

  • MySQL or MariaDB server with SSL enabled
  • Your own CA and certs/keys for server and client
  • Working MySQL account with REQUIRE SSL

Example

This short example demonstrates the bug:

<?php

$mysql = mysqli_init();
$host = 'example.net:3306';
$flags = MYSQLI_CLIENT_SSL;
mysqli_ssl_set($mysql, 'x509.key', 'x509.pem', 'x509.ca', null, null);
mysqli_real_connect($mysql, $host, 'username', 'password', null, null, null, $flags);

PHP 8.1 output

PHP Warning:  mysqli_real_connect(): Cannot connect to MySQL by using SSL in …
PHP Fatal error:  Uncaught mysqli_sql_exception:  (trying to connect via (null)) in …

Null? Ok, let's retry with older PHP releases…

PHP 7.0 output

PHP Warning:  mysqli_real_connect(): Cannot connect to MySQL by using SSL in …
PHP Warning:  mysqli_real_connect(): [2002]  (trying to connect via tcp://example.net:3306:3306) in …
PHP Warning:  mysqli_real_connect(): (HY000/2002):  in …

Note the doubled port! This is not a C&P error.

Workarounds

Removing the port from the host argument is a possible workaround.

<?php

$mysql = mysqli_init();
$host = 'example.net';
$flags = MYSQLI_CLIENT_SSL;
mysqli_ssl_set($mysql, 'x509.key', 'x509.pem', 'x509.ca', null, null);
mysqli_real_connect($mysql, $host, 'username', 'password', null, 3306, null, $flags);

Using MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT is another workaround, but that's not a good solution.

<?php

$mysql = mysqli_init();
$host = 'example.net:3306';
$flags = MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
mysqli_ssl_set($mysql, 'x509.key', 'x509.pem', 'x509.ca', null, null);
mysqli_real_connect($mysql, $host, 'username', 'password', null, null, null, $flags);

Not using mysqli_ssl_set() is one more workaround, but now we don't have client certificates anymore.

<?php

$mysql = mysqli_init();
$host = 'example.net:3306';
$flags = MYSQLI_CLIENT_SSL;
mysqli_real_connect($mysql, $host, 'username', 'password', null, null, null, $flags);

Conclusion

I think only the first port occurrence in the connection string will be used:

mysqli_real_connect($mysql, 'example.net:3301', 'username', 'password', null, 3302, null, MYSQLI_CLIENT_SSL);

Results internally in tcp://example.net:3301:3302 and opens a TCP connection to example.net:3301. Now the CommonName (or SAN) of the certificate will be checked against example.net:3301.

Suggestions

Some personal ideas…

  • Don't internally add a port to the connection string, if it already contains a port. – or –
  • Remove port suffix from host if dedicated port argument isn't null. – or –
  • Extract the port from host argument and remove it. – or –
  • Ignore port suffix at certificate verification.

PHP Version

PHP 8.1.7 with mysqlnd (deb.sury.org)

Operating System

Debian 11 (Bullseye)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions