PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-waf / src / class-waf-request.php
jetpack / jetpack_vendor / automattic / jetpack-waf / src Last commit date
brute-force-protection 8 months ago exceptions 8 months ago abstract-blocked-login-page.php 2 months ago class-brute-force-protection.php 1 month ago class-compatibility.php 1 month ago class-rest-controller.php 8 months ago class-waf-blocked-login-page.php 8 months ago class-waf-blocklog-manager.php 8 months ago class-waf-cli.php 8 months ago class-waf-constants.php 1 year ago class-waf-initializer.php 1 month ago class-waf-operators.php 2 years ago class-waf-request.php 6 months ago class-waf-rules-manager.php 1 year ago class-waf-runner.php 1 month ago class-waf-runtime.php 2 months ago class-waf-standalone-bootstrap.php 1 month ago class-waf-stats.php 1 year ago class-waf-transforms.php 2 years ago functions.php 1 year ago
class-waf-request.php
408 lines
1 <?php
2 /**
3 * HTTP request representation specific for the WAF.
4 *
5 * @package automattic/jetpack-waf
6 */
7
8 namespace Automattic\Jetpack\Waf;
9
10 require_once __DIR__ . '/functions.php';
11
12 <<<'PHAN'
13 @phan-type RequestFile = array{ name: string, filename: string }
14 PHAN;
15
16 /**
17 * Request representation.
18 */
19 class Waf_Request {
20 /**
21 * The request URL, broken into three pieces: the host, the filename, and the query string
22 *
23 * @example for `https://wordpress.com/index.php?myvar=red`
24 * $this->url = [ 'https://wordpress.com', '/index.php', '?myvar=red' ]
25 * @var array{0: string, 1: string, 2: string}|null
26 */
27 protected $url = null;
28
29 /**
30 * Trusted proxies.
31 *
32 * @var array List of trusted proxy IP addresses.
33 */
34 private $trusted_proxies = array();
35
36 /**
37 * Trusted headers.
38 *
39 * @var array List of headers to trust from the trusted proxies.
40 */
41 private $trusted_headers = array();
42
43 /**
44 * Sets the list of IP addresses for the proxies to trust. Trusted headers will only be accepted as the
45 * user IP address from these IP adresses.
46 *
47 * Popular choices include:
48 * - 192.168.0.1
49 * - 10.0.0.1
50 *
51 * @param array $proxies List of proxy IP addresses.
52 * @return void
53 */
54 public function set_trusted_proxies( $proxies ) {
55 $this->trusted_proxies = (array) $proxies;
56 }
57
58 /**
59 * Sets the list of headers to be trusted from the proxies. These headers will only be taken into account
60 * if the request comes from a trusted proxy as configured with set_trusted_proxies().
61 *
62 * Popular choices include:
63 * - HTTP_CLIENT_IP
64 * - HTTP_X_FORWARDED_FOR
65 * - HTTP_X_FORWARDED
66 * - HTTP_X_CLUSTER_CLIENT_IP
67 * - HTTP_FORWARDED_FOR
68 * - HTTP_FORWARDED
69 *
70 * @param array $headers List of HTTP header strings.
71 * @return void
72 */
73 public function set_trusted_headers( $headers ) {
74 $this->trusted_headers = (array) $headers;
75 }
76
77 /**
78 * Determines the users real IP address based on the settings passed to set_trusted_proxies() and
79 * set_trusted_headers() before. On CLI, this will be null.
80 *
81 * @return string|null
82 */
83 public function get_real_user_ip_address() {
84 $remote_addr = ! empty( $_SERVER['REMOTE_ADDR'] ) ? wp_unslash( $_SERVER['REMOTE_ADDR'] ) : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
85
86 if ( in_array( $remote_addr, $this->trusted_proxies, true ) ) {
87 $ip_by_header = $this->get_ip_by_header( array_merge( $this->trusted_headers, array( 'REMOTE_ADDR' ) ) );
88 if ( ! empty( $ip_by_header ) ) {
89 return $ip_by_header;
90 }
91 }
92
93 return $remote_addr;
94 }
95
96 /**
97 * Iterates through a given list of HTTP headers and attempts to get the IP address from the header that
98 * a proxy sends along. Make sure you trust the IP address before calling this method.
99 *
100 * @param array $headers The list of headers to check.
101 * @return string|null
102 */
103 private function get_ip_by_header( $headers ) {
104 foreach ( $headers as $key ) {
105 if ( isset( $_SERVER[ $key ] ) ) {
106 foreach ( explode( ',', wp_unslash( $_SERVER[ $key ] ) ) as $ip ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- filter_var is applied below.
107 $ip = trim( $ip );
108
109 if ( filter_var( $ip, FILTER_VALIDATE_IP ) !== false ) {
110 return $ip;
111 }
112 }
113 }
114 }
115
116 return null;
117 }
118
119 /**
120 * Returns the headers that were sent with this request
121 *
122 * @return array{0: string, 1: scalar}[]
123 */
124 public function get_headers() {
125 $value = array();
126 $has_content_length = false;
127 foreach ( $_SERVER as $k => $v ) {
128 $k = strtolower( $k );
129 if ( 'http_' === substr( $k, 0, 5 ) ) {
130 $value[] = array( $this->normalize_header_name( substr( $k, 5 ) ), $v );
131 } elseif ( 'content_type' === $k && '' !== $v ) {
132 $value[] = array( 'content-type', $v );
133 } elseif ( 'content_length' === $k && '' !== $v ) {
134 $has_content_length = true;
135 $value[] = array( 'content-length', $v );
136 }
137 }
138 if ( ! $has_content_length ) {
139 $value[] = array( 'content-length', '0' );
140 }
141
142 return $value;
143 }
144
145 /**
146 * Returns the value of a specific header that was sent with this request
147 *
148 * @param string $name The name of the header to retrieve.
149 * @return string
150 */
151 public function get_header( $name ) {
152 $name = $this->normalize_header_name( $name );
153 foreach ( $this->get_headers() as list( $header_name, $header_value ) ) {
154 if ( $header_name === $name ) {
155 return $header_value;
156 }
157 }
158 return '';
159 }
160
161 /**
162 * Change a header name to all-lowercase and replace spaces and underscores with dashes.
163 *
164 * @param string $name The header name to normalize.
165 * @return string
166 */
167 public function normalize_header_name( $name ) {
168 return str_replace( array( ' ', '_' ), '-', strtolower( $name ) );
169 }
170
171 /**
172 * Get the method for this request (GET, POST, etc).
173 *
174 * @return string
175 */
176 public function get_method() {
177 return isset( $_SERVER['REQUEST_METHOD'] )
178 ? filter_var( wp_unslash( $_SERVER['REQUEST_METHOD'] ), FILTER_DEFAULT )
179 : '';
180 }
181
182 /**
183 * Get the protocol for this request (HTTP, HTTPS, etc)
184 *
185 * @return string
186 */
187 public function get_protocol() {
188 return isset( $_SERVER['SERVER_PROTOCOL'] )
189 ? filter_var( wp_unslash( $_SERVER['SERVER_PROTOCOL'] ), FILTER_DEFAULT )
190 : '';
191 }
192
193 /**
194 * Returns the URL parts for this request.
195 *
196 * @see $this->url
197 * @return array{0: string, 1: string, 2: string}
198 */
199 protected function get_url() {
200 if ( null !== $this->url ) {
201 return $this->url;
202 }
203
204 $uri = isset( $_SERVER['REQUEST_URI'] ) ? filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ), FILTER_DEFAULT ) : '/';
205 if ( false !== strpos( $uri, '?' ) ) {
206 // remove the query string (we'll pull it from elsewhere later)
207 $uri = urldecode( substr( $uri, 0, strpos( $uri, '?' ) ) );
208 } else {
209 $uri = urldecode( $uri );
210 }
211 $query_string = isset( $_SERVER['QUERY_STRING'] ) ? '?' . filter_var( wp_unslash( $_SERVER['QUERY_STRING'] ), FILTER_DEFAULT ) : '';
212 if ( 1 === preg_match( '/^https?:\/\//', $uri ) ) {
213 // sometimes $_SERVER[REQUEST_URI] already includes the full domain name
214 $uri_host = substr( $uri, 0, strpos( $uri, '/', 8 ) );
215 $uri_path = substr( $uri, strlen( $uri_host ) );
216 $this->url = array( $uri_host, $uri_path, $query_string );
217 } else {
218 // otherwise build the URI manually
219 $uri_scheme = ( ! empty( $_SERVER['HTTPS'] ) && 'off' !== $_SERVER['HTTPS'] )
220 ? 'https'
221 : 'http';
222 $uri_host = isset( $_SERVER['HTTP_HOST'] )
223 ? filter_var( wp_unslash( $_SERVER['HTTP_HOST'] ), FILTER_DEFAULT )
224 : (
225 isset( $_SERVER['SERVER_NAME'] )
226 ? filter_var( wp_unslash( $_SERVER['SERVER_NAME'] ), FILTER_DEFAULT )
227 : ''
228 );
229 $uri_port = isset( $_SERVER['SERVER_PORT'] )
230 ? filter_var( wp_unslash( $_SERVER['SERVER_PORT'] ), FILTER_SANITIZE_NUMBER_INT )
231 : '';
232 // we only need to include the port if it's non-standard
233 if ( $uri_port && ( 'http' === $uri_scheme && '80' !== $uri_port || 'https' === $uri_scheme && '443' !== $uri_port ) ) {
234 $uri_port = ':' . $uri_port;
235 } else {
236 $uri_port = '';
237 }
238 $this->url = array(
239 $uri_scheme . '://' . $uri_host . $uri_port,
240 $uri,
241 $query_string,
242 );
243 }
244 return $this->url;
245 }
246
247 /**
248 * Get the requested URI
249 *
250 * @param boolean $include_host If true, the scheme and domain will be included in the returned string (i.e. 'https://wordpress.com/index.php).
251 * If false, only the requested URI path will be returned (i.e. '/index.php').
252 * @return string
253 */
254 public function get_uri( $include_host = false ) {
255 list( $host, $file, $query ) = $this->get_url();
256
257 return ( $include_host ? $host : '' ) . $file . $query;
258 }
259
260 /**
261 * Return the filename part of the request
262 *
263 * @example for 'https://wordpress.com/some/page?id=5', return '/some/page'
264 * @return string
265 */
266 public function get_filename() {
267 return $this->get_url()[1];
268 }
269
270 /**
271 * Return the basename part of the request
272 *
273 * @example for 'https://wordpress.com/some/page.php?id=5', return 'page.php'
274 * @return string
275 */
276 public function get_basename() {
277 // Get the filename part of the request
278 $filename = $this->get_filename();
279 // Normalize slashes
280 $filename = str_replace( '\\', '/', $filename );
281 // Remove trailing slashes
282 $filename = rtrim( $filename, '/' );
283 // Return the basename
284 $offset = strrpos( $filename, '/' );
285 return $offset !== false ? substr( $filename, $offset + 1 ) : $filename;
286 }
287
288 /**
289 * Return the query string. If present, it will be prefixed with '?'. Otherwise, it will be an empty string.
290 *
291 * @return string
292 */
293 public function get_query_string() {
294 return $this->get_url()[2];
295 }
296
297 /**
298 * Returns the request body.
299 *
300 * @return string
301 */
302 public function get_body() {
303 $body = file_get_contents( 'php://input' );
304 return false === $body ? '' : $body;
305 }
306
307 /**
308 * Returns the cookies
309 *
310 * @return array{string, scalar}[]
311 */
312 public function get_cookies() {
313 return flatten_array( $_COOKIE );
314 }
315
316 /**
317 * Returns the GET variables
318 *
319 * @return array{string, scalar}[]
320 */
321 public function get_get_vars() {
322 return flatten_array( $_GET );
323 }
324
325 /**
326 * Returns the POST variables from a JSON body
327 *
328 * @return array{string, scalar}[]
329 */
330 private function get_json_post_vars() {
331 $decoded_json = json_decode( $this->get_body(), true ) ?? array();
332 return flatten_array( $decoded_json, 'json', true );
333 }
334
335 /**
336 * Returns the POST variables from a urlencoded body
337 *
338 * @return array{string, scalar}[]
339 */
340 private function get_urlencoded_post_vars() {
341 parse_str( $this->get_body(), $params );
342 return flatten_array( $params );
343 }
344
345 /**
346 * Returns the POST variables
347 *
348 * @param string $body_processor Manually specifiy the method to use to process the body. Options are 'URLENCODED' and 'JSON'.
349 *
350 * @return array{string, scalar}[]
351 */
352 public function get_post_vars( string $body_processor = '' ) {
353 $content_type = $this->get_header( 'content-type' );
354
355 // If the body processor is specified by the rules file, trust it.
356 if ( 'URLENCODED' === $body_processor ) {
357 return $this->get_urlencoded_post_vars();
358 }
359 if ( 'JSON' === $body_processor ) {
360 return $this->get_json_post_vars();
361 }
362
363 // Otherwise, use $_POST if it's not empty.
364 if ( ! empty( $_POST ) ) {
365 return flatten_array( $_POST );
366 }
367
368 // Lastly, try to parse the body based on the content type.
369 if ( strpos( $content_type, 'application/json' ) !== false ) {
370 return $this->get_json_post_vars();
371 }
372 if ( strpos( $content_type, 'application/x-www-form-urlencoded' ) !== false ) {
373 return $this->get_urlencoded_post_vars();
374 }
375
376 // Don't try to parse any other content types.
377 return array();
378 }
379
380 /**
381 * Returns the files that were uploaded with this request (i.e. what's in the $_FILES superglobal)
382 *
383 * @return RequestFile[]
384 */
385 public function get_files() {
386 $files = array();
387 foreach ( $_FILES as $field_name => $arr ) {
388 // flatten the values in case we were given inputs with brackets
389 foreach ( flatten_array( $arr ) as list( $arr_key, $arr_value ) ) {
390 if ( $arr_key === 'name' ) {
391 // if this file was a simple (non-nested) name and unique, then just add it.
392 $files[] = array(
393 'name' => $field_name,
394 'filename' => $arr_value,
395 );
396 } elseif ( 'name[' === substr( $arr_key, 0, 5 ) ) {
397 // otherwise this was a file with a nested name and/or multiple files with the same name
398 $files[] = array(
399 'name' => $field_name . substr( $arr_key, 4 ),
400 'filename' => $arr_value,
401 );
402 }
403 }
404 }
405 return $files;
406 }
407 }
408