Skip to content

Commit

Permalink
WAF: Add support for handling IP ranges in allow/block lists (#29131)
Browse files Browse the repository at this point in the history
  • Loading branch information
nateweller committed Feb 27, 2023
1 parent c75fd47 commit 8fa35bc
Show file tree
Hide file tree
Showing 12 changed files with 282 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Added a utility function to extract an array of IP addresses from a given string.
76 changes: 76 additions & 0 deletions projects/packages/ip/src/class-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,80 @@ public static function ip_address_is_in_range( $ip, $range_low, $range_high ) {
return false;
}

/**
* Extracts IP addresses from a given string.
*
* We allow for both, one IP per line or comma-; semicolon; or whitespace-separated lists. This also validates the IP addresses
* and only returns the ones that look valid. IP ranges using the "-" character are also supported.
*
* @param string $ips List of ips - example: "8.8.8.8\n4.4.4.4,2.2.2.2;1.1.1.1 9.9.9.9,5555.5555.5555.5555,1.1.1.10-1.1.1.20".
* @return array List of valid IP addresses. - example based on input example: array('8.8.8.8', '4.4.4.4', '2.2.2.2', '1.1.1.1', '9.9.9.9', '1.1.1.10-1.1.1.20')
*/
public static function get_ip_addresses_from_string( $ips ) {
$ips = (string) $ips;
$ips = preg_split( '/[\s,;]/', $ips );

$result = array();

foreach ( $ips as $ip ) {
// Validate both IP values from the range.
$range = explode( '-', $ip );
if ( count( $range ) === 2 ) {
if ( self::validate_ip_range( $range[0], $range[1] ) ) {
$result[] = $ip;
}
continue;
}

// Validate the single IP value.
if ( filter_var( $ip, FILTER_VALIDATE_IP ) !== false ) {
$result[] = $ip;
}
}

return $result;
}

/**
* Validates the low and high IP addresses of a range.
*
* NOTE: servers that do not support inet_pton cannot support ipv6.
*
* @param string $range_low Low IP address.
* @param string $range_high High IP address.
* @return bool True if the range is valid, false otherwise.
*/
public static function validate_ip_range( $range_low, $range_high ) {
// Validate that both IP addresses are valid.
if ( ! filter_var( $range_low, FILTER_VALIDATE_IP ) || ! filter_var( $range_high, FILTER_VALIDATE_IP ) ) {
return false;
}

// Validate that the $range_low is lower or equal to $range_high.
if ( function_exists( 'inet_pton' ) ) {
// The inet_pton will give us binary string of an ipv4 or ipv6.
// We can then use strcmp to see if the address is in range.
$ip_low = inet_pton( $range_low );
$ip_high = inet_pton( $range_high );
if ( false === $ip_low || false === $ip_high ) {
return false;
}
if ( strcmp( $ip_low, $ip_high ) > 0 ) {
return false;
}
} else {
// The ip2long will give us an integer of an ipv4 address only. it will produce FALSE for ipv6.
$ip_low = ip2long( $range_low );
$ip_high = ip2long( $range_high );
if ( false === $ip_low || false === $ip_high ) {
return false;
}
if ( $ip_low > $ip_high ) {
return false;
}
}

return true;
}

}
51 changes: 51 additions & 0 deletions projects/packages/ip/tests/php/test-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -305,4 +305,55 @@ public function test_ip_address_is_in_range() {
$this->assertFalse( Utils::ip_address_is_in_range( $out_range_ip, $range_low, $range_high ) );
}

/**
* Test `get_ip_addresses_from_string`.
* Covers IPv4 and IPv6 addresses, including ranges, concatenated with various delimiters.
*
* @covers ::get_ip_addresses_from_string
*/
public function test_get_ip_addresses_from_string() {
$ip_string =
// IPv4.
"1.1.1.1\n2.2.2.2,3.3.3.3;4.4.4.4 5.5.5.5-6.6.6.6\n" .
// IPv6.
"2001:db8::1\n2001:db8::2,2001:db8::3;2001:db8::4 2001:db8::5-2001:db8::6\n" .
// Invalid IP addresses.
'hello world - 1.2.3:4,9999:9999:9999.9999:9999:9999:9999';

$expected = array(
'1.1.1.1',
'2.2.2.2',
'3.3.3.3',
'4.4.4.4',
'5.5.5.5-6.6.6.6',
'2001:db8::1',
'2001:db8::2',
'2001:db8::3',
'2001:db8::4',
'2001:db8::5-2001:db8::6',
);

$this->assertEquals( $expected, Utils::get_ip_addresses_from_string( $ip_string ) );
}

/**
* Test `validate_ip_range`.
*
* @covers ::validate_ip_range
*/
public function test_validate_ip_range() {
// Valid range.
$this->assertTrue( Utils::validate_ip_range( '1.1.1.1', '2.2.2.2' ) );
$this->assertTrue( Utils::validate_ip_range( '2001:db8::1', '2001:db8::2' ) );

// Invalid ranges.
$this->assertFalse( Utils::validate_ip_range( '2.2.2.2', '1.1.1.1' ) );
$this->assertFalse( Utils::validate_ip_range( '2001:db8::2', '2001:db8::1' ) );
$this->assertFalse( Utils::validate_ip_range( '1.1.1', '2.2.2.2' ) );

// Ranges with the same low and high address are still considered valid.
$this->assertTrue( Utils::validate_ip_range( '1.1.1.1', '1.1.1.1' ) );
$this->assertTrue( Utils::validate_ip_range( '2001:db8::1', '2001:db8::1' ) );
}

}
4 changes: 4 additions & 0 deletions projects/packages/waf/changelog/add-ip-range-support
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Added support for IP ranges in allow and block lists.
3 changes: 2 additions & 1 deletion projects/packages/waf/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"require": {
"automattic/jetpack-connection": "@dev",
"automattic/jetpack-constants": "@dev",
"automattic/jetpack-ip": "@dev",
"automattic/jetpack-status": "@dev",
"wikimedia/aho-corasick": "^1.0"
},
Expand Down Expand Up @@ -56,7 +57,7 @@
"link-template": "https://github.com/Automattic/jetpack-waf/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "0.9.x-dev"
"dev-trunk": "0.10.x-dev"
}
},
"config": {
Expand Down
27 changes: 3 additions & 24 deletions projects/packages/waf/src/class-waf-rules-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace Automattic\Jetpack\Waf;

use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\IP\Utils as IP_Utils;
use Jetpack_Options;
use WP_Error;

Expand Down Expand Up @@ -259,28 +260,6 @@ public static function generate_automatic_rules() {
update_option( self::AUTOMATIC_RULES_LAST_UPDATED_OPTION_NAME, time() );
}

/**
* We allow for both, one IP per line or comma-; semicolon; or whitespace-separated lists. This also validates the IP addresses
* and only returns the ones that look valid.
*
* @param string $ips List of ips - example: "8.8.8.8\n4.4.4.4,2.2.2.2;1.1.1.1 9.9.9.9,5555.5555.5555.5555".
* @return array List of valid IP addresses. - example based on input example: array('8.8.8.8', '4.4.4.4', '2.2.2.2', '1.1.1.1', '9.9.9.9')
*/
public static function ip_option_to_array( $ips ) {
$ips = (string) $ips;
$ips = preg_split( '/[\s,;]/', $ips );

$result = array();

foreach ( $ips as $ip ) {
if ( filter_var( $ip, FILTER_VALIDATE_IP ) !== false ) {
$result[] = $ip;
}
}

return $result;
}

/**
* Generates the rules.php script
*
Expand Down Expand Up @@ -309,8 +288,8 @@ public static function generate_ip_rules() {
$wp_filesystem->mkdir( dirname( $block_ip_file_path ) );
}

$allow_list = self::ip_option_to_array( get_option( self::IP_ALLOW_LIST_OPTION_NAME ) );
$block_list = self::ip_option_to_array( get_option( self::IP_BLOCK_LIST_OPTION_NAME ) );
$allow_list = IP_Utils::get_ip_addresses_from_string( get_option( self::IP_ALLOW_LIST_OPTION_NAME ) );
$block_list = IP_Utils::get_ip_addresses_from_string( get_option( self::IP_BLOCK_LIST_OPTION_NAME ) );

$allow_rules_content = '';
// phpcs:disable WordPress.PHP.DevelopmentFunctions
Expand Down
28 changes: 24 additions & 4 deletions projects/packages/waf/src/class-waf-runtime.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

namespace Automattic\Jetpack\Waf;

use Automattic\Jetpack\IP\Utils as IP_Utils;

require_once __DIR__ . '/functions.php';

// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This class is all about sanitizing input.
Expand Down Expand Up @@ -663,14 +665,32 @@ function ( $item ) {
}

/**
* Verifies is ip from request is in an array.
* Verifies if the IP from the current request is in an array.
*
* @param array $array Array to verify ip against.
* @param array $array Array of IP addresses to verify the request IP against.
* @return bool
*/
public function is_ip_in_array( $array ) {
$real_ip = $this->request->get_real_user_ip_address();
$real_ip = $this->request->get_real_user_ip_address();
$array_length = count( $array );

for ( $i = 0; $i < $array_length; $i++ ) {
// Check if the IP matches a provided range.
$range = explode( '-', $array[ $i ] );
if ( count( $range ) === 2 ) {
if ( IP_Utils::ip_address_is_in_range( $real_ip, $range[0], $range[1] ) ) {
return true;
}
continue;
}

return in_array( $real_ip, $array, true );
// Check if the IP is an exact match.
if ( $real_ip === $array[ $i ] ) {
return true;
}
}

return false;
}

/**
Expand Down
6 changes: 4 additions & 2 deletions projects/packages/waf/src/class-waf-stats.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

namespace Automattic\Jetpack\Waf;

use Automattic\Jetpack\IP\Utils as IP_Utils;

/**
* Retrieves WAF stats.
*/
Expand All @@ -24,7 +26,7 @@ public static function get_ip_allow_list_count() {
return 0;
}

$results = Waf_Rules_Manager::ip_option_to_array( $ip_allow_list );
$results = IP_Utils::get_ip_addresses_from_string( $ip_allow_list );

return count( $results );
}
Expand All @@ -41,7 +43,7 @@ public static function get_ip_block_list_count() {
return 0;
}

$results = Waf_Rules_Manager::ip_option_to_array( $ip_block_list );
$results = IP_Utils::get_ip_addresses_from_string( $ip_block_list );

return count( $results );
}
Expand Down
5 changes: 5 additions & 0 deletions projects/plugins/jetpack/changelog/add-waf-ip-ranges
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: other
Comment: Updated composer.lock.


54 changes: 52 additions & 2 deletions projects/plugins/jetpack/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions projects/plugins/protect/changelog/add-waf-ip-ranges
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: changed
Comment: Updated composer.lock.


Loading

0 comments on commit 8fa35bc

Please sign in to comment.