Skip to content

Instantly share code, notes, and snippets.

@attila
Last active March 28, 2018 19:47
Show Gist options
  • Save attila/8f7fb4854ad891f646b1e034c05665b4 to your computer and use it in GitHub Desktop.
Save attila/8f7fb4854ad891f646b1e034c05665b4 to your computer and use it in GitHub Desktop.
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index 0428bd3..1814aa8 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -2360,6 +2360,10 @@ function _drupal_bootstrap_configuration() {
timer_start('page');
// Initialize the configuration, including variables from settings.php.
drupal_settings_initialize();
+
+ // Sanitize unsafe keys from the request.
+ require_once DRUPAL_ROOT . '/includes/request-sanitizer.inc';
+ DrupalRequestSanitizer::sanitize();
}
/**
@@ -3570,3 +3574,38 @@ function drupal_clear_opcode_cache($filepath) {
@apc_delete_file($filepath);
}
}
+
+/**
+ * Invokes trigger_error() with logging delayed until the end of the request.
+ *
+ * This is an alternative to PHP's trigger_error() function which can be used
+ * during low-level Drupal core operations that need to avoid being interrupted
+ * by a watchdog() call.
+ *
+ * Normally, Drupal's error handler calls watchdog() in response to a
+ * trigger_error() call. However, this invokes hook_watchdog() which can run
+ * arbitrary code. If the trigger_error() happens in the middle of an
+ * operation such as a rebuild operation which should not be interrupted by
+ * arbitrary code, that could potentially break or trigger the rebuild again.
+ * This function protects against that by delaying the watchdog() call until
+ * the end of the current page request.
+ *
+ * This is an internal function which should only be called by low-level Drupal
+ * core functions. It may be removed in a future Drupal 7 release.
+ *
+ * @param string $error_msg
+ * The error message to trigger. As with trigger_error() itself, this is
+ * limited to 1024 bytes; additional characters beyond that will be removed.
+ * @param int $error_type
+ * (optional) The type of error. This should be one of the E_USER family of
+ * constants. As with trigger_error() itself, this defaults to E_USER_NOTICE
+ * if not provided.
+ *
+ * @see _drupal_log_error()
+ */
+function _drupal_trigger_error_with_delayed_logging($error_msg, $error_type = E_USER_NOTICE) {
+ $delay_logging = &drupal_static(__FUNCTION__, FALSE);
+ $delay_logging = TRUE;
+ trigger_error($error_msg, $error_type);
+ $delay_logging = FALSE;
+}
diff --git a/includes/request-sanitizer.inc b/includes/request-sanitizer.inc
new file mode 100644
index 0000000..1daa6b5
--- /dev/null
+++ b/includes/request-sanitizer.inc
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @file
+ * Contains code for sanitizing user input from the request.
+ */
+
+/**
+ * Sanitizes user input from the request.
+ */
+class DrupalRequestSanitizer {
+
+ /**
+ * Tracks whether the request was already sanitized.
+ */
+ protected static $sanitized = FALSE;
+
+ /**
+ * Modifies the request to strip dangerous keys from user input.
+ */
+ public static function sanitize() {
+ if (!self::$sanitized) {
+ $whitelist = variable_get('sanitize_input_whitelist', array());
+ $log_sanitized_keys = variable_get('sanitize_input_logging', FALSE);
+
+ // Process query string parameters.
+ $get_sanitized_keys = array();
+ $_GET = self::stripDangerousValues($_GET, $whitelist, $get_sanitized_keys);
+ if ($log_sanitized_keys && $get_sanitized_keys) {
+ _drupal_trigger_error_with_delayed_logging(format_string('Potentially unsafe keys removed from query string parameters (GET): @keys', array('@keys' => implode(', ', $get_sanitized_keys))), E_USER_NOTICE);
+ }
+
+ // Process request body parameters.
+ $post_sanitized_keys = array();
+ $_POST = self::stripDangerousValues($_POST, $whitelist, $post_sanitized_keys);
+ if ($log_sanitized_keys && $post_sanitized_keys) {
+ _drupal_trigger_error_with_delayed_logging(format_string('Potentially unsafe keys removed from request body parameters (POST): @keys', array('@keys' => implode(', ', $post_sanitized_keys))), E_USER_NOTICE);
+ }
+
+ // Process cookie parameters.
+ $cookie_sanitized_keys = array();
+ $_COOKIE = self::stripDangerousValues($_COOKIE, $whitelist, $cookie_sanitized_keys);
+ if ($log_sanitized_keys && $cookie_sanitized_keys) {
+ _drupal_trigger_error_with_delayed_logging(format_string('Potentially unsafe keys removed from cookie parameters (COOKIE): @keys', array('@keys' => implode(', ', $cookie_sanitized_keys))), E_USER_NOTICE);
+ }
+
+ $request_sanitized_keys = array();
+ $_REQUEST = self::stripDangerousValues($_REQUEST, $whitelist, $request_sanitized_keys);
+
+ self::$sanitized = TRUE;
+ }
+ }
+
+ /**
+ * Strips dangerous keys from the provided input.
+ *
+ * @param mixed $input
+ * The input to sanitize.
+ * @param string[] $whitelist
+ * An array of keys to whitelist as safe.
+ * @param string[] $sanitized_keys
+ * An array of keys that have been removed.
+ *
+ * @return mixed
+ * The sanitized input.
+ */
+ protected static function stripDangerousValues($input, array $whitelist, array &$sanitized_keys) {
+ if (is_array($input)) {
+ foreach ($input as $key => $value) {
+ if ($key !== '' && $key[0] === '#' && !in_array($key, $whitelist, TRUE)) {
+ unset($input[$key]);
+ $sanitized_keys[] = $key;
+ }
+ else {
+ $input[$key] = self::stripDangerousValues($input[$key], $whitelist, $sanitized_keys);
+ }
+ }
+ }
+ return $input;
+ }
+
+}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment