PluginProbe
Patchstack – WordPress & Plugins Security / 2.3.5
Patchstack – WordPress & Plugins Security v2.3.5
2.3.7 trunk 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.17 2.1.18 2.1.19 2.1.2 2.1.20 2.1.21 2.1.22 2.1.23 2.1.24 2.1.25 2.1.3 2.1.4 2.1.5 2.1.6 All 49 releases
← All changes | includes/firewall.php +99 -632 2.1.02.3.5 View file →
@@ -10,17 +10,17 @@
10 10 */
11 11 class P_Firewall extends P_Core {
12 12
13 13 /**
14 - * Parse the firewall and whitelist rules and determine if it's valid.
15 - * Then set the types with all server/client variables and launch the processor.
14 + * Launch the firewall rule processor.
16 15 *
17 16 * @param bool $from_main Whether or not the firewall is loaded from the main script or not.
18 17 * @param Patchstack $core
19 18 * @param bool $skip Whether or not to process and execute the rules.
19 + * @param bool $muCall Whether or not this was called from mu-plugin.
20 20 * @return void
21 21 */
22 - public function __construct( $from_main = false, $core = null, $skip = false ) {
22 + public function __construct( $from_main = false, $core = null, $skip = false, $muCall = false ) {
23 23 if ( ! $from_main || ! $core ) {
24 24 if ( $core ) {
25 25 parent::__construct( $core );
26 26 }
@@ -25,657 +25,122 @@
25 25 parent::__construct( $core );
26 26 }
27 27 return;
28 28 }
29 -
29 +
30 30 parent::__construct( $core );
31 31
32 32 // If we only want to initialize the firewall but not execute the rules.
33 - if ( $skip ) {
33 + if ( $skip || defined( 'DOING_CRON' ) ) {
34 34 return;
35 35 }
36 36
37 - // Process the firewall rules.
38 - $this->processor();
39 - }
40 -
41 - /**
42 - * Check the custom whitelist rules defined in the backend of WordPress
43 - * and attempt to match it with the request.
44 - *
45 - * @return boolean
46 - */
47 - private function is_custom_whitelisted() {
48 - $whitelist = str_replace( '<?php exit; ?>', '', get_option( 'patchstack_custom_whitelist_rules', '' ) );
49 - if ( empty( $whitelist ) ) {
50 - return false;
51 - }
52 -
53 - // Loop through all lines.
54 - $lines = explode( "\n", $whitelist );
55 - $ip = $this->get_ip();
56 -
57 - foreach ( $lines as $line ) {
58 - $t = explode( ':', $line );
59 -
60 - if ( count( $t ) == 2 ) {
61 - $val = strtolower( trim( $t[1] ) );
62 - switch ( strtolower( $t[0] ) ) {
63 - // IP address match.
64 - case 'ip':
65 - if ( $ip == $val ) {
66 - return true;
67 - }
68 - break;
69 - // Payload match.
70 - case 'payload':
71 - if ( count( $_POST ) > 0 && strpos( strtolower( print_r( $_POST, true ) ), $val ) !== false ) {
72 - return true;
73 - }
74 -
75 - if ( count( $_GET ) > 0 && strpos( strtolower( print_r( $_GET, true ) ), $val ) !== false ) {
76 - return true;
77 - }
78 - break;
79 - // URL match.
80 - case 'url':
81 - if ( strpos( strtolower( $_SERVER['REQUEST_URI'] ), $val ) !== false ) {
82 - return true;
83 - }
84 - break;
85 - }
86 - }
87 - }
88 -
89 - return false;
90 - }
91 -
92 - /**
93 - * Determine if the request should be whitelisted.
94 - *
95 - * @return boolean
96 - */
97 - private function is_whitelisted() {
98 - // First check if the user has custom whitelist rules configured.
99 - if ( $this->is_custom_whitelisted() ) {
100 - return true;
101 - }
102 -
103 - // Load the whitelist.
104 - $whitelists = get_option( 'patchstack_whitelist_rules', '' );
105 - if ( $whitelists == null || $whitelists == '' ) {
106 - return false;
107 - }
108 -
109 - // Parse the whitelist.
110 - $whitelists = json_decode( str_replace( '<?php exit; ?>', '', $whitelists ), true );
111 -
112 - // Grab visitor's IP address and request data.
113 - $client_ip = $this->get_ip();
114 - $requests = $this->capture_request();
115 -
116 - foreach ( $whitelists as $whitelist ) {
117 - $whitelist_rule = json_decode( $whitelist['rule'] );
118 - $matched_rules = 0;
119 -
120 - // If matches on all request methods, only 1 rule match is required to whitelist.
121 - if ( $whitelist_rule->method === 'ALL' ) {
122 - $count_rules = 1;
123 - } else {
124 - if ( ! is_null( $whitelist_rule ) ) {
125 - $count_rules = $whitelist_rule->rules;
126 - $count_rules = $this->count_rules( $count_rules );
127 - }
128 - }
129 -
130 - // If an IP address match is given, determine if it matches.
131 - $ip = isset( $whitelist_rule->rules, $whitelist_rule->rules->ip_address ) ? $whitelist_rule->rules->ip_address : null;
132 - if ( ! is_null( $ip ) ) {
133 - if ( strpos( $ip, '*' ) !== false ) {
134 - $whitelisted_ip = $this->plugin->ban->check_wildcard_rule( $client_ip, $ip );
135 - } elseif ( strpos( $ip, '-' ) !== false ) {
136 - $whitelisted_ip = $this->plugin->ban->check_range_rule( $client_ip, $ip );
137 - } elseif ( strpos( $ip, '/' ) !== false ) {
138 - $whitelisted_ip = $this->plugin->ban->check_subnet_mask_rule( $client_ip, $ip );
139 - } elseif ( $client_ip == $ip ) {
140 - $whitelisted_ip = true;
141 - } else {
142 - $whitelisted_ip = false;
143 - }
144 - } else {
145 - $whitelisted_ip = true;
146 - }
147 -
148 - foreach ( $requests as $key => $request ) {
149 - if ( $whitelist_rule->method == $requests['method'] || $whitelist_rule->method == 'ALL' ) {
150 - $test = strtolower( preg_replace( '/(?!^)[A-Z]{2,}(?=[A-Z][a-z])|[A-Z][a-z]/', '->$0', $key ) );
151 - $rule = array_reduce(
152 - explode( '->', $test ),
153 - function ( $o, $p ) {
154 - return $o->$p;
155 - },
156 - $whitelist_rule
157 - );
158 -
159 - if ( ! is_null( $rule ) && substr( $key, 0, 4 ) == 'rule' && $this->is_rule_match( $rule, $request ) ) {
160 - $matched_rules++;
161 - }
162 - }
163 - }
164 -
165 - if ( $matched_rules >= $count_rules && $whitelisted_ip ) {
166 - return true;
167 - }
168 - }
169 -
170 - return false;
171 - }
172 -
173 - /**
174 - * Retrieve all HTTP headers that start with HTTP_.
175 - *
176 - * @return array
177 - */
178 - private function get_headers() {
179 - $headers = array();
180 - foreach ( $_SERVER as $name => $value ) {
181 - if ( substr( $name, 0, 5 ) == 'HTTP_' ) {
182 - $headers[ str_replace( ' ', '-', ucwords( strtolower( str_replace( '_', ' ', substr( $name, 5 ) ) ) ) ) ] = $value;
183 - }
184 - }
185 -
186 - return $headers;
187 - }
188 -
189 - /**
190 - * Retrieve information about any file uploads.
191 - *
192 - * @return array
193 - */
194 - private function get_file_upload_data() {
195 - if ( ! is_array( $_FILES ) || count( $_FILES ) == 0 ) {
196 - return '';
197 - }
198 -
199 - // Extract the information we need from $_FILES.
200 - $return = array();
201 - foreach ( $_FILES as $key => $data ) {
202 - foreach ( $data as $key2 => $data2 ) {
203 -
204 - // We only want the name and type.
205 - if ( ! in_array( $key2, array( 'name', 'type' ) ) ) {
206 - continue;
207 - }
208 -
209 - if ( ! is_array( $data2 ) ) {
210 - $return[] = $key2 . '=' . $data2;
211 - } else {
212 - $return[] = $key2 . '=' . @$this->multi_implode( $data2, '&' . $key2 . '=' );
213 - }
214 - }
215 - }
216 -
217 - return implode( '&', $return );
218 - }
219 -
220 -
221 - /**
222 - * Returns all request methods and parameters
223 - *
224 - * @return string
225 - */
226 - private function capture_request() {
227 - $data = $this->capture_keys();
228 -
229 - // Get the method and URL.
230 - $method = $_SERVER['REQUEST_METHOD'];
231 - $rulesUri = $_SERVER['REQUEST_URI'];
232 -
233 - // Store the header values in different formats.
234 - $rulesHeadersKeys = array();
235 - $rulesHeadersValues = array();
236 - $rulesHeadersCombinations = array();
237 -
238 - // Retrieve the headers.
239 - $headers = $this->get_headers();
240 - $rulesHeadersAll = implode( ' ', $headers );
241 - foreach ( $headers as $name => $value ) {
242 - $rulesHeadersKeys[] = $name;
243 - $rulesHeadersValues[] = $value;
244 - $rulesHeadersCombinations[] = $name . ': ' . $value;
245 - }
246 -
247 - // Store the $_POST values in different formats.
248 - $rulesBodyKeys = array();
249 - $rulesBodyValues = array();
250 - $rulesBodyCombinations = array();
251 -
252 - // Retrieve the $_POST values.
253 - $rulesBodyAll = urldecode( http_build_query( $data['POST'] ) );
254 - foreach ( $data['POST'] as $key => $value ) {
255 - if ( is_array( $value ) ) {
256 - $value = @$this->multi_implode( $value, ' ' );
257 - }
258 - $rulesBodyKeys[] = $key;
259 - $rulesBodyValues[] = $value;
260 - $rulesBodyCombinations[] = $key . '=' . $value;
261 - }
262 -
263 - // Store the $_GET values in different formats.
264 - $rulesParamsKeys = array();
265 - $rulesParamsValues = array();
266 - $rulesParamsCombinations = array();
267 -
268 - // Retrieve the $_GET values.
269 - $rulesParamsAll = urldecode( http_build_query( $data['GET'] ) );
270 - foreach ( $data['GET'] as $key => $value ) {
271 - if ( is_array( $value ) ) {
272 - $value = @$this->multi_implode( $value, ' ' );
273 - }
274 - $rulesParamsKeys[] = $key;
275 - $rulesParamsValues[] = $value;
276 - $rulesParamsCombinations[] = $key . '=' . $value;
277 - }
278 -
279 - // Raw POST data.
280 - $rulesRawPost = @file_get_contents( 'php://input' );
281 -
282 - // Data about file uploads.
283 - $rulesFile = $this->get_file_upload_data();
284 -
285 - // Return each value as its own array.
286 - return compact(
287 - 'method',
288 - 'rulesFile',
289 - 'rulesRawPost',
290 - 'rulesUri',
291 - 'rulesHeadersAll',
292 - 'rulesHeadersKeys',
293 - 'rulesHeadersValues',
294 - 'rulesHeadersCombinations',
295 - 'rulesBodyAll',
296 - 'rulesBodyKeys',
297 - 'rulesBodyValues',
298 - 'rulesBodyCombinations',
299 - 'rulesParamsAll',
300 - 'rulesParamsKeys',
301 - 'rulesParamsValues',
302 - 'rulesParamsCombinations'
37 + // Load the extension.
38 + require_once __DIR__ . '/../lib/patchstack/vendor/autoload.php';
39 + $extension = new Patchstack\Extensions\WordPress\Extension(
40 + [
41 + 'patchstack_basic_firewall_roles' => $this->get_option( 'patchstack_basic_firewall_roles', [ 'administrator', 'editor', 'author' ] ),
42 + 'patchstack_whitelist' => get_option( 'patchstack_whitelist', '' )
43 + ],
44 + $this
303 45 );
304 - }
305 46
306 - /**
307 - * Capture the keys of the request.
308 - *
309 - * @return array
310 - */
311 - private function capture_keys() {
312 - // Data we want to go through.
313 - $data = array(
314 - 'POST' => $_POST,
315 - 'GET' => $_GET,
47 + // Initiate the firewall processor with our settings.
48 + $firewall = new Patchstack\Processor(
49 + $extension,
50 + json_decode(get_option('patchstack_firewall_rules_v3', '[]'), true),
51 + json_decode(get_option('patchstack_whitelist_rules_v3', '[]'), true),
52 + [
53 + 'autoblockAttempts' => $this->get_option( 'patchstack_autoblock_attempts', 10 ),
54 + 'autoblockMinutes' => $this->get_option( 'patchstack_autoblock_minutes', 30 ),
55 + 'autoblockTime' => $this->get_option( 'patchstack_autoblock_blocktime', 60 ),
56 + 'whitelistKeysRules' => json_decode( get_option( 'patchstack_whitelist_keys_rules', '[]' ), true ),
57 + 'mustUsePluginCall' => $muCall
58 + ],
59 + json_decode(get_option('patchstack_firewall_rules', '[]'), true),
60 + json_decode(get_option('patchstack_whitelist_rules', '[]'), true)
316 61 );
317 62
318 - // Determine if there are any keys we should remove from the data set.
319 - if ( get_option( 'patchstack_whitelist_keys_rules', '' ) == '' ) {
320 - return $data;
321 - }
322 -
323 - // Must be valid JSON and decodes to at least 2 primary data arrays.
324 - $keys = json_decode( get_option( 'patchstack_whitelist_keys_rules' ), true );
325 - if ( ! $keys || ! is_array( $keys ) || $keys && count( $keys ) < 2 ) {
326 - return $data;
327 - }
328 -
329 - // Remove the keys where necessary, go through all data types (GET, POST).
330 - foreach ( $keys as $type => $entries ) {
331 -
332 - // Go through all whitelisted actions.
333 - foreach ( $entries as $entry ) {
334 - $t = explode( '.', $entry );
335 -
336 - // For non-multidimensional array checks.
337 - if ( count( $t ) == 1 ) {
338 - // If the value itself exists.
339 - if ( isset( $data[ $type ][ $t[0] ] ) ) {
340 - unset( $data[ $type ][ $t[0] ] );
341 - }
342 -
343 - // For pattern checking.
344 - if ( strpos( $t[0], '*' ) !== false ) {
345 - $star = explode( '*', $t[0] );
346 -
347 - // Loop through all $_POST, $_GET values.
348 - foreach ( $data as $method => $values ) {
349 - foreach ( $values as $key => $value ) {
350 - if ( ! is_array( $value ) && strpos( $key, $star[0] ) !== false ) {
351 - unset( $data[ $method ][ $key ] );
352 - }
353 - }
354 - }
355 - }
356 - continue;
357 - }
358 -
359 - // For multidimensional array checks.
360 - $end =& $data[ $type ];
361 - $skip = false;
362 - foreach ( $t as $var ) {
363 - if ( ! isset( $end[ $var ] ) ) {
364 - $skip = true;
365 - break;
366 - }
367 - $end =& $end[ $var ];
368 - }
369 -
370 - // Since we cannot unset it due to it being a reference variable,
371 - // we just set it to an empty string instead.
372 - if ( ! $skip ) {
373 - $end = '';
374 - }
375 - }
376 - }
377 -
378 - return $data;
63 + // Launch the firewall.
64 + $firewall->launch();
379 65 }
380 66
381 67 /**
382 - * Implode array recursively.
68 + * Determine if the user is authenticated and in the list of whitelisted roles.
383 69 *
384 - * @param $array
385 - * @param $glue
386 - * @return bool|string
387 - */
388 - private function multi_implode( $array, $glue ) {
389 - $ret = '';
390 -
391 - foreach ( $array as $item ) {
392 - if ( is_array( $item ) ) {
393 - $ret .= $this->multi_implode( $item, $glue ) . $glue;
394 - } else {
395 - $ret .= $item . $glue;
396 - }
397 - }
398 -
399 - return substr( $ret, 0, 0 - strlen( $glue ) );
400 - }
401 -
402 - /**
403 - * Determine if the request matches the given firewall or whitelist rule.
404 - *
405 - * @param string $rule
406 - * @param string|array $request
407 70 * @return bool
408 71 */
409 - private function is_rule_match( $rule, $request ) {
410 - $is_matched = false;
411 - if ( is_array( $request ) ) {
412 - foreach ( $request as $key => $value ) {
413 - $is_matched = $this->is_rule_match( $rule, $value );
414 - if ( $is_matched ) {
415 - return $is_matched;
416 - }
417 - }
418 - } else {
419 - return preg_match( $rule, urldecode( $request ) );
72 + public function is_authenticated() {
73 + if ( ! is_user_logged_in() ) {
74 + return false;
420 75 }
421 76
422 - return $is_matched;
423 - }
424 -
425 - /**
426 - * Count the number of rules.
427 - *
428 - * @param array $array
429 - * @return integer
430 - */
431 - private function count_rules( $array ) {
432 - $counter = 0;
433 - if ( is_object( $array ) ) {
434 - $array = (array) $array;
77 + // Get the whitelisted roles.
78 + $roles = $this->get_option( 'patchstack_basic_firewall_roles', [ 'administrator', 'editor', 'author' ] );
79 + if ( ! is_array( $roles ) ) {
80 + return false;
435 81 }
436 -
437 - if ( $array['uri'] ) {
438 - $counter++;
82 +
83 + // Special scenario for super admins on a multisite environment.
84 + if ( in_array( 'administrator', $roles ) && is_multisite() && is_super_admin() ) {
85 + return true;
439 86 }
440 87
441 - foreach ( array( 'body', 'params', 'headers' ) as $type ) {
442 - foreach ( $array[ $type ] as $key => $value ) {
443 - if ( ! is_null( $value ) ) {
444 - $counter++;
445 - }
446 - }
88 + // Get the roles of the user.
89 + $user = wp_get_current_user();
90 + if ( ! isset( $user->roles ) || count( (array) $user->roles ) == 0 ) {
91 + return false;
447 92 }
448 93
449 - return $counter;
94 + // Is the user in the whitelist roles list?
95 + $role_count = array_intersect( $user->roles, $roles );
96 + return count( $role_count ) != 0;
450 97 }
451 98
452 99 /**
453 - * Runs the firewall rules processor.
100 + * Display error page.
454 101 *
102 + * @param integer $fid
455 103 * @return void
456 104 */
457 - private function processor() {
458 - // Load the firewall rules.
459 - $rules = json_decode( get_option( 'patchstack_firewall_rules', '' ), true );
460 - if ( $rules == '' || is_null( $rules ) ) {
461 - return;
105 + public function display_error_page( $fid = 1 ) {
106 + if ( $fid != 22 && $fid != 23 && $fid != 24 && $fid != 'login' ) {
107 + $this->log_request( $fid );
462 108 }
463 109
464 - // Determine if the user is temporarily blocked from the site.
465 - if ( $this->is_auto_ip_blocked() > $this->get_option( 'patchstack_autoblock_attempts', 10 ) && ! $this->is_authenticated() ) {
466 - $this->display_error_page( 22 );
110 + // Supported by a number of popular caching plugins.
111 + if ( ! defined( 'DONOTCACHEPAGE' ) ) {
112 + define( 'DONOTCACHEPAGE', true );
467 113 }
468 114
469 - // Check for whitelist.
470 - $is_whitelisted = $this->is_whitelisted();
115 + // Because WP Fastest Cache just has to be special...
116 + if (function_exists('wpfc_exclude_current_page')) {
117 + @wpfc_exclude_current_page();
118 + }
471 119
472 - // Obtain the IP address and request data.
473 - $client_ip = $this->get_ip();
474 - $requests = $this->capture_request();
120 + // Send forbidden headers and no-caching headers as well.
121 + status_header(403);
122 + send_nosniff_header();
123 + nocache_headers();
475 124
476 - // Iterate through all root objects.
477 - foreach ( $rules as $firewall_rule ) {
478 - $blocked_count = 0;
479 - $firewall_rule['bypass_whitelist'] = isset( $firewall_rule['bypass_whitelist'] ) ? $firewall_rule['bypass_whitelist'] : false;
480 -
481 - // Do we need to skip the whitelist for a particular rule?
482 - if ( isset( $firewall_rule['bypass_whitelist'] ) && ! $firewall_rule['bypass_whitelist'] && $is_whitelisted ) {
483 - continue;
484 - }
485 -
486 - $rule_terms = json_decode( $firewall_rule['rule'] );
487 -
488 - // Determine if we should match the IP address.
489 - $ip = isset( $rule_terms->rules->ip_address ) ? $rule_terms->rules->ip_address : null;
490 - if ( ! is_null( $ip ) ) {
491 - $matched_ip = false;
492 - if ( strpos( $ip, '*' ) !== false ) {
493 - $matched_ip = $this->plugin->ban->check_wildcard_rule( $client_ip, $ip );
494 - } elseif ( strpos( $ip, '-' ) !== false ) {
495 - $matched_ip = $this->plugin->ban->check_range_rule( $client_ip, $ip );
496 - } elseif ( strpos( $ip, '/' ) !== false ) {
497 - $matched_ip = $this->plugin->ban->check_subnet_mask_rule( $client_ip, $ip );
498 - } elseif ( $client_ip == $ip ) {
499 - $matched_ip = true;
500 - }
501 -
502 - if ( ! $matched_ip ) {
503 - continue;
504 - }
505 - }
506 -
507 - // If matches on all request methods, only 1 rule match is required to block
508 - if ( $rule_terms->method === 'ALL' ) {
509 - $count_rules = 1;
510 - } else {
511 - $count_rules = json_decode( json_encode( $rule_terms->rules ), true );
512 - $count_rules = $this->count_rules( $count_rules );
513 - }
514 -
515 - // Loop through all request data that we captured.
516 - foreach ( $requests as $key => $request ) {
517 -
518 - // Treat the raw POST data string as the body contents of all values combined.
519 - if ( $key == 'rulesRawPost' ) {
520 - $key = 'rulesBodyAll';
521 - }
522 -
523 - // Determine if the requesting method matches.
524 - if ( $rule_terms->method == $requests['method'] || $rule_terms->method == 'ALL' || $rule_terms->method == 'GET' || ( $rule_terms->method == 'FILES' && $this->is_file_upload() ) ) {
525 - $test = strtolower( preg_replace( '/(?!^)[A-Z]{2,}(?=[A-Z][a-z])|[A-Z][a-z]/', '->$0', $key ) );
526 - $exp = explode( '->', $test );
527 -
528 - // Determine if a rule exists for this request.
529 - $rule = array_reduce(
530 - $exp,
531 - function ( $o, $p ) {
532 - return $o->$p;
533 - },
534 - $rule_terms
535 - );
536 -
537 - // Determine if the rule matches the request.
538 - if ( ! is_null( $rule ) && substr( $key, 0, 4 ) == 'rule' && $this->is_rule_match( $rule, $request ) ) {
539 - $blocked_count++;
540 - }
541 - }
542 - }
543 -
544 - // Determine if the user should be blocked.
545 - if ( $blocked_count >= $count_rules ) {
546 - if ( $rule_terms->type == 'BLOCK' ) {
547 - $this->block_user( $firewall_rule['id'], (bool) $firewall_rule['bypass_whitelist'] );
548 - } elseif ( $rule_terms->type == 'LOG' ) {
549 - $this->log_user( $firewall_rule['id'] );
550 - } elseif ( $rule_terms->type == 'REDIRECT' ) {
551 - $this->redirect_user( $firewall_rule['id'], $rule_terms->type_params );
552 - }
553 - }
554 - }
555 - }
556 -
557 - /**
558 - * Determine if the current request is a file upload.
559 - *
560 - * @return boolean
561 - */
562 - private function is_file_upload() {
563 - return isset( $_FILES ) && count( $_FILES ) > 0;
564 - }
565 -
566 - /**
567 - * Automatically block the user if there are many blocked requests in a short period of time.
568 - *
569 - * @return integer
570 - */
571 - public function is_auto_ip_blocked() {
572 - // Calculate block time.
573 - $minutes = (int) $this->get_option( 'patchstack_autoblock_minutes', 30 );
574 - $timeout = (int) $this->get_option( 'patchstack_autoblock_blocktime', 60 );
575 - if ( empty( $minutes ) || empty( $timeout ) ) {
576 - $time = 30 + 60;
125 + if ( $fid == 'login' ) {
126 + require_once dirname( __FILE__ ) . '/views/access-denied-login.php';
577 127 } else {
578 - $time = $minutes + $timeout;
128 + require_once dirname( __FILE__ ) . '/views/access-denied.php';
579 129 }
580 -
581 - // Determine if the user should be blocked.
582 - global $wpdb;
583 - $results = $wpdb->get_results(
584 - $wpdb->prepare( 'SELECT COUNT(*) as numIps FROM ' . $wpdb->prefix . "patchstack_firewall_log WHERE block_type = 'BLOCK' AND apply_ban = 1 AND ip = '%s' AND log_date >= ('" . current_time( 'mysql' ) . "' - INTERVAL %d MINUTE)", array( $this->get_ip(), $time ) ),
585 - OBJECT
586 - );
587 -
588 - if ( ! isset( $results, $results[0], $results[0]->numIps ) ) {
589 - return 0;
590 - }
591 - return $results[0]->numIps;
592 - }
593 -
594 - /**
595 - * Block the user, and log, do whatever is necessary.
596 - *
597 - * @param string $rule
598 - * @param bool $bypass
599 - * @return void
600 - */
601 - private function block_user( $rule, $bypass = false ) {
602 - if ( ! $this->is_authenticated( $bypass ) ) {
603 - $this->display_error_page( '55' . intval( $rule ) );
604 - }
605 - }
606 -
607 - /**
608 - * Log the user action.
609 - *
610 - * @param string $rule
611 - * @return void
612 - */
613 - private function log_user( $rule ) {
614 - $this->log_hacker( $rule, '', 'LOG' );
615 - }
616 -
617 - /**
618 - * Log the user action and redirect.
619 - *
620 - * @param integer $rule_id
621 - * @param string $redirect
622 - * @return void
623 - */
624 - private function redirect_user( $rule_id, $redirect ) {
625 - $this->log_hacker( $rule_id, '', 'REDIRECT' );
626 -
627 - // Don't redirect an invalid URL.
628 - if ( ! $redirect || stripos( $redirect, 'http' ) === false ) {
629 - return;
630 - }
631 -
632 - ob_start();
633 - header( 'Location: ' . $redirect );
634 - ob_end_flush();
130 +
635 131 exit;
636 132 }
637 133
638 134 /**
639 - * Determine if the user is authenticated and in the list of whitelisted roles.
640 - *
641 - * @param bool $bypass
642 - * @return bool
643 - */
644 - public function is_authenticated( $bypass = false ) {
645 - if ( $bypass || ! is_user_logged_in() ) {
646 - return false;
647 - }
648 -
649 - // Special scenario for super admins on a multisite environment.
650 - $roles = $this->get_option( 'patchstack_basic_firewall_roles', array( 'administrator', 'editor', 'author' ) );
651 - if ( in_array( 'administrator', $roles ) && is_multisite() && is_super_admin() ) {
652 - return true;
653 - }
654 -
655 - // User is logged in, determine the role.
656 - $user = wp_get_current_user();
657 - if ( ! isset( $user->roles ) || count( (array) $user->roles ) == 0 ) {
658 - return false;
659 - }
660 -
661 - // Is the user in the whitelist roles list?
662 - $role_count = array_intersect( $user->roles, $roles );
663 - return count( $role_count ) != 0;
664 - }
665 -
666 - /**
667 135 * Log the blocked request.
668 - *
669 - * @param integer $fid firewall
670 - * @param array $query_vars
671 - * @param string $block_type
672 - * @param array $block_params
136 + *
137 + * @param int $fid
673 138 * @return void
674 139 */
675 - private function log_hacker( $fid = 1, $post_data = '', $block_type = 'BLOCK' ) {
140 + private function log_request( $fid = 1 ) {
676 141 global $wpdb;
677 - if ( ! $wpdb || $fid == 22 ) {
142 + if ( ! $wpdb || $fid == 22 || $fid == 23 || $fid == 24 || $fid == 'login' ) {
678 143 return;
679 144 }
680 145
681 146 // Insert into the logs.
@@ -687,41 +152,43 @@
687 152 'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '',
688 153 'method' => isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : '',
689 154 'fid' => $fid,
690 155 'flag' => '',
691 - 'post_data' => $post_data != '' ? json_encode( $post_data ) : $this->get_post_data(),
692 - 'block_type' => $block_type,
156 + 'post_data' => '',
157 + 'block_type' => 'BLOCK',
693 158 )
694 159 );
695 160 }
696 161
697 162 /**
698 - * Get POST data.
699 - *
700 - * @return string|NULL
163 + * Extract the number of blocked hits the past 30 days based on the current counters.
164 + *
165 + * @return int
701 166 */
702 - private function get_post_data() {
703 - if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || $_SERVER['REQUEST_METHOD'] != 'POST' ) {
704 - return null;
167 + public function get_hits_counter() {
168 + $counters = get_option( 'patchstack_hits_last_30', [] );
169 + if (!is_array($counters) || count($counters) === 0) {
170 + return 0;
705 171 }
706 172
707 - return json_encode( $_POST );
708 - }
173 + // Set the range of dates we need.
174 + $hits = 0;
175 + $start = new \DateTime();
176 + $start->modify('-30 days');
709 177
710 - /**
711 - * Display error page.
712 - *
713 - * @param integer $fid
714 - * @return void
715 - */
716 - public function display_error_page( $fid = 1 ) {
717 - if ( $fid != 22 ) {
718 - $this->log_hacker( $fid );
719 - }
178 + $end = new \DateTime();
179 + $end->modify('+1 day');
720 180
721 - header( 'Cache-Control: no-store' );
722 - header( 'Pragma: no-cache' );
723 - http_response_code( 403 );
724 - require_once dirname( __FILE__ ) . '/views/access-denied.php';
725 - exit;
181 + $interval = new \DateInterval('P1D');
182 + $range = new \DatePeriod($start, $interval, $end);
183 +
184 + // Set the range from -6 days to +1 day from now.
185 + foreach ($range as $date) {
186 + $formattedDate = $date->format('Y-m-d');
187 + if (isset($counters[$formattedDate])) {
188 + $hits += $counters[$formattedDate];
189 + }
190 + }
191 +
192 + return $hits;
726 193 }
727 194 }