WordPress.org

Make WordPress Core

Ticket #41083: 41083.6.diff

File 41083.6.diff, 5.4 KB (added by iandunn, 2 years ago)

41083.diff with string manipulation approach to IPv4 addresses

  • src/wp-admin/includes/class-wp-community-events.php

    diff --git src/wp-admin/includes/class-wp-community-events.php src/wp-admin/includes/class-wp-community-events.php
    index a204f9cafe..79c4a85adb 100644
    class WP_Community_Events { 
    233233         *                      or false on failure.
    234234         */
    235235        public static function get_unsafe_client_ip() {
    236                 $client_ip = false;
     236                $client_ip = $netmask = false;
     237                $ip_prefix = '';
    237238
    238239                // In order of preference, with the best ones for this purpose first.
    239240                $address_headers = array(
    class WP_Community_Events { 
    260261                        }
    261262                }
    262263
    263                 // These functions are not available on Windows until PHP 5.3.
    264                 if ( function_exists( 'inet_pton' ) && function_exists( 'inet_ntop' ) ) {
    265                         if ( 4 === strlen( inet_pton( $client_ip ) ) ) {
    266                                 $netmask = '255.255.255.0'; // ipv4.
    267                         } else {
    268                                 $netmask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; // ipv6.
     264                if ( ! $client_ip ) {
     265                        return false;
     266                }
     267
     268                // Detect what kind of IP address this is.
     269                $is_ipv6 = substr_count( $client_ip, ':' ) > 1;
     270                $is_ipv4 = ( 3 === substr_count( $client_ip, '.' ) );
     271
     272                if ( $is_ipv6 && $is_ipv4 ) {
     273                        // IPv6 compatibility mode, temporarily strip the IPv6 part, and treat it like IPv4.
     274                        $ip_prefix = '::ffff:';
     275                        $client_ip = preg_replace( '/^\[?[0-9a-f:]*:/i', '', $client_ip );
     276                        $client_ip = str_replace( ']', '', $client_ip );
     277                        $is_ipv6   = false;
     278                }
     279
     280                if ( $is_ipv6 ) {
     281                        // IPv6 addresses will always be enclosed in [] if there's a port.
     282                        $ip_start = 1;
     283                        $ip_end   = (int) strpos( $client_ip, ']' ) - 1;
     284                        $netmask  = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000';
     285
     286                        // Strip the port (and [] from IPv6 addresses), if they exist.
     287                        if ( $ip_end > 0 ) {
     288                                $client_ip = substr( $client_ip, $ip_start, $ip_end );
    269289                        }
    270290
    271                         $client_ip = inet_ntop( inet_pton( $client_ip ) & inet_pton( $netmask ) );
     291                        // Partially anonymize the IP by reducing it to the corresponding network ID.
     292                        if ( function_exists( 'inet_pton' ) && function_exists( 'inet_ntop' ) ) {
     293                                $client_ip = inet_ntop( inet_pton( $client_ip ) & inet_pton( $netmask ) );
     294                        }
     295                } elseif ( $is_ipv4 ) {
     296                        // Strip any port and partially anonymize the IP.
     297                        $last_octet_position = strrpos( $client_ip, '.' );
     298                        $client_ip           = substr( $client_ip, 0, $last_octet_position ) . '.0';
     299                } else {
     300                        return false;
    272301                }
    273302
    274                 return $client_ip;
     303                // Restore the IPv6 prefix to compatibility mode addresses.
     304                return $ip_prefix . $client_ip;
    275305        }
    276306
    277307        /**
  • tests/phpunit/tests/admin/includesCommunityEvents.php

    diff --git tests/phpunit/tests/admin/includesCommunityEvents.php tests/phpunit/tests/admin/includesCommunityEvents.php
    index ed5cc2caee..50b5623a28 100644
    class Test_WP_Community_Events extends WP_UnitTestCase { 
    255255                        'filename' => '',
    256256                );
    257257        }
     258
     259        /**
     260         * Test that get_unsafe_client_ip() properly anonymizes all possible address formats
     261         *
     262         * @dataProvider data_get_unsafe_client_ip_anonymization
     263         *
     264         * @ticket 41083
     265         */
     266        public function test_get_unsafe_client_ip_anonymization( $raw_ip, $expected_result ) {
     267                $_SERVER['REMOTE_ADDR'] = $raw_ip;
     268                $actual_result          = WP_Community_Events::get_unsafe_client_ip();
     269
     270                $this->assertEquals( $expected_result, $actual_result );
     271        }
     272
     273        public function data_get_unsafe_client_ip_anonymization() {
     274                return array(
     275                        // Invalid IP.
     276                        array(
     277                                '',    // Raw IP address
     278                                false, // Expected result
     279                        ),
     280                        // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings.
     281                        array(
     282                                'unknown',
     283                                false,
     284                        ),
     285                        // IPv4, no port
     286                        array(
     287                                '10.20.30.45',
     288                                '10.20.30.0',
     289                        ),
     290                        // IPv4, port
     291                        array(
     292                                '10.20.30.45:20000',
     293                                '10.20.30.0',
     294                        ),
     295                        // IPv6, no port
     296                        array(
     297                                '2a03:2880:2110:df07:face:b00c::1',
     298                                '2a03:2880:2110:df07::',
     299                        ),
     300                        // IPv6, port
     301                        array(
     302                                '[2a03:2880:2110:df07:face:b00c::1]:20000',
     303                                '2a03:2880:2110:df07::',
     304                        ),
     305                        // IPv6, no port, reducible representation
     306                        array(
     307                                '0000:0000:0000:0000:0000:0000:0000:0001',
     308                                '::',
     309                        ),
     310                        // IPv6, no port, partially reducible representation
     311                        array(
     312                                '1000:0000:0000:0000:0000:0000:0000:0001',
     313                                '1000::',
     314                        ),
     315                        // IPv6, port, reducible representation
     316                        array(
     317                                '[0000:0000:0000:0000:0000:0000:0000:0001]:1234',
     318                                '::',
     319                        ),
     320                        // IPv6, port, partially reducible representation
     321                        array(
     322                                '[1000:0000:0000:0000:0000:0000:0000:0001]:5678',
     323                                '1000::',
     324                        ),
     325                        // IPv6, no port, reduced representation
     326                        array(
     327                                '::',
     328                                '::',
     329                        ),
     330                        // IPv6, no port, reduced representation
     331                        array(
     332                                '::1',
     333                                '::',
     334                        ),
     335                        // IPv6, port, reduced representation
     336                        array(
     337                                '[::]:20000',
     338                                '::',
     339                        ),
     340                        // IPv6, address brackets without port delimiter and number, reduced representation
     341                        array(
     342                                '[::1]',
     343                                '::',
     344                        ),
     345                        // IPv6, no port, compatibility mode
     346                        array(
     347                                '::ffff:10.15.20.25',
     348                                '::ffff:10.15.20.0',
     349                        ),
     350                        // IPv6, port, compatibility mode
     351                        array(
     352                                '[::ffff:10.15.20.25]:30000',
     353                                '::ffff:10.15.20.0',
     354                        ),
     355                        // IPv6, no port, compatibility mode shorthand
     356                        array(
     357                                '::127.0.0.1',
     358                                '::ffff:127.0.0.0',
     359                        ),
     360                        // IPv6, port, compatibility mode shorthand
     361                        array(
     362                                '[::127.0.0.1]:30000',
     363                                '::ffff:127.0.0.0',
     364                        ),
     365                );
     366        }
    258367}