Moved from security bug report: "This should be a pubic hardening since there is no direct exploit"
Risk: If someone has
* At least read-only access to wherever php is writing its error logs,
* And can cause Drupal to fail to connect to the database (or can just wait for a network event that causes this),
Then they can find the username and password Drupal is using to connect.
This is with Drupal 8.6.16
I’m fairly certain this is in just Drupal Core, but haven’t actually tried it with just a base install.
If Drupal fails to connect to the database, it logs to the standard error log: (sanitized our database and password info with XXXX)
PDOException: SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or
service not known in /var/www/html/core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php on line 79 #0 /var/www/html/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php(420): PDO->__construct('mysql:host=va-v...', 'XXXdevweb', 'XXXXXXXXXXXX', Array)\n#1 /var/www/html/core/lib/Drupal/Core/Database/Database.php(371): Drupal\\Core\\Database\\Driver\\mysql\\Connection::open(Array)\n#2 /var/www/html/core/lib/Drupal/Core/Database/Database.php(166): Drupal\\Core\\Database\\Database::openConnection('default', 'default')\n#3 [internal function]: Drupal\\Core\\Database\\Database::getConnection('default')\n#4 /var/www/html/core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php(79): call_user_func_array('Drupal\\\\Core\\\\Dat...', Array)\n#5 /var/www/html/core/lib/Drupal/Component/DependencyInjection/Container.php(171): Drupal\\Component\\DependencyInjection\\PhpArrayContainer->createService(Array, 'database')\n#6 /var/www/html/core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php(260): Drupal\\Component\\DependencyInjection\\Container->get('database', 1)\n#7 /var/www/html/core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php(62): Drupal\\Component\\DependencyInjection\\PhpArrayContainer->resolveServicesAndParameters(Array)\n#8 /var/www/html/core/lib/Drupal/Component/DependencyInjection/Container.php(171): Drupal\\Component\\DependencyInjection\\PhpArrayContainer->createService(Array, 'cache.container')\n#9 /var/www/html/core/lib/Drupal/Core/DrupalKernel.php(543): Drupal\\Component\\DependencyInjection\\Container->get('cache.container')\n#10 /var/www/html/core/lib/Drupal/Core/DrupalKernel.php(904): Drupal\\Core\\DrupalKernel->getCachedContainerDefinition()\n#11 /var/www/html/core/lib/Drupal/Core/DrupalKernel.php(476): Drupal\\Core\\DrupalKernel->initializeContainer()\n#12 /var/www/html/core/lib/Drupal/Core/DrupalKernel.php(692): Drupal\\Core\\DrupalKernel->boot()\n#13 /var/www/html/index.php(19): Drupal\\Core\\DrupalKernel->handle(Object(Symfony\\Component\\HttpFoundation\\Request))\n#14 {main}
Which includes both the username (XXXdevweb) and password (XXXXXXXXXXXX) in plain text.
The problem is in core/lib/Drupal/Core/Database/Driver/mysql/Connection.php in the catch for line 419. The default error handler prints the sensitive information.
try {
$pdo = new \PDO($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
}
catch (\PDOException $e) {
if ($e->getCode() == static::DATABASE_NOT_FOUND) {
throw new DatabaseNotFoundException($e->getMessage(), $e->getCode(), $e);
}
if ($e->getCode() == static::ACCESS_DENIED) {
throw new DatabaseAccessDeniedException($e->getMessage(), $e->getCode(), $e);
}
throw $e;
}
The below fixes the issue, but uses DatabaseAccessDeniedException() incorrectly, and shouldn’t be used, but a new exception handler like it would probably be the correct solution. I just don’t have the time to dig into things to identify what’s the correct resolution, and this works well enough for us.
diff a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
429c429
> throw $e;
---
< throw new DatabaseAccessDeniedException($e->getMessage(), $e->getCode(), $e);
--
Thanks,
Billy Arnold
Senior Systems Administrator
Virginia Interactive / VI
Issue fork drupal-3061567
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
cilefen commentedComment #3
cilefen commentedComment #10
Silicon.Valet commentedWrapping the host lookup fail exception does the needfuls (for me anyway). but it does beg the question about why we allow the bare PDOexception to be rethrown when
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%...
says "This class will always wrap a PDOException."
/me looks at actual code and determines that is a lie
I didn't address that here, because I feel like that probably requires some discussion. But my PR adds the DrupalHostLookupException and catches the case.
Comment #11
Silicon.Valet commentedAfter consulting https://dev.mysql.com/doc/mysql-errors/8.0/en/client-error-reference.html
I see that 2002 is a more general connect failure, so I'm renaming it.
Searching logs I see that 2006 (connection dropped) also leaks credentials, so I'm addressing that in an update to the PR as well.
Comment #12
longwaveThanks for working on this!
The testbot doesn't like one of your file permissions:
Also I think it would be good if we could add tests for these cases, if possible. Not sure how we could simulate or force "gone away" in a test but we should be able to force a connection failure with some known bad credentials.
Comment #13
bobbygryzyngerUploading versions of the patch for currently supported versions of Core.
Comment #14
bobbygryzyngerFixing patch paths.
Comment #15
Silicon.Valet commentedRegarding creating tests, does the test setup test against MySQL? These are driver specific errors. I'd also be wary of going down this cascade as there are not tests for the /existing/ thrown exceptions.
Comment #16
longwaveCore tests run on all our supported database drivers, but individual tests can opt to skip if they are not running on a relevant driver.
\Drupal\FunctionalTests\Bootstrap\UncaughtExceptionTest::testLostDatabaseConnection()already tests for theDatabaseAccessDeniedExceptioncase on both MySQL and Postgres, so I think it should be possible to copy and modify this to testDatabaseConnectionErrorExceptionas well? Not sure aboutDatabaseGoneAwayExceptionthough, unless we can do something like set a very short timeout and cause it to be exceeded.Comment #19
jordanpagewhite commentedRe-rolled for the 9.4.x branch
Comment #21
alexdmccabeRe-rolled for 10.0.x. Hopefully this could get committed to 10.0 instead of 10.1.
Comment #22
alexdmccabeGot the patch paths wrong.
Comment #23
alexdmccabeOkay, never mind, I don't know why this isn't applying.
Comment #24
alexdmccabeShould apply, but the interdiff from #19 fails.
Comment #25
jordanpagewhite commentedThis issue will be resolved for applications running PHP 8.2 due to the #[\SensitiveParameter] attribute being used in the PDO constructor. Please see my comment on https://www.drupal.org/project/drupal/issues/3296293#comment-14872664 for more context.
Comment #26
cilefen commentedBecause PHP 8.2 has a natural and risk-free solution to the problem I suggest we close this as a duplicate of #3296293: Apply SensitiveParameter attribute and move credit there.
Comment #27
solideogloria commentedComment #29
krisahil commentedThe error logging for mysql error 2002 (connection refused) has been fixed in https://www.drupal.org/project/drupal/issues/2610858, released in Drupal core 10.0.6.
Here's a re-roll of the current issue's patch that includes error code 2006 (server gone away).
Comment #30
solideogloria commentedI agree that this should be closed as a duplicate of #3296293: Apply SensitiveParameter attribute
Comment #31
krisahil commentedRe-rolled patch against Drupal 10.2.
Comment #32
solideogloria commentedThe patch is not needed in Drupal 10.1+, because credentials are not logged when an exception occurs. Drupal uses the
#[\SensitiveParameter]attribute.If you think the above patch code should still be added, it probably needs to be in another issue, as this issue is closed, and the problem that was reported has already been fixed.
Comment #33
krisahil commentedThanks for pointing that out. However, that attribute only takes effect if the site is hosted on PHP 8.2+ (IIUC). I am maintaining a site on PHP 8.1, so I need this patch, until we upgrade to PHP 8.2
Comment #34
solideogloria commentedAh, that's right. My mistake.