Verified Commit 3ebfcc89 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3457781 by catch, longwave, senscybersecurity, cmlara, cilefen,...

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

(cherry picked from commit 48b0aea1)
(cherry picked from commit 97118bca)
parent 345597dc
Loading
Loading
Loading
Loading
Loading
+25 −17
Original line number Diff line number Diff line
@@ -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,9 +271,14 @@ function _drupal_log_error($error, $fatal = FALSE) {
          '#title' => 'Error',
          '#markup' => $message,
        ];
        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);
      $response->setStatusCode(500, '500 Service unavailable (with message)');
+5 −0
Original line number Diff line number Diff line
@@ -43,6 +43,11 @@
  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);
+17 −0
Original line number Diff line number Diff line
@@ -68,4 +68,21 @@ public function testFileTransferHooks(): void {
    $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@');
  }

}