| @@ -509,13 +509,40 @@ | ||
| 509 | 509 | /** |
| 510 | 510 | * Send Initial Data to API |
| 511 | 511 | */ |
| 512 | 512 | if ( false == $site_id && false !== $this->item_id && ( false === $original_site_url || $original_site_url != $site_url ) ) { |
| 513 | - if ( isset( $_SERVER[ 'REMOTE_ADDR' ] ) && ! empty( $_SERVER[ 'REMOTE_ADDR' ] && '127.0.0.1' != $_SERVER[ 'REMOTE_ADDR' ] ) ) { //phpcs:ignore | |
| 514 | - $country_request = wp_remote_get( 'http://ip-api.com/json/' . $_SERVER[ 'REMOTE_ADDR' ] . '?fields=country' ); //phpcs:ignore | |
| 515 | - if ( ! is_wp_error( $country_request ) && 200 == $country_request[ 'response' ][ 'code' ] ) { | |
| 516 | - $ip_data = json_decode( $country_request[ 'body' ] ); | |
| 517 | - $body[ 'country' ] = isset( $ip_data->country ) ? $ip_data->country : 'NOT SET'; | |
| 513 | + // Validate before interpolating: REMOTE_ADDR went into the URL raw, so | |
| 514 | + // anything the host put there (some proxy setups write a comma-joined | |
| 515 | + // list, or a value taken from a client header) became URL path/query. | |
| 516 | + // FILTER_VALIDATE_IP also drops private, loopback and reserved | |
| 517 | + // addresses — a LAN address tells the geolocator nothing and sending | |
| 518 | + // it is a needless disclosure. | |
| 519 | + $remote_addr = isset( $_SERVER[ 'REMOTE_ADDR' ] ) ? wp_unslash( $_SERVER[ 'REMOTE_ADDR' ] ) : ''; //phpcs:ignore | |
| 520 | + $public_ip = filter_var( | |
| 521 | + $remote_addr, | |
| 522 | + FILTER_VALIDATE_IP, | |
| 523 | + FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE | |
| 524 | + ); | |
| 525 | + | |
| 526 | + if ( false !== $public_ip ) { | |
| 527 | + // NOTE: http, not https, is deliberate — ip-api.com serves TLS only | |
| 528 | + // on its paid plans and answers 403 over https on the free tier, so | |
| 529 | + // switching the scheme would silently disable country detection | |
| 530 | + // rather than secure it. The response is therefore untrusted input: | |
| 531 | + // it is read for a single string field, length-bounded, and | |
| 532 | + // sanitised below. The residual exposure is that the site's own | |
| 533 | + // outbound IP travels in cleartext to a third party. | |
| 534 | + $country_request = wp_remote_get( | |
| 535 | + 'http://ip-api.com/json/' . rawurlencode( $public_ip ) . '?fields=country', | |
| 536 | + [ 'timeout' => 5 ] | |
| 537 | + ); //phpcs:ignore | |
| 538 | + | |
| 539 | + if ( ! is_wp_error( $country_request ) && 200 == wp_remote_retrieve_response_code( $country_request ) ) { | |
| 540 | + $ip_data = json_decode( wp_remote_retrieve_body( $country_request ) ); | |
| 541 | + $country = ( is_object( $ip_data ) && isset( $ip_data->country ) && is_string( $ip_data->country ) ) | |
| 542 | + ? sanitize_text_field( $ip_data->country ) | |
| 543 | + : ''; | |
| 544 | + $body[ 'country' ] = ( '' !== $country ) ? substr( $country, 0, 64 ) : 'NOT SET'; | |
| 518 | 545 | } |
| 519 | 546 | } |
| 520 | 547 | |
| 521 | 548 | $body[ 'plugin_slug' ] = $this->plugin_name; |