PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.2
4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 All 200 releases
← All changes | includes/Insights/Insights.php +32 -5 4.9.04.9.2 View file →
@@ -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;