diff --git a/src/wp-admin/includes/class-wp-community-events.php b/src/wp-admin/includes/class-wp-community-events.php index c191b0a26c..fe16dc46e4 100644 --- a/src/wp-admin/includes/class-wp-community-events.php +++ b/src/wp-admin/includes/class-wp-community-events.php @@ -233,7 +233,7 @@ class WP_Community_Events { * or false on failure. */ public static function get_unsafe_client_ip() { - $client_ip = $netmask = false; + $client_ip = false; $ip_prefix = ''; // In order of preference, with the best ones for this purpose first. @@ -279,13 +279,27 @@ class WP_Community_Events { if ( $is_ipv6 ) { // IPv6 addresses will always be enclosed in [] if there's a port. - $ip_start = 1; - $ip_end = (int) strpos( $client_ip, ']' ) - 1; - $netmask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; + $left_bracket = strpos( $client_ip, '[' ); + $right_bracket = strpos( $client_ip, ']' ); + $percent = strpos( $client_ip, '%' ); + $netmask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; // Strip the port (and [] from IPv6 addresses), if they exist. - if ( $ip_end > 0 ) { - $client_ip = substr( $client_ip, $ip_start, $ip_end ); + if ( false !== $left_bracket && false !== $right_bracket ) { + $client_ip = substr( $client_ip, $left_bracket + 1, $right_bracket - $left_bracket - 1 ); + } elseif ( false !== $left_bracket || false !== $right_bracket ) { + // The IP has one bracket, but not both, so it's malformed. + return false; + } + + // Strip the reachability scope. + if ( false !== $percent ) { + $client_ip = substr( $client_ip, 0, $percent ); + } + + // No invalid characters should be left. + if ( preg_match( '/[^0-9a-f:]/i', $client_ip ) ) { + return false; } // Partially anonymize the IP by reducing it to the corresponding network ID. diff --git a/tests/phpunit/tests/admin/includesCommunityEvents.php b/tests/phpunit/tests/admin/includesCommunityEvents.php index 92b36e44cd..bd6a6cdbe6 100644 --- a/tests/phpunit/tests/admin/includesCommunityEvents.php +++ b/tests/phpunit/tests/admin/includesCommunityEvents.php @@ -502,6 +502,41 @@ class Test_WP_Community_Events extends WP_UnitTestCase { 'unknown', false, ), + // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings. + array( + 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001', + false, + ), + // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings. + array( + 'or=\"1000:0000:0000:0000:0000:0000:0000:0001', + false, + ), + // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings. + array( + '1000:0000:0000:0000:0000:0000:0000:0001or=\"', + false, + ), + // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings. + array( + 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001]:400', + '1000::', + ), + // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings. + array( + 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001]', + '1000::', + ), + // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings. + array( + 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001]400', + '1000::', + ), + // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings. + array( + '[1000:0000:0000:0000:0000:0000:0000:0001]:235\"or=', + '1000::', + ), // IPv4, no port array( '10.20.30.45', @@ -569,7 +604,7 @@ class Test_WP_Community_Events extends WP_UnitTestCase { ), // IPv6, port, compatibility mode array( - '[::ffff:10.15.20.25]:30000', + '[::FFFF:10.15.20.25]:30000', '::ffff:10.15.20.0', ), // IPv6, no port, compatibility mode shorthand @@ -582,6 +617,16 @@ class Test_WP_Community_Events extends WP_UnitTestCase { '[::127.0.0.1]:30000', '::ffff:127.0.0.0', ), + // IPv6 with reachability scope + array( + 'fe80::b059:65f4:e877:c40%16', + 'fe80::', + ), + // IPv6 with reachability scope + array( + 'FE80::B059:65F4:E877:C40%eth0', + 'fe80::', + ), ); } }