Issue #3457781 by catch, longwave, senscybersecurity, cmlara, cilefen, poker10, greggles, alexpott, ericgsmith, xjm: Maintenance pages leak sensitive environment information

(cherry picked from commit 48b0aea1d5)
(cherry picked from commit 97118bca05)
(cherry picked from commit 3ebfcc8919)
merge-requests/9229/merge
Alex Pott 2024-10-02 13:53:26 -07:00
parent bba4829900
commit c2063e23b2
No known key found for this signature in database
GPG Key ID: BDA67E7EE836E5CE
3 changed files with 47 additions and 17 deletions

View File

@ -147,7 +147,7 @@ function error_displayable($error = NULL) {
* Non-recoverable fatal errors cannot be logged by Drupal.
*/
function _drupal_log_error($error, $fatal = FALSE) {
$is_installer = InstallerKernel::installationAttempted();
$is_installer = InstallerKernel::installationAttempted() && \Drupal::hasContainer();
// Backtrace, exception and 'severity_level' are not valid replacement values
// for t().
@ -158,7 +158,7 @@ function _drupal_log_error($error, $fatal = FALSE) {
// When running inside the testing framework, we relay the errors
// to the tested site by the way of HTTP headers.
if (DRUPAL_TEST_IN_CHILD_SITE && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
if (defined('DRUPAL_TEST_IN_CHILD_SITE') && DRUPAL_TEST_IN_CHILD_SITE && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
_drupal_error_header($error['@message'], $error['%type'], $error['%function'], $error['%file'], $error['%line']);
}
@ -174,7 +174,7 @@ function _drupal_log_error($error, $fatal = FALSE) {
// implementations to use it.
\Drupal::logger('php')->log($severity, '%type: @message in %function (line %line of %file) @backtrace_string.', $error + ['backtrace' => $backtrace, 'exception' => $exception, 'severity_level' => $severity]);
}
catch (\Exception $e) {
catch (\Throwable) {
// We can't log, for example because the database connection is not
// available. At least try to log to PHP error log.
error_log(strtr('Failed to log error: ' . Error::DEFAULT_ERROR_MESSAGE . ' @backtrace_string', $error));
@ -223,12 +223,16 @@ function _drupal_log_error($error, $fatal = FALSE) {
}
// Attempt to reduce verbosity by removing DRUPAL_ROOT from the file path
// in the message. This does not happen for (false) security.
if (\Drupal::hasService('kernel')) {
$root_length = strlen(\Drupal::root());
if (substr($error['%file'], 0, $root_length) == \Drupal::root()) {
$error['%file'] = substr($error['%file'], $root_length + 1);
}
// in the message. This also prevents full path disclosure, see
// https://owasp.org/www-community/attacks/Full_Path_Disclosure.
try {
$root = \Drupal::root();
}
catch (\Throwable) {
$root = realpath(dirname(__DIR__, 2));
}
if (str_starts_with($error['%file'], $root)) {
$error['%file'] = substr($error['%file'], strlen($root) + 1);
}
// Check if verbose error reporting is on.
@ -244,14 +248,13 @@ function _drupal_log_error($error, $fatal = FALSE) {
}
else {
// With verbose logging, we will also include a backtrace.
// First trace is the error itself, already contained in the message.
// While the second trace is the error source and also contained in the
// message, the message doesn't contain argument values, so we output it
// once more in the backtrace.
array_shift($backtrace);
// Generate a backtrace containing only scalar argument values.
$error['@backtrace'] = Error::formatBacktrace($backtrace);
// Strip arguments from the backtrace.
$error['@backtrace'] = Error::formatBacktrace(array_map(function ($trace) {
unset($trace['args']);
return $trace;
}, $backtrace));
$message = new FormattableMarkup('<details class="error-with-backtrace"><summary>' . Error::DEFAULT_ERROR_MESSAGE . '</summary><pre class="backtrace">@backtrace</pre></details>', $error);
}
}
@ -268,8 +271,13 @@ function _drupal_log_error($error, $fatal = FALSE) {
'#title' => 'Error',
'#markup' => $message,
];
install_display_output($output, $GLOBALS['install_state']);
exit;
try {
install_display_output($output, $GLOBALS['install_state']);
exit;
}
catch (\Throwable) {
// The maintenance page failed, so fall back to a plain error message.
}
}
$response->setContent($message);

View File

@ -43,6 +43,11 @@ if (OpCodeCache::isEnabled() && !ini_get('opcache.save_comments')) {
exit();
}
// Set the Drupal custom error handler.
require_once $root_path . '/core/includes/errors.inc';
set_error_handler('_drupal_error_handler');
set_exception_handler('_drupal_exception_handler');
// Start the installer.
require_once $root_path . '/core/includes/install.core.inc';
install_drupal($class_loader);

View File

@ -68,4 +68,21 @@ class SystemAuthorizeTest extends BrowserTestBase {
$this->assertSession()->responseContains('core/misc/states.js');
}
/**
* Tests error handling in authorize.php.
*/
public function testError(): void {
$settings_filename = $this->siteDirectory . '/settings.php';
chmod($settings_filename, 0777);
$settings_php = file_get_contents($settings_filename);
$settings_php .= "\ndefine('SIMPLETEST_COLLECT_ERRORS', FALSE);\n";
$settings_php .= "\ntrigger_error('Test warning', E_USER_WARNING);\n";
file_put_contents($settings_filename, $settings_php);
$this->drupalGetAuthorizePHP();
$this->assertSession()->pageTextContains('User warning: Test warning');
$this->assertSession()->pageTextMatches('@line \d+ of sites/simpletest@');
}
}