Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
<?php
namespace Drupal\Tests\Core\Security;
use Drupal\Core\Security\RequestSanitizer;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests RequestSanitizer class.
*
* @coversDefaultClass \Drupal\Core\Security\RequestSanitizer
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @group Security
*/
class RequestSanitizerTest extends UnitTestCase {
/**
* Log of errors triggered during sanitization.
*
* @var array
*/
protected $errors;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->errors = [];
set_error_handler([$this, "errorHandler"]);
}
/**
* Tests RequestSanitizer class.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request to sanitize.
* @param array $expected
* An array of expected request parameters after sanitization. The possible
* keys are 'cookies', 'query', 'request' which correspond to the parameter
* bags names on the request object. These values are also used to test the
* PHP globals post sanitization.
* @param array|null $expected_errors
* An array of expected errors. If set to NULL then error logging is
* disabled.
* @param array $whitelist
* An array of keys to whitelist and not sanitize.
*
* @dataProvider providerTestRequestSanitization
*/
public function testRequestSanitization(Request $request, array $expected = [], array $expected_errors = NULL, array $whitelist = []) {
// Set up globals.
$_GET = $request->query->all();
$_POST = $request->request->all();
$_COOKIE = $request->cookies->all();
$_REQUEST = array_merge($request->query->all(), $request->request->all());
$request->server->set('QUERY_STRING', http_build_query($request->query->all()));
$_SERVER['QUERY_STRING'] = $request->server->get('QUERY_STRING');
$request = RequestSanitizer::sanitize($request, $whitelist, is_null($expected_errors) ? FALSE : TRUE);
// Normalise the expected data.
$expected += ['cookies' => [], 'query' => [], 'request' => []];
$expected_query_string = http_build_query($expected['query']);
// Test the request.
$this->assertEquals($expected['cookies'], $request->cookies->all());
$this->assertEquals($expected['query'], $request->query->all());
$this->assertEquals($expected['request'], $request->request->all());
$this->assertTrue($request->attributes->get(RequestSanitizer::SANITIZED));
// The request object normalizes the request query string.
$this->assertEquals(Request::normalizeQueryString($expected_query_string), $request->getQueryString());
// Test PHP globals.
$this->assertEquals($expected['cookies'], $_COOKIE);
$this->assertEquals($expected['query'], $_GET);
$this->assertEquals($expected['request'], $_POST);
$expected_request = array_merge($expected['query'], $expected['request']);
$this->assertEquals($expected_request, $_REQUEST);
$this->assertEquals($expected_query_string, $_SERVER['QUERY_STRING']);
// Ensure any expected errors have been triggered.
if (!empty($expected_errors)) {
foreach ($expected_errors as $expected_error) {
$this->assertError($expected_error, E_USER_NOTICE);
}
}
else {
$this->assertEquals([], $this->errors);
}
}
/**
* Data provider for testRequestSanitization.
*
* @return array
*/
public function providerTestRequestSanitization() {
$tests = [];
$request = new Request(['q' => 'index.php']);
$tests['no sanitization GET'] = [$request, ['query' => ['q' => 'index.php']]];
$request = new Request([], ['field' => 'value']);
$tests['no sanitization POST'] = [$request, ['request' => ['field' => 'value']]];
$request = new Request([], [], [], ['key' => 'value']);
$tests['no sanitization COOKIE'] = [$request, ['cookies' => ['key' => 'value']]];
$request = new Request(['q' => 'index.php'], ['field' => 'value'], [], ['key' => 'value']);
$tests['no sanitization GET, POST, COOKIE'] = [$request, ['query' => ['q' => 'index.php'], 'request' => ['field' => 'value'], 'cookies' => ['key' => 'value']]];
$request = new Request(['q' => 'index.php']);
$tests['no sanitization GET log'] = [$request, ['query' => ['q' => 'index.php']], []];
$request = new Request([], ['field' => 'value']);
$tests['no sanitization POST log'] = [$request, ['request' => ['field' => 'value']], []];
$request = new Request([], [], [], ['key' => 'value']);
$tests['no sanitization COOKIE log'] = [$request, ['cookies' => ['key' => 'value']], []];
$request = new Request(['#q' => 'index.php']);
$tests['sanitization GET'] = [$request];
$request = new Request([], ['#field' => 'value']);
$tests['sanitization POST'] = [$request];
$request = new Request([], [], [], ['#key' => 'value']);
$tests['sanitization COOKIE'] = [$request];
$request = new Request(['#q' => 'index.php'], ['#field' => 'value'], [], ['#key' => 'value']);
$tests['sanitization GET, POST, COOKIE'] = [$request];
$request = new Request(['#q' => 'index.php']);
$tests['sanitization GET log'] = [$request, [], ['Potentially unsafe keys removed from query string parameters (GET): #q']];
$request = new Request([], ['#field' => 'value']);
$tests['sanitization POST log'] = [$request, [], ['Potentially unsafe keys removed from request body parameters (POST): #field']];
$request = new Request([], [], [], ['#key' => 'value']);
$tests['sanitization COOKIE log'] = [$request, [], ['Potentially unsafe keys removed from cookie parameters: #key']];
$request = new Request(['#q' => 'index.php'], ['#field' => 'value'], [], ['#key' => 'value']);
$tests['sanitization GET, POST, COOKIE log'] = [$request, [], ['Potentially unsafe keys removed from query string parameters (GET): #q', 'Potentially unsafe keys removed from request body parameters (POST): #field', 'Potentially unsafe keys removed from cookie parameters: #key']];
$request = new Request(['q' => 'index.php', 'foo' => ['#bar' => 'foo']]);
$tests['recursive sanitization log'] = [$request, ['query' => ['q' => 'index.php', 'foo' => []]], ['Potentially unsafe keys removed from query string parameters (GET): #bar']];
$request = new Request(['q' => 'index.php', 'foo' => ['#bar' => 'foo']]);
$tests['recursive no sanitization whitelist'] = [$request, ['query' => ['q' => 'index.php', 'foo' => ['#bar' => 'foo']]], [], ['#bar']];
$request = new Request([], ['#field' => 'value']);
$tests['no sanitization POST whitelist'] = [$request, ['request' => ['#field' => 'value']], [], ['#field']];
$request = new Request(['q' => 'index.php', 'foo' => ['#bar' => 'foo', '#foo' => 'bar']]);
$tests['recursive multiple sanitization log'] = [$request, ['query' => ['q' => 'index.php', 'foo' => []]], ['Potentially unsafe keys removed from query string parameters (GET): #bar, #foo']];
$request = new Request(['#q' => 'index.php']);
$request->attributes->set(RequestSanitizer::SANITIZED, TRUE);
$tests['already sanitized request'] = [$request, ['query' => ['#q' => 'index.php']]];
$request = new Request(['destination' => 'whatever?%23test=value']);
$tests['destination removal GET'] = [$request];
$request = new Request([], ['destination' => 'whatever?%23test=value']);
$tests['destination removal POST'] = [$request];
$request = new Request([], [], [], ['destination' => 'whatever?%23test=value']);
$tests['destination removal COOKIE'] = [$request];
$request = new Request(['destination' => 'whatever?%23test=value']);
$tests['destination removal GET log'] = [$request, [], ['Potentially unsafe destination removed from query parameter bag because it contained the following keys: #test']];
$request = new Request([], ['destination' => 'whatever?%23test=value']);
$tests['destination removal POST log'] = [$request, [], ['Potentially unsafe destination removed from request parameter bag because it contained the following keys: #test']];
$request = new Request([], [], [], ['destination' => 'whatever?%23test=value']);
$tests['destination removal COOKIE log'] = [$request, [], ['Potentially unsafe destination removed from cookies parameter bag because it contained the following keys: #test']];
$request = new Request(['destination' => 'whatever?q[%23test]=value']);
$tests['destination removal subkey'] = [$request];
$request = new Request(['destination' => 'whatever?q[%23test]=value']);
$tests['destination whitelist'] = [$request, ['query' => ['destination' => 'whatever?q[%23test]=value']], [], ['#test']];
$request = new Request(['destination' => "whatever?\x00bar=base&%23test=value"]);
$tests['destination removal zero byte'] = [$request];
$request = new Request(['destination' => 'whatever?q=value']);
$tests['destination kept'] = [$request, ['query' => ['destination' => 'whatever?q=value']]];
$request = new Request(['destination' => 'whatever']);
$tests['destination no query'] = [$request, ['query' => ['destination' => 'whatever']]];
return $tests;
}
/**
* Catches and logs errors to $this->errors.
*
* @param int $errno
* The severity level of the error.
* @param string $errstr
* The error message.
*/
public function errorHandler($errno, $errstr) {
$this->errors[] = compact('errno', 'errstr');
}
/**
* Asserts that the expected error has been logged.
*
* @param string $errstr
* The error message.
* @param int $errno
* The severity level of the error.
*/
protected function assertError($errstr, $errno) {
foreach ($this->errors as $error) {
if ($error['errstr'] === $errstr && $error['errno'] === $errno) {
return;
}
}
$this->fail("Error with level $errno and message '$errstr' not found in " . var_export($this->errors, TRUE));
}
}