Pull merge.
[yaffs-website] / web / core / lib / Drupal / Core / Security / RequestSanitizer.php
1 <?php
2
3 namespace Drupal\Core\Security;
4
5 use Drupal\Component\Utility\UrlHelper;
6 use Symfony\Component\HttpFoundation\ParameterBag;
7 use Symfony\Component\HttpFoundation\Request;
8
9 /**
10  * Sanitizes user input.
11  */
12 class RequestSanitizer {
13
14   /**
15    * Request attribute to mark the request as sanitized.
16    */
17   const SANITIZED = '_drupal_request_sanitized';
18
19   /**
20    * The name of the setting that configures the whitelist.
21    */
22   const SANITIZE_WHITELIST = 'sanitize_input_whitelist';
23
24   /**
25    * The name of the setting that determines if sanitized keys are logged.
26    */
27   const SANITIZE_LOG = 'sanitize_input_logging';
28
29   /**
30    * Strips dangerous keys from user input.
31    *
32    * @param \Symfony\Component\HttpFoundation\Request $request
33    *   The incoming request to sanitize.
34    * @param string[] $whitelist
35    *   An array of keys to whitelist as safe. See default.settings.php.
36    * @param bool $log_sanitized_keys
37    *   (optional) Set to TRUE to log keys that are sanitized.
38    *
39    * @return \Symfony\Component\HttpFoundation\Request
40    *   The sanitized request.
41    */
42   public static function sanitize(Request $request, $whitelist, $log_sanitized_keys = FALSE) {
43     if (!$request->attributes->get(self::SANITIZED, FALSE)) {
44       $update_globals = FALSE;
45       $bags = [
46         'query' => 'Potentially unsafe keys removed from query string parameters (GET): %s',
47         'request' => 'Potentially unsafe keys removed from request body parameters (POST): %s',
48         'cookies' => 'Potentially unsafe keys removed from cookie parameters: %s',
49       ];
50       foreach ($bags as $bag => $message) {
51         if (static::processParameterBag($request->$bag, $whitelist, $log_sanitized_keys, $bag, $message)) {
52           $update_globals = TRUE;
53         }
54       }
55       if ($update_globals) {
56         $request->overrideGlobals();
57       }
58       $request->attributes->set(self::SANITIZED, TRUE);
59     }
60     return $request;
61   }
62
63   /**
64    * Processes a request parameter bag.
65    *
66    * @param \Symfony\Component\HttpFoundation\ParameterBag $bag
67    *   The parameter bag to process.
68    * @param string[] $whitelist
69    *   An array of keys to whitelist as safe.
70    * @param bool $log_sanitized_keys
71    *   Set to TRUE to log keys that are sanitized.
72    * @param string $bag_name
73    *   The request parameter bag name. Either 'query', 'request' or 'cookies'.
74    * @param string $message
75    *   The message to log if the parameter bag contains keys that are removed.
76    *   If the message contains %s that is replaced by a list of removed keys.
77    *
78    * @return bool
79    *   TRUE if the parameter bag has been sanitized, FALSE if not.
80    */
81   protected static function processParameterBag(ParameterBag $bag, $whitelist, $log_sanitized_keys, $bag_name, $message) {
82     $sanitized = FALSE;
83     $sanitized_keys = [];
84     $bag->replace(static::stripDangerousValues($bag->all(), $whitelist, $sanitized_keys));
85     if (!empty($sanitized_keys)) {
86       $sanitized = TRUE;
87       if ($log_sanitized_keys) {
88         trigger_error(sprintf($message, implode(', ', $sanitized_keys)));
89       }
90     }
91
92     if ($bag->has('destination')) {
93       $destination = $bag->get('destination');
94       $destination_dangerous_keys = static::checkDestination($destination, $whitelist);
95       if (!empty($destination_dangerous_keys)) {
96         // The destination is removed rather than sanitized because the URL
97         // generator service is not available and this method is called very
98         // early in the bootstrap.
99         $bag->remove('destination');
100         $sanitized = TRUE;
101         if ($log_sanitized_keys) {
102           trigger_error(sprintf('Potentially unsafe destination removed from %s parameter bag because it contained the following keys: %s', $bag_name, implode(', ', $destination_dangerous_keys)));
103         }
104       }
105       // Sanitize the destination parameter (which is often used for redirects)
106       // to prevent open redirect attacks leading to other domains.
107       if (UrlHelper::isExternal($destination)) {
108         // The destination is removed because it is an external URL.
109         $bag->remove('destination');
110         $sanitized = TRUE;
111         if ($log_sanitized_keys) {
112           trigger_error(sprintf('Potentially unsafe destination removed from %s parameter bag because it points to an external URL.', $bag_name));
113         }
114       }
115     }
116     return $sanitized;
117   }
118
119   /**
120    * Checks a destination string to see if it is dangerous.
121    *
122    * @param string $destination
123    *   The destination string to check.
124    * @param array $whitelist
125    *   An array of keys to whitelist as safe.
126    *
127    * @return array
128    *   The dangerous keys found in the destination parameter.
129    */
130   protected static function checkDestination($destination, array $whitelist) {
131     $dangerous_keys = [];
132     $parts = UrlHelper::parse($destination);
133     // If there is a query string, check its query parameters.
134     if (!empty($parts['query'])) {
135       static::stripDangerousValues($parts['query'], $whitelist, $dangerous_keys);
136     }
137     return $dangerous_keys;
138   }
139
140   /**
141    * Strips dangerous keys from $input.
142    *
143    * @param mixed $input
144    *   The input to sanitize.
145    * @param string[] $whitelist
146    *   An array of keys to whitelist as safe.
147    * @param string[] $sanitized_keys
148    *   An array of keys that have been removed.
149    *
150    * @return mixed
151    *   The sanitized input.
152    */
153   protected static function stripDangerousValues($input, array $whitelist, array &$sanitized_keys) {
154     if (is_array($input)) {
155       foreach ($input as $key => $value) {
156         if ($key !== '' && $key[0] === '#' && !in_array($key, $whitelist, TRUE)) {
157           unset($input[$key]);
158           $sanitized_keys[] = $key;
159         }
160         else {
161           $input[$key] = static::stripDangerousValues($input[$key], $whitelist, $sanitized_keys);
162         }
163       }
164     }
165     return $input;
166   }
167
168 }