PluginProbe
The WP Remote WordPress Plugin / 6.62
The WP Remote WordPress Plugin v6.62
6.72 6.69 6.65 6.62 6.48 6.47 4.87 4.97 5.05 5.09 5.16 5.22 5.24 5.25 5.38 5.41 5.42 5.45 5.47 5.53 5.56 5.65 5.68 5.72 5.73 All 53 releases
wpremote / protect / request.php

request.php in The WP Remote WordPress Plugin 6.62, at protect/request.php

733 lines 20.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH') && !defined('MCDATAPATH')) exit;
4
5 if (!class_exists('WPRProtectRequest_V662')) :
6 class WPRProtectRequest_V662 {
7 public $ip;
8 public $host = '';
9 public $uri;
10 public $method = '';
11 public $path = '';
12 public $timestamp;
13 public $get_params;
14 public $post_params;
15 public $cookies;
16 public $headers = array();
17 public $file_names = array();
18 public $json_params = array();
19 public $raw_body = '';
20 public $files;
21 public $respcode;
22 public $status = WPRProtectRequest_V662::STATUS_ALLOWED;
23 public $category = WPRProtectRequest_V662::CATEGORY_NORMAL;
24
25 public $wp_user;
26
27 private $can_get_raw_body = false;
28 private $can_decode_json = false;
29 private $can_get_uploaded_file_content = false;
30
31 private $max_raw_body_length = 1000000;
32 private $max_json_decode_depth = 512;
33 private $max_uploaded_file_content_length = 8192;
34 private $max_total_uploaded_file_content_length = 65536;
35
36 private $raw_body_status = 'not_loaded';
37 private $json_params_status = 'not_loaded';
38 private $raw_body_truncated = false;
39 private $raw_body_loaded = false;
40 private $json_params_loaded = false;
41 private $uploaded_file_content_statuses = array();
42 private $uploaded_file_content_cache = array();
43 private $uploaded_file_content_bytes_read = 0;
44
45 #XNOTE: SHould be part of Protect.
46 const STATUS_ALLOWED = 1;
47 const STATUS_BLOCKED = 2;
48 const STATUS_BYPASSED = 3;
49
50 const CATEGORY_BLACKLISTED = 1;
51 const CATEGORY_NORMAL = 10;
52 const CATEGORY_WHITELISTED = 20;
53 const CATEGORY_BOT_BLOCKED = 30;
54 const CATEGORY_COUNTRY_BLOCKED = 40;
55 const CATEGORY_USER_BLACKLISTED = 50;
56 const CATEGORY_RULE_BLOCKED = 60;
57 const CATEGORY_RULE_ALLOWED = 70;
58 const CATEGORY_PRIVATEIP = 80;
59 const CATEGORY_GLOBAL_BOT_BLOCKED = 90;
60
61 public function __construct($ip_header, $config) {
62 $this->ip = WPRProtectUtils_V662::getIP($ip_header);
63 $this->timestamp = time();
64 $this->get_params = $_GET; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
65 $this->cookies = $_COOKIE;
66 $this->post_params = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing
67 $this->files = $_FILES; // phpcs:ignore WordPress.Security.NonceVerification.Missing
68
69 if (array_key_exists('cangetrawbody', $config) && is_bool($config['cangetrawbody'])) {
70 $this->can_get_raw_body = $config['cangetrawbody'];
71 }
72
73 if (array_key_exists('maxrawbodylength', $config) && is_int($config['maxrawbodylength'])) {
74 $this->max_raw_body_length = $config['maxrawbodylength'];
75 }
76
77 if (array_key_exists('candecodejson', $config) && is_bool($config['candecodejson'])) {
78 $this->can_decode_json = $config['candecodejson'];
79 }
80
81 if (array_key_exists('maxjsondecodedepth', $config) && is_int($config['maxjsondecodedepth'])) {
82 $this->max_json_decode_depth = $config['maxjsondecodedepth'];
83 }
84
85 if (array_key_exists('cangetuploadedfilecontent', $config) && is_bool($config['cangetuploadedfilecontent'])) {
86 $this->can_get_uploaded_file_content = $config['cangetuploadedfilecontent'];
87 }
88
89 if (array_key_exists('maxuploadedfilecontentlength', $config) && is_int($config['maxuploadedfilecontentlength'])) {
90 $this->max_uploaded_file_content_length = $config['maxuploadedfilecontentlength'];
91 }
92
93 if (array_key_exists('maxtotaluploadedfilecontentlength', $config) && is_int($config['maxtotaluploadedfilecontentlength'])) {
94 $this->max_total_uploaded_file_content_length = $config['maxtotaluploadedfilecontentlength'];
95 }
96
97 if (!empty($_FILES)) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
98 foreach ($_FILES as $input => $file) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
99 $this->file_names[$input] = $file['name'];
100 }
101 }
102 if (is_array($_SERVER)) {
103 foreach ($_SERVER as $key => $value) {
104 if (strpos($key, 'HTTP_') === 0) {
105 $header = $this->normalizeHeaderName($key);
106 $this->headers[$header] = $value;
107 }
108 }
109 $content_type = WPRHelper::getRawParam('SERVER', 'CONTENT_TYPE');
110 if (isset($content_type)) {
111 $this->headers['Content-Type'] = $content_type;
112 }
113 $content_length = WPRHelper::getRawParam('SERVER', 'CONTENT_LENGTH');
114 if (isset($content_length)) {
115 $this->headers['Content-Length'] = $content_length;
116 }
117 $referer = WPRHelper::getRawParam('SERVER', 'REFERER');
118 if (isset($referer)) {
119 $this->headers['Referer'] = $referer;
120 }
121 $http_user_agent = WPRHelper::getRawParam('SERVER', 'HTTP_USER_AGENT');
122 if (isset($http_user_agent)) {
123 $this->headers['User-Agent'] = $http_user_agent;
124 }
125
126 if (array_key_exists('Host', $this->headers)) {
127 $this->host = $this->headers['Host'];
128 } elseif (array_key_exists('SERVER_NAME', $_SERVER)) {
129 $this->host = WPRHelper::getRawParam('SERVER', 'SERVER_NAME');
130 }
131
132 $request_method = WPRHelper::getRawParam('SERVER', 'REQUEST_METHOD');
133 $this->method = isset($request_method) ? $request_method : 'GET';
134 $request_uri = WPRHelper::getRawParam('SERVER', 'REQUEST_URI');
135 $this->uri = isset($request_uri) ? $request_uri : '';
136 $_uri = parse_url($this->uri);
137 $this->path = (is_array($_uri) && array_key_exists('path', $_uri)) ? $_uri['path'] : $this->uri;
138 }
139
140 }
141
142 public static function blacklistedCategories() {
143 return array(
144 WPRProtectRequest_V662::CATEGORY_BOT_BLOCKED,
145 WPRProtectRequest_V662::CATEGORY_COUNTRY_BLOCKED,
146 WPRProtectRequest_V662::CATEGORY_USER_BLACKLISTED,
147 WPRProtectRequest_V662::CATEGORY_GLOBAL_BOT_BLOCKED
148 );
149 }
150
151 public static function whitelistedCategories() {
152 return array(WPRProtectRequest_V662::CATEGORY_WHITELISTED);
153 }
154
155 public function setRespCode($code) {
156 $this->respcode = $code;
157 }
158
159 public function getRespCode() {
160 if (!isset($this->respcode) && function_exists('http_response_code')) {
161 $this->respcode = http_response_code();
162 }
163
164 return $this->respcode;
165 }
166
167 public function getStatus() {
168 return $this->status;
169 }
170
171 public function getCategory() {
172 return $this->category;
173 }
174
175 private function getKeyVal($array, $key) {
176 if (is_array($array)) {
177 if (is_array($key)) {
178 $_key = array_shift($key);
179 if (array_key_exists($_key, $array)) {
180 if (count($key) > 0) {
181 return $this->getKeyVal($array[$_key], $key);
182 } else {
183 return $array[$_key];
184 }
185 }
186 } else {
187 return array_key_exists($key, $array) ? $array[$key] : null;
188 }
189 }
190 return null;
191 }
192
193 private function getContentMediaType($content_type) {
194 if (!is_string($content_type)) {
195 return null;
196 }
197
198 $parts = explode(';', $content_type, 2);
199 $media_type = strtolower(trim($parts[0]));
200 return $media_type !== '' ? $media_type : null;
201 }
202
203 private function isJsonContentType($content_type) {
204 $media_type = $this->getContentMediaType($content_type);
205 if (!isset($media_type)) {
206 return false;
207 }
208
209 return preg_match('/^application\/(?:[\w!#$&^.+-]+\+)?json(?:\+oembed)?$/', $media_type) === 1;
210 }
211
212 private function normalizeHeaderName($name) {
213 if (!is_string($name)) {
214 return null;
215 }
216
217 $name = trim($name);
218 if (stripos($name, 'HTTP_') === 0) {
219 $name = substr($name, 5);
220 }
221 $name = str_replace(array('-', '_'), ' ', $name);
222 return str_replace(' ', '-', ucwords(strtolower($name)));
223 }
224
225 private function isUploadedFileKey($key) {
226 return is_string($key) || is_int($key);
227 }
228
229 private function normalizeUploadedFileIndexKeys($index) {
230 if ($index === null) {
231 return array();
232 }
233
234 if ($this->isUploadedFileKey($index)) {
235 return array($index);
236 }
237
238 if (!is_array($index) || empty($index)) {
239 return null;
240 }
241
242 foreach ($index as $key) {
243 if (!$this->isUploadedFileKey($key)) {
244 return null;
245 }
246 }
247
248 return array_values($index);
249 }
250
251 private function buildUploadedFileStatusKey($field_name, $index_keys) {
252 $status_key = (string) $field_name;
253 foreach ($index_keys as $key) {
254 $status_key .= '[' . (string) $key . ']';
255 }
256
257 return $status_key;
258 }
259
260 private function setUploadedFileContentStatus($field_name, $index_keys, $status) {
261 $status_key = $this->buildUploadedFileStatusKey($field_name, $index_keys);
262 if ($status_key !== '') {
263 $this->uploaded_file_content_statuses[$status_key] = $status;
264 }
265 }
266
267 private function resolveUploadedFileEntry($field_name) {
268 if (!$this->isUploadedFileKey($field_name)) {
269 return null;
270 }
271
272 if (!is_array($this->files) || !array_key_exists($field_name, $this->files) ||
273 !is_array($this->files[$field_name])) {
274 $this->setUploadedFileContentStatus($field_name, array(), 'missing_file');
275 return null;
276 }
277
278 return $this->files[$field_name];
279 }
280
281 private function getUploadedFileMetaValue($file_entry, $meta_key, $index_keys) {
282 if (!is_array($file_entry) || !array_key_exists($meta_key, $file_entry)) {
283 return null;
284 }
285
286 if (empty($index_keys)) {
287 return $file_entry[$meta_key];
288 }
289
290 return $this->getKeyVal($file_entry[$meta_key], $index_keys);
291 }
292
293 private function isUploadedFilePath($path) {
294 return is_string($path) && $path !== '' && is_uploaded_file($path);
295 }
296
297 private function normalizeUploadedFileSize($size) {
298 if (is_int($size)) {
299 return $size;
300 }
301
302 if (is_string($size) && preg_match('/^\d+$/', $size) === 1) {
303 return (int) $size;
304 }
305
306 return null;
307 }
308
309 private function resolveUploadedFileReadStatus($read_limit, $requested_limit, $per_file_limit, $limited_by_total,
310 $file_size, $content_length) {
311
312 if ($content_length === 0) {
313 return 'empty';
314 }
315
316 if (isset($file_size) && $file_size <= $content_length) {
317 return 'available';
318 }
319
320 if ($limited_by_total) {
321 return 'truncated_by_total_limit';
322 }
323
324 if (isset($file_size) && $file_size > $read_limit) {
325 if ($requested_limit > $per_file_limit) {
326 return 'truncated_by_config_limit';
327 }
328
329 return 'truncated_by_function_limit';
330 }
331
332 if (!isset($file_size) && $content_length >= $read_limit) {
333 if ($requested_limit > $per_file_limit) {
334 return 'truncated_by_config_limit';
335 }
336
337 if ($read_limit < $requested_limit) {
338 return 'truncated_by_function_limit';
339 }
340 }
341
342 return 'available';
343 }
344
345 private function readUploadedFileContent($file_entry, $field_name, $index_keys, $max_bytes) {
346 if (!$this->can_get_uploaded_file_content) {
347 $this->setUploadedFileContentStatus($field_name, $index_keys, 'disabled_by_config');
348 return null;
349 }
350
351 $error = $this->getUploadedFileMetaValue($file_entry, 'error', $index_keys);
352 if ((string) $error !== '0') {
353 $this->setUploadedFileContentStatus($field_name, $index_keys, 'upload_error');
354 return null;
355 }
356
357 $tmp_name = $this->getUploadedFileMetaValue($file_entry, 'tmp_name', $index_keys);
358 if (!is_string($tmp_name) || $tmp_name === '') {
359 $this->setUploadedFileContentStatus($field_name, $index_keys, 'missing_tmp_name');
360 return null;
361 }
362
363 if (!$this->isUploadedFilePath($tmp_name)) {
364 $this->setUploadedFileContentStatus($field_name, $index_keys, 'not_uploaded_file');
365 return null;
366 }
367
368 $requested_limit = max(0, (int) $max_bytes);
369 $per_file_limit = max(0, $this->max_uploaded_file_content_length);
370 $total_limit = max(0, $this->max_total_uploaded_file_content_length);
371 $configured_target = min($requested_limit, $per_file_limit);
372 if ($configured_target <= 0) {
373 $this->setUploadedFileContentStatus($field_name, $index_keys, 'empty');
374 return '';
375 }
376
377 $status_key = $this->buildUploadedFileStatusKey($field_name, $index_keys);
378 $cached_length = 0;
379 if (array_key_exists($status_key, $this->uploaded_file_content_cache)) {
380 $cached_length = strlen($this->uploaded_file_content_cache[$status_key]['content']);
381 }
382
383 $remaining_limit = max(0, $total_limit - $this->uploaded_file_content_bytes_read);
384 $read_limit = min($configured_target, $cached_length + $remaining_limit);
385 $limited_by_total = $read_limit < $configured_target;
386 if ($read_limit <= 0) {
387 $this->setUploadedFileContentStatus($field_name, $index_keys, 'total_limit_exceeded');
388 return null;
389 }
390
391 if (array_key_exists($status_key, $this->uploaded_file_content_cache) &&
392 $cached_length >= $read_limit) {
393
394 $content = substr($this->uploaded_file_content_cache[$status_key]['content'], 0, $read_limit);
395 } else {
396 $content = file_get_contents($tmp_name, false, null, 0, $read_limit);
397 if ($content === false) {
398 $this->setUploadedFileContentStatus($field_name, $index_keys, 'read_failed');
399 return null;
400 }
401
402 $this->uploaded_file_content_bytes_read += max(0, strlen($content) - $cached_length);
403 $this->uploaded_file_content_cache[$status_key] = array(
404 'content' => $content,
405 'limit' => $read_limit
406 );
407 }
408
409 $file_size = $this->normalizeUploadedFileSize($this->getUploadedFileMetaValue($file_entry, 'size', $index_keys));
410 $this->setUploadedFileContentStatus(
411 $field_name,
412 $index_keys,
413 $this->resolveUploadedFileReadStatus(
414 $read_limit,
415 $requested_limit,
416 $per_file_limit,
417 $limited_by_total,
418 $file_size,
419 strlen($content)
420 )
421 );
422
423 return $content;
424 }
425
426 private function readUploadedFileContents($file_entry, $field_name, $index_keys, $max_bytes) {
427 $tmp_name = $this->getUploadedFileMetaValue($file_entry, 'tmp_name', $index_keys);
428
429 if (is_array($tmp_name)) {
430 $contents = array();
431 foreach ($tmp_name as $key => $value) {
432 $contents[$key] = $this->readUploadedFileContents(
433 $file_entry,
434 $field_name,
435 array_merge($index_keys, array($key)),
436 $max_bytes
437 );
438 }
439 return $contents;
440 }
441
442 if ($tmp_name === null) {
443 $this->setUploadedFileContentStatus($field_name, $index_keys, 'missing_file');
444 return null;
445 }
446
447 return $this->readUploadedFileContent($file_entry, $field_name, $index_keys, $max_bytes);
448 }
449
450 private function loadRawBody() {
451 if ($this->raw_body_loaded) {
452 return;
453 }
454
455 $this->raw_body_loaded = true;
456 if (!$this->can_get_raw_body) {
457 $this->raw_body_status = 'disabled_by_config';
458 return;
459 }
460
461 $read_limit = max(0, $this->max_raw_body_length);
462 $_raw_body = file_get_contents("php://input", false, null, 0, $read_limit + 1);
463 if ($_raw_body === false) {
464 $this->raw_body_status = 'read_failed';
465 return;
466 }
467
468 $is_truncated = strlen($_raw_body) > $read_limit;
469 $this->raw_body = $_raw_body;
470 $this->raw_body_truncated = $is_truncated;
471 $this->raw_body_status = $is_truncated ? 'truncated_by_limit' : 'available';
472 }
473
474 private function loadJsonParams() {
475 if ($this->json_params_loaded) {
476 return;
477 }
478
479 $this->json_params_loaded = true;
480 if (!$this->can_decode_json) {
481 $this->json_params_status = 'disabled_by_config';
482 return;
483 }
484
485 if (!$this->isJsonContentType($this->getContentType())) {
486 $this->json_params_status = 'unsupported_content_type';
487 return;
488 }
489
490 $this->loadRawBody();
491 if (!in_array($this->raw_body_status, array('available', 'truncated_by_limit'), true)) {
492 $this->json_params_status = 'raw_body_unavailable';
493 return;
494 }
495
496 if ($this->raw_body_status === 'truncated_by_limit') {
497 $this->json_params_status = 'raw_body_truncated';
498 return;
499 }
500
501 $_json_params = WPRProtectUtils_V662::safeDecodeJSON(
502 $this->raw_body,
503 true,
504 $this->max_json_decode_depth
505 );
506 if (isset($_json_params)) {
507 $this->json_params['JSON'] = $_json_params;
508 $this->json_params_status = 'available';
509 } elseif (function_exists('json_last_error') && json_last_error() === JSON_ERROR_NONE) {
510 $this->json_params_status = 'decoded_null';
511 } else {
512 $this->json_params_status = 'decode_failed';
513 }
514 }
515
516 public function getPostParams() {
517 if (func_num_args() > 0) {
518 $args = func_get_args();
519 return $this->getKeyVal($this->post_params, $args);
520 }
521 return $this->post_params;
522 }
523
524 public function getCookies() {
525 if (func_num_args() > 0) {
526 $args = func_get_args();
527 return $this->getKeyVal($this->cookies, $args);
528 }
529 return $this->cookies;
530 }
531
532 public function getGetParams() {
533 if (func_num_args() > 0) {
534 $args = func_get_args();
535 return $this->getKeyVal($this->get_params, $args);
536 }
537 return $this->get_params;
538 }
539
540 public function getAllParams() {
541 return array("getParams" => $this->get_params, "postParams" => $this->post_params, "jsonParams" => $this->getJsonParams());
542 }
543
544 public function getHeader($key) {
545 $key = $this->normalizeHeaderName($key);
546 return isset($key) && array_key_exists($key, $this->headers) ? $this->headers[$key] : null;
547 }
548
549 public function getHeaders() {
550 if (func_num_args() > 0) {
551 $args = func_get_args();
552 $args[0] = $this->normalizeHeaderName($args[0]);
553 if (!isset($args[0])) {
554 return null;
555 }
556 return $this->getKeyVal($this->headers, $args);
557 }
558 return $this->headers;
559 }
560
561 public function getFiles() {
562 if (func_num_args() > 0) {
563 $args = func_get_args();
564 return $this->getKeyVal($this->files, $args);
565 }
566 return $this->files;
567 }
568
569 public function getFileNames() {
570 if (func_num_args() > 0) {
571 $args = func_get_args();
572 return $this->getKeyVal($this->file_names, $args);
573 }
574 return $this->file_names;
575 }
576
577 public function getUploadedFileContent($field_name, $max_bytes, $index = null) {
578 $index_keys = $this->normalizeUploadedFileIndexKeys($index);
579 if (!is_array($index_keys)) {
580 return null;
581 }
582
583 $file_entry = $this->resolveUploadedFileEntry($field_name);
584 if (!is_array($file_entry)) {
585 return null;
586 }
587
588 return $this->readUploadedFileContents($file_entry, $field_name, $index_keys, $max_bytes);
589 }
590
591 public function getUploadedFileMeta($field_name, $meta_key, $index = null) {
592 if (!is_string($meta_key) || $meta_key === '') {
593 return null;
594 }
595
596 $index_keys = $this->normalizeUploadedFileIndexKeys($index);
597 if (!is_array($index_keys)) {
598 return null;
599 }
600
601 $file_entry = $this->resolveUploadedFileEntry($field_name);
602 if (!is_array($file_entry)) {
603 return null;
604 }
605
606 $meta_value = $this->getUploadedFileMetaValue($file_entry, $meta_key, $index_keys);
607 if ($meta_key === 'size' && !is_array($meta_value)) {
608 return $this->normalizeUploadedFileSize($meta_value);
609 }
610
611 return $meta_value;
612 }
613
614 public function getUploadedFileContentStatus($field_name = null, $index = null) {
615 if ($field_name === null) {
616 return $this->uploaded_file_content_statuses;
617 }
618
619 if (!$this->isUploadedFileKey($field_name)) {
620 return null;
621 }
622
623 $index_keys = $this->normalizeUploadedFileIndexKeys($index);
624 if (!is_array($index_keys)) {
625 return null;
626 }
627
628 $status_key = $this->buildUploadedFileStatusKey($field_name, $index_keys);
629 return array_key_exists($status_key, $this->uploaded_file_content_statuses) ?
630 $this->uploaded_file_content_statuses[$status_key] : null;
631 }
632
633 public function getHost() {
634 return $this->host;
635 }
636
637 public function getURI() {
638 return $this->uri;
639 }
640
641 public function getAction() {
642 $post_action = $this->getPostParams('action');
643 if (isset($post_action)) {
644 return $post_action;
645 } else {
646 return $this->getGetParams('action');
647 }
648 }
649
650 public function getPath() {
651 return $this->path;
652 }
653
654 public function getIP() {
655 return $this->ip;
656 }
657
658 public function getMethod() {
659 return $this->method;
660 }
661
662 public function getTimestamp() {
663 return $this->timestamp;
664 }
665
666 public function getRequestID() {
667 if (!defined("WPR_REQUEST_ID")) {
668 define("WPR_REQUEST_ID", uniqid(mt_rand())); // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand
669 }
670
671 return WPR_REQUEST_ID;
672 }
673
674 public function getServerValue($key) {
675 $val = WPRHelper::getRawParam('SERVER', $key);
676 return isset($val) ? $val : false;
677 }
678
679 public function getHeadersV2() {
680 return $this->headers;
681 }
682
683 public function getFilesV2() {
684 return $this->files;
685 }
686
687 public function getFileNamesV2() {
688 return $this->file_names;
689 }
690
691 public function getPostParamsV2() {
692 return $this->post_params;
693 }
694
695 public function getGetParamsV2() {
696 return $this->get_params;
697 }
698
699 public function getCookiesV2() {
700 return $this->cookies;
701 }
702
703 public function getJsonParams() {
704 $this->loadJsonParams();
705 return $this->json_params;
706 }
707
708 public function getRawBody() {
709 $this->loadRawBody();
710 return $this->raw_body;
711 }
712
713 public function getBodyParserStatus() {
714 return array(
715 'raw_body_status' => $this->raw_body_status,
716 'raw_body_truncated' => $this->raw_body_truncated,
717 'json_params_status' => $this->json_params_status,
718 'uploaded_file_content_statuses' => $this->uploaded_file_content_statuses,
719 'uploaded_file_content_bytes_read' => $this->uploaded_file_content_bytes_read,
720 'max_uploaded_file_content_length' => $this->max_uploaded_file_content_length,
721 'max_total_uploaded_file_content_length' => $this->max_total_uploaded_file_content_length
722 );
723 }
724
725 public function getContentType() {
726 return $this->getHeader('Content-Type');
727 }
728
729 public function getContentLength() {
730 return $this->getHeader('Content-Length');
731 }
732 }
733 endif;