← All changes
|
jetpack_vendor/automattic/jetpack-waf/src/class-waf-runtime.php
+168
-109
12.7.3
→
16.3
View file →
| @@ -18,13 +18,16 @@ | ||
| 18 | 18 | * |
| 19 | 19 | * @var string JETPACK_WAF_MODE |
| 20 | 20 | */ |
| 21 | 21 | |
| 22 | +// Type aliases for this file. | |
| 23 | +<<<'PHAN' | |
| 24 | +@phan-type Target = array{ only?: string[], except?: string[], count?: boolean } | |
| 25 | +@phan-type TargetBag = array<string, Target> | |
| 26 | +PHAN; | |
| 27 | + | |
| 22 | 28 | /** |
| 23 | 29 | * Waf_Runtime class |
| 24 | - * | |
| 25 | - * @template Target as array{ only?: string[], except?: string[], count?: boolean } | |
| 26 | - * @template TargetBag as array<string, Target> | |
| 27 | 30 | */ |
| 28 | 31 | class Waf_Runtime { |
| 29 | 32 | /** |
| 30 | 33 | * If used, normalize_array_targets() will just return the number of matching values, instead of the values themselves. |
| @@ -35,8 +38,16 @@ | ||
| 35 | 38 | */ |
| 36 | 39 | const NORMALIZE_ARRAY_MATCH_VALUES = 2; |
| 37 | 40 | |
| 38 | 41 | /** |
| 42 | + * The version of this runtime class. Used by rule files to ensure compatibility. | |
| 43 | + * | |
| 44 | + * @since 0.21.0 | |
| 45 | + * | |
| 46 | + * @var int | |
| 47 | + */ | |
| 48 | + public $version = 1; | |
| 49 | + /** | |
| 39 | 50 | * Last rule. |
| 40 | 51 | * |
| 41 | 52 | * @var string |
| 42 | 53 | */ |
| @@ -57,9 +68,9 @@ | ||
| 57 | 68 | * Matched var names. |
| 58 | 69 | * |
| 59 | 70 | * @var array |
| 60 | 71 | */ |
| 61 | - public $matched_var_names = array(); | |
| 72 | + public $matched_vars_names = array(); | |
| 62 | 73 | /** |
| 63 | 74 | * Matched var name. |
| 64 | 75 | * |
| 65 | 76 | * @var string |
| @@ -64,8 +75,14 @@ | ||
| 64 | 75 | * |
| 65 | 76 | * @var string |
| 66 | 77 | */ |
| 67 | 78 | public $matched_var_name = ''; |
| 79 | + /** | |
| 80 | + * Body Processor. | |
| 81 | + * | |
| 82 | + * @var string 'URLENCODED' | 'JSON' | '' | |
| 83 | + */ | |
| 84 | + private $body_processor = ''; | |
| 68 | 85 | |
| 69 | 86 | /** |
| 70 | 87 | * State. |
| 71 | 88 | * |
| @@ -123,9 +140,9 @@ | ||
| 123 | 140 | * Constructor method. |
| 124 | 141 | * |
| 125 | 142 | * @param Waf_Transforms $transforms Transforms. |
| 126 | 143 | * @param Waf_Operators $operators Operators. |
| 127 | - * @param Waf_Request? $request Information about the request. | |
| 144 | + * @param ?Waf_Request $request Information about the request. | |
| 128 | 145 | */ |
| 129 | 146 | public function __construct( $transforms, $operators, $request = null ) { |
| 130 | 147 | $this->transforms = $transforms; |
| 131 | 148 | $this->operators = $operators; |
| @@ -185,9 +202,9 @@ | ||
| 185 | 202 | unset( $targets[ $name ] ); |
| 186 | 203 | } else { |
| 187 | 204 | // otherwise just mark single props to ignore. |
| 188 | 205 | $targets[ $name ]['except'] = array_merge( |
| 189 | - isset( $targets[ $name ]['except'] ) ? $targets[ $name ]['except'] : array(), | |
| 206 | + $targets[ $name ]['except'] ?? array(), | |
| 190 | 207 | $props |
| 191 | 208 | ); |
| 192 | 209 | } |
| 193 | 210 | } |
| @@ -206,13 +223,9 @@ | ||
| 206 | 223 | * @param bool $capture Capture. |
| 207 | 224 | * @return bool |
| 208 | 225 | */ |
| 209 | 226 | public function match_targets( $transforms, $targets, $match_operator, $match_value, $match_not, $capture = false ) { |
| 210 | - $this->matched_vars = array(); | |
| 211 | - $this->matched_var_names = array(); | |
| 212 | - $this->matched_var = ''; | |
| 213 | - $this->matched_var_name = ''; | |
| 214 | - $match_found = false; | |
| 227 | + $match_found = false; | |
| 215 | 228 | |
| 216 | 229 | // get values. |
| 217 | 230 | $values = $this->normalize_targets( $targets ); |
| 218 | 231 | |
| @@ -233,14 +246,14 @@ | ||
| 233 | 246 | // If either: |
| 234 | 247 | // - rule is negated ("not" flag set) and the target was not matched |
| 235 | 248 | // - rule not negated and the target was matched |
| 236 | 249 | // then this is considered a match. |
| 237 | - $match_found = true; | |
| 238 | - $this->matched_var_names[] = $v['source']; | |
| 239 | - $this->matched_vars[] = $v['value']; | |
| 240 | - $this->matched_var_name = end( $this->matched_var_names ); | |
| 241 | - $this->matched_var = end( $this->matched_vars ); | |
| 242 | - $matched[] = array( $v, $match ); | |
| 250 | + $match_found = true; | |
| 251 | + $this->matched_vars_names[] = $v['name']; | |
| 252 | + $this->matched_vars[] = $v['value']; | |
| 253 | + $this->matched_var_name = end( $this->matched_vars_names ); | |
| 254 | + $this->matched_var = end( $this->matched_vars ); | |
| 255 | + $matched[] = array( $v, $match ); | |
| 243 | 256 | // Set any captured matches into state if the rule has the "capture" flag. |
| 244 | 257 | if ( $capture ) { |
| 245 | 258 | $captures = is_array( $match ) ? $match : array( $match ); |
| 246 | 259 | foreach ( array_slice( $captures, 0, 10 ) as $i => $c ) { |
| @@ -253,106 +266,85 @@ | ||
| 253 | 266 | return $match_found; |
| 254 | 267 | } |
| 255 | 268 | |
| 256 | 269 | /** |
| 257 | - * Block. | |
| 270 | + * Generate a secure hash for an IP address. | |
| 258 | 271 | * |
| 259 | - * @param string $action Action. | |
| 260 | - * @param string $rule_id Rule id. | |
| 261 | - * @param string $reason Block reason. | |
| 262 | - * @param int $status_code Http status code. | |
| 272 | + * @param string $ip IP address. | |
| 273 | + * @return string Hashed IP. | |
| 263 | 274 | */ |
| 264 | - public function block( $action, $rule_id, $reason, $status_code = 403 ) { | |
| 265 | - if ( ! $reason ) { | |
| 266 | - $reason = "rule $rule_id"; | |
| 267 | - } else { | |
| 268 | - $reason = $this->sanitize_output( $reason ); | |
| 269 | - } | |
| 275 | + private function get_ip_hash( string $ip ): string { | |
| 276 | + $hash_key = wp_salt( 'auth' ); | |
| 277 | + return hash_hmac( 'sha256', $ip, $hash_key ); | |
| 278 | + } | |
| 270 | 279 | |
| 271 | - $this->write_blocklog( $rule_id, $reason ); | |
| 272 | - error_log( "Jetpack WAF Blocked Request\t$action\t$rule_id\t$status_code\t$reason" ); | |
| 273 | - header( "X-JetpackWAF-Blocked: $status_code - rule $rule_id" ); | |
| 274 | - if ( defined( 'JETPACK_WAF_MODE' ) && 'normal' === JETPACK_WAF_MODE ) { | |
| 275 | - $protocol = isset( $_SERVER['SERVER_PROTOCOL'] ) ? wp_unslash( $_SERVER['SERVER_PROTOCOL'] ) : 'HTTP'; | |
| 276 | - header( $protocol . ' 403 Forbidden', true, $status_code ); | |
| 277 | - die( "rule $rule_id - reason $reason" ); | |
| 278 | - } | |
| 280 | + /** | |
| 281 | + * Check if the IP is allowed for recovery. | |
| 282 | + * | |
| 283 | + * @param string $ip IP address. | |
| 284 | + * @return bool | |
| 285 | + */ | |
| 286 | + public function is_ip_allowed_for_recovery( string $ip ): bool { | |
| 287 | + $allow_hash = get_transient( 'jetpack_waf_recovery_' . $ip ); | |
| 288 | + return $allow_hash && hash_equals( $allow_hash, $this->get_ip_hash( $ip ) ); | |
| 279 | 289 | } |
| 280 | 290 | |
| 281 | 291 | /** |
| 282 | - * Write block logs. We won't write to the file if it exceeds 100 mb. | |
| 292 | + * Process a recovery attempt. | |
| 283 | 293 | * |
| 284 | - * @param string $rule_id Rule id. | |
| 285 | - * @param string $reason Block reason. | |
| 294 | + * @param string $real_ip The real IP address of the request. | |
| 286 | 295 | */ |
| 287 | - public function write_blocklog( $rule_id, $reason ) { | |
| 288 | - $log_data = array(); | |
| 289 | - $log_data['rule_id'] = $rule_id; | |
| 290 | - $log_data['reason'] = $reason; | |
| 291 | - $log_data['timestamp'] = gmdate( 'Y-m-d H:i:s' ); | |
| 296 | + private function allow_login_or_prompt_recovery( $real_ip ) { | |
| 297 | + $blocked_login_page = Waf_Blocked_Login_Page::instance( $real_ip ); | |
| 292 | 298 | |
| 293 | - if ( defined( 'JETPACK_WAF_SHARE_DATA' ) && JETPACK_WAF_SHARE_DATA ) { | |
| 294 | - $file_path = JETPACK_WAF_DIR . '/waf-blocklog'; | |
| 295 | - $file_exists = file_exists( $file_path ); | |
| 296 | - | |
| 297 | - if ( ! $file_exists || filesize( $file_path ) < ( 100 * 1024 * 1024 ) ) { | |
| 298 | - $fp = fopen( $file_path, 'a+' ); | |
| 299 | - | |
| 300 | - if ( $fp ) { | |
| 301 | - try { | |
| 302 | - fwrite( $fp, json_encode( $log_data ) . "\n" ); | |
| 303 | - } finally { | |
| 304 | - fclose( $fp ); | |
| 305 | - } | |
| 306 | - } | |
| 307 | - } | |
| 299 | + if ( $blocked_login_page->is_blocked_user_valid() ) { | |
| 300 | + // Allow the IP to bypass the block for 15 minutes. | |
| 301 | + set_transient( 'jetpack_waf_recovery_' . $real_ip, $this->get_ip_hash( $real_ip ), 15 * 60 ); | |
| 302 | + return; | |
| 308 | 303 | } |
| 309 | 304 | |
| 310 | - $this->write_blocklog_row( $log_data ); | |
| 305 | + $blocked_login_page->render_and_die(); | |
| 311 | 306 | } |
| 312 | 307 | |
| 313 | 308 | /** |
| 314 | - * Write block logs to database. | |
| 309 | + * Block. | |
| 315 | 310 | * |
| 316 | - * @param array $log_data Log data. | |
| 311 | + * @param string $action Action. | |
| 312 | + * @param string $rule_id Rule id. | |
| 313 | + * @param string $reason Block reason. | |
| 314 | + * @param int $status_code Http status code. | |
| 317 | 315 | */ |
| 318 | - private function write_blocklog_row( $log_data ) { | |
| 319 | - $conn = $this->connect_to_wordpress_db(); | |
| 316 | + public function block( $action, $rule_id, $reason, $status_code = 403 ) { | |
| 317 | + // The recovery flow needs transients, `wp_salt()` and `$pagenow`, so it cannot run in | |
| 318 | + // standalone mode, where the WAF executes from `auto_prepend_file` before WordPress. | |
| 319 | + if ( 'ip block list' === $reason && defined( 'ABSPATH' ) ) { | |
| 320 | + $real_ip = $this->request->get_real_user_ip_address(); | |
| 320 | 321 | |
| 321 | - if ( ! $conn ) { | |
| 322 | - return; | |
| 323 | - } | |
| 322 | + if ( $this->is_ip_allowed_for_recovery( $real_ip ) ) { | |
| 323 | + return; | |
| 324 | + } | |
| 324 | 325 | |
| 325 | - global $table_prefix; | |
| 326 | - | |
| 327 | - $statement = $conn->prepare( "INSERT INTO {$table_prefix}jetpack_waf_blocklog(reason,rule_id, timestamp) VALUES (?, ?, ?)" ); | |
| 328 | - if ( false !== $statement ) { | |
| 329 | - $statement->bind_param( 'sis', $log_data['reason'], $log_data['rule_id'], $log_data['timestamp'] ); | |
| 330 | - $statement->execute(); | |
| 331 | - | |
| 332 | - if ( $conn->insert_id > 100 ) { | |
| 333 | - $conn->query( "DELETE FROM {$table_prefix}jetpack_waf_blocklog ORDER BY log_id LIMIT 1" ); | |
| 326 | + global $pagenow; | |
| 327 | + if ( isset( $pagenow ) && 'wp-login.php' === $pagenow ) { | |
| 328 | + $this->allow_login_or_prompt_recovery( $real_ip ); | |
| 329 | + return; | |
| 334 | 330 | } |
| 335 | 331 | } |
| 336 | - } | |
| 337 | 332 | |
| 338 | - /** | |
| 339 | - * Connect to WordPress database. | |
| 340 | - */ | |
| 341 | - private function connect_to_wordpress_db() { | |
| 342 | - if ( ! file_exists( JETPACK_WAF_WPCONFIG ) ) { | |
| 343 | - return; | |
| 333 | + if ( ! $reason ) { | |
| 334 | + $reason = "rule $rule_id"; | |
| 335 | + } else { | |
| 336 | + $reason = $this->sanitize_output( $reason ); | |
| 344 | 337 | } |
| 345 | 338 | |
| 346 | - require_once JETPACK_WAF_WPCONFIG; | |
| 347 | - $conn = new \mysqli( DB_HOST, DB_USER, DB_PASSWORD, DB_NAME ); // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__mysqli | |
| 348 | - | |
| 349 | - if ( $conn->connect_error ) { | |
| 350 | - error_log( 'Could not connect to the database:' . $conn->connect_error ); | |
| 351 | - return null; | |
| 339 | + Waf_Blocklog_Manager::write_blocklog( $rule_id, $reason ); | |
| 340 | + error_log( "Jetpack WAF Blocked Request\t$action\t$rule_id\t$status_code\t$reason" ); | |
| 341 | + header( "X-JetpackWAF-Blocked: $status_code - rule $rule_id" ); | |
| 342 | + if ( defined( 'JETPACK_WAF_MODE' ) && 'normal' === JETPACK_WAF_MODE ) { | |
| 343 | + $protocol = isset( $_SERVER['SERVER_PROTOCOL'] ) ? wp_unslash( $_SERVER['SERVER_PROTOCOL'] ) : 'HTTP'; | |
| 344 | + header( $protocol . ' 403 Forbidden', true, $status_code ); | |
| 345 | + die( "rule $rule_id - reason $reason" ); | |
| 352 | 346 | } |
| 353 | - | |
| 354 | - return $conn; | |
| 355 | 347 | } |
| 356 | 348 | |
| 357 | 349 | /** |
| 358 | 350 | * Redirect. |
| @@ -358,13 +350,14 @@ | ||
| 358 | 350 | * Redirect. |
| 359 | 351 | * |
| 360 | 352 | * @param string $rule_id Rule id. |
| 361 | 353 | * @param string $url Url. |
| 354 | + * @return never | |
| 362 | 355 | */ |
| 363 | 356 | public function redirect( $rule_id, $url ) { |
| 364 | 357 | error_log( "Jetpack WAF Redirected Request.\tRule:$rule_id\t$url" ); |
| 365 | 358 | header( "Location: $url" ); |
| 366 | - exit; | |
| 359 | + exit( 0 ); | |
| 367 | 360 | } |
| 368 | 361 | |
| 369 | 362 | /** |
| 370 | 363 | * Flag rule for removal. |
| @@ -405,11 +398,9 @@ | ||
| 405 | 398 | * |
| 406 | 399 | * @param string $key Key. |
| 407 | 400 | */ |
| 408 | 401 | public function get_var( $key ) { |
| 409 | - return isset( $this->state[ $key ] ) | |
| 410 | - ? $this->state[ $key ] | |
| 411 | - : ''; | |
| 402 | + return $this->state[ $key ] ?? ''; | |
| 412 | 403 | } |
| 413 | 404 | |
| 414 | 405 | /** |
| 415 | 406 | * Set variable value. |
| @@ -494,9 +485,9 @@ | ||
| 494 | 485 | $this->request->get_protocol() |
| 495 | 486 | ); |
| 496 | 487 | break; |
| 497 | 488 | case 'request_basename': |
| 498 | - $value = basename( $this->request->get_filename() ); | |
| 489 | + $value = $this->request->get_basename(); | |
| 499 | 490 | break; |
| 500 | 491 | case 'request_body': |
| 501 | 492 | $value = $this->request->get_body(); |
| 502 | 493 | break; |
| @@ -509,9 +500,9 @@ | ||
| 509 | 500 | case 'args_get_names': |
| 510 | 501 | $value = $this->args_names( $this->meta( 'args_get' ) ); |
| 511 | 502 | break; |
| 512 | 503 | case 'args_post': |
| 513 | - $value = $this->request->get_post_vars(); | |
| 504 | + $value = $this->request->get_post_vars( $this->get_body_processor() ); | |
| 514 | 505 | break; |
| 515 | 506 | case 'args_post_names': |
| 516 | 507 | $value = $this->args_names( $this->meta( 'args_post' ) ); |
| 517 | 508 | break; |
| @@ -535,8 +526,20 @@ | ||
| 535 | 526 | break; |
| 536 | 527 | case 'files_names': |
| 537 | 528 | $value = $this->args_names( $this->meta( 'files' ) ); |
| 538 | 529 | break; |
| 530 | + case 'matched_vars': | |
| 531 | + $value = array_combine( $this->matched_vars_names, $this->matched_vars ); | |
| 532 | + break; | |
| 533 | + case 'matched_var': | |
| 534 | + $value = array( $this->matched_var_name => $this->matched_var ); | |
| 535 | + break; | |
| 536 | + case 'matched_vars_names': | |
| 537 | + $value = $this->matched_vars_names; | |
| 538 | + break; | |
| 539 | + case 'matched_var_name': | |
| 540 | + $value = array( $this->matched_var_name ); | |
| 541 | + break; | |
| 539 | 542 | } |
| 540 | 543 | $this->metadata[ $key ] = $value; |
| 541 | 544 | } |
| 542 | 545 | |
| @@ -560,8 +563,30 @@ | ||
| 560 | 563 | return $output; |
| 561 | 564 | } |
| 562 | 565 | |
| 563 | 566 | /** |
| 567 | + * Get the body processor. | |
| 568 | + * | |
| 569 | + * @return string | |
| 570 | + */ | |
| 571 | + private function get_body_processor() { | |
| 572 | + return $this->body_processor; | |
| 573 | + } | |
| 574 | + | |
| 575 | + /** | |
| 576 | + * Set the body processor. | |
| 577 | + * | |
| 578 | + * @param string $processor Processor to set. Either 'URLENCODED' or 'JSON'. | |
| 579 | + * | |
| 580 | + * @return void | |
| 581 | + */ | |
| 582 | + public function set_body_processor( $processor ) { | |
| 583 | + if ( $processor === 'URLENCODED' || $processor === 'JSON' ) { | |
| 584 | + $this->body_processor = $processor; | |
| 585 | + } | |
| 586 | + } | |
| 587 | + | |
| 588 | + /** | |
| 564 | 589 | * Change a string to all lowercase and replace spaces and underscores with dashes. |
| 565 | 590 | * |
| 566 | 591 | * @param string $name Name. |
| 567 | 592 | * @return string |
| @@ -588,16 +613,16 @@ | ||
| 588 | 613 | * source: For targets that are associative arrays (like ARGS), this will be the target name AND the key in that target (i.e. "args:z" for ARGS:z) |
| 589 | 614 | * value: The value that was found in the associated target. |
| 590 | 615 | * |
| 591 | 616 | * @param TargetBag $targets An assoc. array with keys that are target name(s) and values are options for how to process that target (include/exclude rules, whether to return values or counts). |
| 592 | - * @return array{ name: string, source: string, value: mixed } | |
| 617 | + * @return array{name: string, source: string, value: mixed}[] | |
| 593 | 618 | */ |
| 594 | 619 | public function normalize_targets( $targets ) { |
| 595 | 620 | $return = array(); |
| 596 | 621 | foreach ( $targets as $k => $v ) { |
| 597 | 622 | $count_only = isset( $v['count'] ) ? self::NORMALIZE_ARRAY_COUNT : 0; |
| 598 | - $only = isset( $v['only'] ) ? $v['only'] : array(); | |
| 599 | - $except = isset( $v['except'] ) ? $v['except'] : array(); | |
| 623 | + $only = $v['only'] ?? array(); | |
| 624 | + $except = $v['except'] ?? array(); | |
| 600 | 625 | $_k = strtolower( $k ); |
| 601 | 626 | switch ( $_k ) { |
| 602 | 627 | case 'request_headers': |
| 603 | 628 | $this->normalize_array_target( |
| @@ -649,11 +674,27 @@ | ||
| 649 | 674 | $this->meta( substr( $_k, 0, -6 ) ) |
| 650 | 675 | ); |
| 651 | 676 | $this->normalize_array_target( $data, $only, $except, $k, $return, $count_only | self::NORMALIZE_ARRAY_MATCH_VALUES ); |
| 652 | 677 | continue 2; |
| 678 | + case 'matched_var': | |
| 679 | + $this->normalize_array_target( $this->meta( $k ), $only, $except, $k, $return, $count_only ); | |
| 680 | + continue 2; | |
| 681 | + | |
| 682 | + case 'matched_var_name': | |
| 683 | + $this->normalize_array_target( $this->meta( $k ), $only, $except, $k, $return, $count_only | self::NORMALIZE_ARRAY_MATCH_VALUES ); | |
| 684 | + continue 2; | |
| 685 | + | |
| 686 | + case 'matched_vars': | |
| 687 | + $this->normalize_array_target( $this->meta( $k ), $only, $except, $k, $return, $count_only ); | |
| 688 | + continue 2; | |
| 689 | + | |
| 690 | + case 'matched_vars_names': | |
| 691 | + $this->normalize_array_target( $this->meta( $k ), $only, $except, $k, $return, $count_only | self::NORMALIZE_ARRAY_MATCH_VALUES ); | |
| 692 | + continue 2; | |
| 693 | + | |
| 653 | 694 | default: |
| 654 | 695 | var_dump( 'Unknown target', $k, $v ); |
| 655 | - exit; | |
| 696 | + exit( 0 ); | |
| 656 | 697 | } |
| 657 | 698 | $return[] = array( |
| 658 | 699 | 'name' => $k, |
| 659 | 700 | 'value' => $v, |
| @@ -664,8 +705,26 @@ | ||
| 664 | 705 | return $return; |
| 665 | 706 | } |
| 666 | 707 | |
| 667 | 708 | /** |
| 709 | + * Reset matched vars after processing a rule. | |
| 710 | + * | |
| 711 | + * @return void | |
| 712 | + */ | |
| 713 | + public function reset_matched_vars() { | |
| 714 | + $this->matched_vars = array(); | |
| 715 | + $this->matched_vars_names = array(); | |
| 716 | + $this->matched_var = ''; | |
| 717 | + $this->matched_var_name = ''; | |
| 718 | + unset( | |
| 719 | + $this->metadata['matched_var'], | |
| 720 | + $this->metadata['matched_vars'], | |
| 721 | + $this->metadata['matched_vars_names'], | |
| 722 | + $this->metadata['matched_var_name'] | |
| 723 | + ); | |
| 724 | + } | |
| 725 | + | |
| 726 | + /** | |
| 668 | 727 | * Verifies if the IP from the current request is in an array. |
| 669 | 728 | * |
| 670 | 729 | * @param array $array Array of IP addresses to verify the request IP against. |
| 671 | 730 | * @return bool |
| @@ -674,10 +733,10 @@ | ||
| 674 | 733 | $real_ip = $this->request->get_real_user_ip_address(); |
| 675 | 734 | $array_length = count( $array ); |
| 676 | 735 | |
| 677 | 736 | for ( $i = 0; $i < $array_length; $i++ ) { |
| 678 | - // Check if the IP matches a provided range. | |
| 679 | - $range = explode( '-', $array[ $i ] ); | |
| 737 | + // Check if the IP matches a provided range or CIDR notation. | |
| 738 | + $range = strpos( $array[ $i ], '/' ) !== false ? array( $array[ $i ], null ) : explode( '-', $array[ $i ] ); | |
| 680 | 739 | if ( count( $range ) === 2 ) { |
| 681 | 740 | if ( IP_Utils::ip_address_is_in_range( $real_ip, $range[0], $range[1] ) ) { |
| 682 | 741 | return true; |
| 683 | 742 | } |
| @@ -695,14 +754,14 @@ | ||
| 695 | 754 | |
| 696 | 755 | /** |
| 697 | 756 | * Extract values from an associative array, potentially applying filters and/or counting results. |
| 698 | 757 | * |
| 699 | - * @param array{ 0: string, 1: scalar }|scalar[] $source The source assoc. array of values (i.e. $_GET, $_SERVER, etc.). | |
| 700 | - * @param string[] $only Only include the values for these keys in the output. | |
| 701 | - * @param string[] $excl Never include the values for these keys in the output. | |
| 702 | - * @param string $name The name of this target (see https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-(v3.x)#Variables). | |
| 703 | - * @param array $results Array to add output values to, will be modified by this method. | |
| 704 | - * @param int $flags Any of the NORMALIZE_ARRAY_* constants defined at the top of the class. | |
| 758 | + * @param array{0: string, 1: scalar}|scalar[] $source The source assoc. array of values (i.e. $_GET, $_SERVER, etc.). | |
| 759 | + * @param string[] $only Only include the values for these keys in the output. | |
| 760 | + * @param string[] $excl Never include the values for these keys in the output. | |
| 761 | + * @param string $name The name of this target (see https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-(v3.x)#Variables). | |
| 762 | + * @param array $results Array to add output values to, will be modified by this method. | |
| 763 | + * @param int $flags Any of the NORMALIZE_ARRAY_* constants defined at the top of the class. | |
| 705 | 764 | */ |
| 706 | 765 | private function normalize_array_target( $source, $only, $excl, $name, &$results, $flags = 0 ) { |
| 707 | 766 | $output = array(); |
| 708 | 767 | $has_only = isset( $only[0] ); |