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