Skip to content

Commit

Permalink
HTTP API: Introduce 'http_allowed_safe_ports' filter in `wp_http_va…
Browse files Browse the repository at this point in the history
…lidate_url()`.

Adds a new filter `'http_allowed_safe_ports'` to control which ports are allowed for remote requests. By default, ports 80, 443, and 8080 are allowed for safe remote requests.

Adds tests. 

Follow-up to [24480].

Props xknown, johnbillion, jorbin, costdev, dd32.
Fixes #54331.

git-svn-id: https://develop.svn.wordpress.org/trunk@52084 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
hellofromtonya committed Nov 9, 2021
1 parent 16b7125 commit bed3a7c
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 8 deletions.
28 changes: 20 additions & 8 deletions src/wp-includes/http.php
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,10 @@ function send_origin_headers() {
* @return string|false URL or false on failure.
*/
function wp_http_validate_url( $url ) {
if ( ! is_string( $url ) || '' === $url || is_numeric( $url ) ) {
return false;
}

$original_url = $url;
$url = wp_kses_bad_protocol( $url, array( 'http', 'https' ) );
if ( ! $url || strtolower( $url ) !== strtolower( $original_url ) ) {
Expand All @@ -534,15 +538,10 @@ function wp_http_validate_url( $url ) {
}

$parsed_home = parse_url( get_option( 'home' ) );

if ( isset( $parsed_home['host'] ) ) {
$same_host = strtolower( $parsed_home['host'] ) === strtolower( $parsed_url['host'] );
} else {
$same_host = false;
}
$same_host = isset( $parsed_home['host'] ) && strtolower( $parsed_home['host'] ) === strtolower( $parsed_url['host'] );
$host = trim( $parsed_url['host'], '.' );

if ( ! $same_host ) {
$host = trim( $parsed_url['host'], '.' );
if ( preg_match( '#^(([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)\.){3}([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)$#', $host ) ) {
$ip = $host;
} else {
Expand Down Expand Up @@ -581,7 +580,20 @@ function wp_http_validate_url( $url ) {
}

$port = $parsed_url['port'];
if ( 80 === $port || 443 === $port || 8080 === $port ) {

/**
* Controls the list of ports considered safe in HTTP API.
*
* Allows to change and allow external requests for the HTTP request.
*
* @since 5.9.0
*
* @param array $allowed_ports Array of integers for valid ports.
* @param string $host Host name of the requested URL.
* @param string $url Requested URL.
*/
$allowed_ports = apply_filters( 'http_allowed_safe_ports', array( 80, 443, 8080 ), $host, $url );
if ( in_array( $port, $allowed_ports, true ) ) {
return $url;
}

Expand Down
175 changes: 175 additions & 0 deletions tests/phpunit/tests/http/http.php
Original file line number Diff line number Diff line change
Expand Up @@ -392,4 +392,179 @@ public function wp_translate_php_url_constant_to_key_testcases() {
);
}

/**
* Test that wp_http_validate_url validates URLs.
*
* @ticket 54331
*
* @dataProvider data_wp_http_validate_url_should_validate
*
* @covers ::wp_http_validate_url
*
* @param string $url The URL to validate.
* @param false|string $cb_safe_ports The name of the callback to http_allowed_safe_ports or false if none.
* Default false.
* @param bool $external_host Whether or not the host is external.
* Default false.
*/
public function test_wp_http_validate_url_should_validate( $url, $cb_safe_ports = false, $external_host = false ) {
if ( $external_host ) {
add_filter( 'http_request_host_is_external', '__return_true' );
}

if ( $cb_safe_ports ) {
add_filter( 'http_allowed_safe_ports', array( $this, $cb_safe_ports ) );
}

$this->assertSame( $url, wp_http_validate_url( $url ) );
}

/**
* Data provider.
*
* @return array
*/
public function data_wp_http_validate_url_should_validate() {
return array(
'no port specified' => array(
'url' => 'http://example.com/caniload.php',
),
'an external request when allowed' => array(
'url' => 'http://172.20.0.123/caniload.php',
'cb_safe_ports' => false,
'external_host' => true,
),
'a port considered safe by default' => array(
'url' => 'https://example.com:8080/caniload.php',
),
'a port considered safe by filter' => array(
'url' => 'https://example.com:81/caniload.php',
'cb_safe_ports' => 'callback_custom_safe_ports',
),
);
}

/**
* Tests that wp_http_validate_url validates a url that uses an unsafe port
* but which matches the host and port used by the site's home url.
*
* @ticket 54331
*
* @covers ::wp_http_validate_url
*/
public function test_wp_http_validate_url_should_validate_with_an_unsafe_port_when_the_host_and_port_match_the_home_url() {
$original_home = get_option( 'home' );
$home_parsed = parse_url( $original_home );
$home_scheme_host = implode( '://', array_slice( $home_parsed, 0, 2 ) );
$home_modified = $home_scheme_host . ':83';

update_option( 'home', $home_modified );

$url = $home_modified . '/caniload.php';
$this->assertSame( $url, wp_http_validate_url( $url ) );

update_option( 'home', $original_home );
}

/**
* Test that wp_http_validate_url does not validate invalid URLs.
*
* @ticket 54331
*
* @dataProvider data_wp_http_validate_url_should_not_validate
*
* @covers ::wp_http_validate_url
*
* @param string $url The URL to validate.
* @param false|string $cb_safe_ports The name of the callback to http_allowed_safe_ports or false if none.
* Default false.
* @param bool $external_host Whether or not the host is external.
* Default false.
*/
public function test_wp_http_validate_url_should_not_validate( $url, $cb_safe_ports = false, $external_host = false ) {
if ( $external_host ) {
add_filter( 'http_request_host_is_external', '__return_true' );
}

if ( $cb_safe_ports ) {
add_filter( 'http_allowed_safe_ports', array( $this, $cb_safe_ports ) );
}

$this->assertFalse( wp_http_validate_url( $url ) );
}

/**
* Data provider.
*
* @return array
*/
public function data_wp_http_validate_url_should_not_validate() {
return array(
'url as false' => array(
'url' => false,
),
'url as null' => array(
'url' => null,
),
'url as int 0' => array(
'url' => 0,
),
'url as string 0' => array(
'url' => '0',
),
'url as int 1' => array(
'url' => 1,
),
'url as string 1' => array(
'url' => '1',
),
'url as array()' => array(
'url' => array(),
),
'an empty url' => array(
'url' => '',
),
'a url with a non-http/https protocol' => array(
'url' => 'ftp://example.com:81/caniload.php',
),
'a malformed url' => array(
'url' => 'http:///example.com:81/caniload.php',
),
'a host that cannot be parsed' => array(
'url' => 'http:example.com/caniload.php',
),
'login information' => array(
'url' => 'http://user:[email protected]/caniload.php',
),
'a host with invalid characters' => array(
'url' => 'http://[exam]ple.com/caniload.php',
),
'a host whose IPv4 address cannot be resolved' => array(
'url' => 'http://exampleeeee.com/caniload.php',
),
'an external request when not allowed' => array(
'url' => 'http://192.168.0.1/caniload.php',
'external_host' => false,
),
'a port not considered safe by default' => array(
'url' => 'https://example.com:81/caniload.php',
),
'a port not considered safe by filter' => array(
'url' => 'https://example.com:82/caniload.php',
'cb_safe_ports' => 'callback_custom_safe_ports',
),
'all safe ports removed by filter' => array(
'url' => 'https://example.com:81/caniload.php',
'cb_safe_ports' => 'callback_remove_safe_ports',
),
);
}

public function callback_custom_safe_ports( $ports ) {
return array( 81, 444, 8081 );
}

public function callback_remove_safe_ports( $ports ) {
return array();
}
}

0 comments on commit bed3a7c

Please sign in to comment.