Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/wp-includes/class-wp-http.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ class WP_Http {
* Send an HTTP request to a URI.
*
* Please note: The only URI that are supported in the HTTP Transport implementation
* are the HTTP and HTTPS protocols.
* are the HTTP and HTTPS protocols. The `webcal` and `webcals` schemes are normalized
* to HTTPS via wp_http_normalize_url() before the request is sent.
*
* @since 2.7.0
*
Expand Down Expand Up @@ -280,12 +281,30 @@ public function request( $url, $args = array() ) {
return $pre;
}

$url = wp_http_normalize_url( $url );

if ( function_exists( 'wp_kses_bad_protocol' ) ) {
if ( $parsed_args['reject_unsafe_urls'] ) {
$url = wp_http_validate_url( $url );
}
if ( $url ) {
$url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) );
/**
* Controls the list of URL protocols allowed in HTTP API requests.
*
* Warning: Only `http` and `https` are supported by HTTP transports. Allowing
* other protocols increases SSRF risk unless handled via the {@see 'pre_http_request'}
* filter or custom transport logic.
*
* @since 6.9.0
*
* @param string[] $protocols Array of allowed URL protocols.
* @param string $url Requested URL.
*/
$allowed_protocols = apply_filters( 'http_allowed_protocols', array( 'http', 'https', 'ssl' ), $url );
if ( ! is_array( $allowed_protocols ) ) {
$allowed_protocols = array( 'http', 'https', 'ssl' );
}
$url = wp_kses_bad_protocol( $url, $allowed_protocols );
}
}

Expand Down
82 changes: 71 additions & 11 deletions src/wp-includes/http.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@ function wp_safe_remote_request( $url, $args = array() ) {
* URL. The URL, and every URL it redirects to, are validated with wp_http_validate_url()
* to avoid Server Side Request Forgery attacks (SSRF).
*
* The only supported protocols are `http` and `https`.
* The only supported protocols are `http` and `https`. Calendar `webcal` and `webcals`
* URLs are normalized to `https` before the request is sent.
*
* @since 3.6.0
*
* @see wp_remote_request() For more information on the response array format.
* @see WP_Http::request() For default arguments information.
* @see wp_http_validate_url() For more information about how the URL is validated.
* @see wp_http_normalize_url() For calendar URL normalization.
*
* @link https://owasp.org/www-community/attacks/Server_Side_Request_Forgery
*
Expand Down Expand Up @@ -173,10 +175,13 @@ function wp_remote_request( $url, $args = array() ) {
*
* Important: If the URL is user-controlled, use `wp_safe_remote_get()` instead.
*
* Calendar `webcal` and `webcals` URLs are normalized to `https` before the request is sent.
*
* @since 2.7.0
*
* @see wp_remote_request() For more information on the response array format.
* @see WP_Http::request() For default arguments information.
* @see wp_http_normalize_url() For calendar URL normalization.
*
* @param string $url URL to retrieve.
* @param array $args Optional. Request arguments. Default empty array.
Expand Down Expand Up @@ -532,10 +537,46 @@ function send_origin_headers() {
return false;
}

/**
* Normalizes a URL for use with the HTTP API transport layer.
*
* Calendar subscription schemes `webcal` and `webcals` are rewritten to `https`
* because HTTP transports only support `http` and `https`.
*
* @since 6.9.0
*
* @param string $url Request URL.
* @return string Normalized URL.
*/
function wp_http_normalize_url( $url ) {
if ( ! is_string( $url ) || '' === $url ) {
return $url;
}

$original_url = $url;

if ( preg_match( '#^webcal:#i', $url ) ) {
$url = preg_replace( '#^webcal:#i', 'https:', $url );
} elseif ( preg_match( '#^webcals:#i', $url ) ) {
$url = preg_replace( '#^webcals:#i', 'https:', $url );
}

/**
* Filters the normalized HTTP request URL.
*
* @since 6.9.0
*
* @param string $url Normalized URL.
* @param string $original_url Original URL before normalization.
*/
return apply_filters( 'http_normalize_url', $url, $original_url );
}

/**
* Validates a URL as safe for use in the HTTP API.
*
* The only supported protocols are `http` and `https`.
* The only supported protocols are `http` and `https`. The `webcal` and `webcals`
* schemes are accepted and validated after normalization to `https`.
*
* Examples of URLs that are considered unsafe:
*
Expand All @@ -561,13 +602,32 @@ function wp_http_validate_url( $url ) {
return false;
}

$original_url = $url;
$url = wp_kses_bad_protocol( $url, array( 'http', 'https' ) );
if ( ! $url || strtolower( $url ) !== strtolower( $original_url ) ) {
$original_url = $url;
$validation_url = wp_http_normalize_url( $url );

/**
* Controls the list of URL protocols allowed in HTTP API validation.
*
* Warning: Only `http` and `https` are supported by HTTP transports. Allowing
* other protocols increases SSRF risk unless handled via the {@see 'pre_http_request'}
* filter or custom transport logic.
*
* @since 6.9.0
*
* @param string[] $protocols Array of allowed URL protocols.
* @param string $url Requested URL.
*/
$allowed_protocols = apply_filters( 'http_allowed_protocols', array( 'http', 'https' ), $original_url );
if ( ! is_array( $allowed_protocols ) ) {
$allowed_protocols = array( 'http', 'https' );
}

$url = wp_kses_bad_protocol( $validation_url, $allowed_protocols );
if ( ! $url || strtolower( $url ) !== strtolower( $validation_url ) ) {
return false;
}

$parsed_url = parse_url( $url );
$parsed_url = parse_url( $validation_url );
if ( ! $parsed_url || empty( $parsed_url['host'] ) ) {
return false;
}
Expand Down Expand Up @@ -611,15 +671,15 @@ function wp_http_validate_url( $url ) {
* @param string $host Host name of the requested URL.
* @param string $url Requested URL.
*/
if ( ! apply_filters( 'http_request_host_is_external', false, $host, $url ) ) {
if ( ! apply_filters( 'http_request_host_is_external', false, $host, $validation_url ) ) {
return false;
}
}
}
}

if ( empty( $parsed_url['port'] ) ) {
return $url;
return $original_url;
}

$port = $parsed_url['port'];
Expand All @@ -636,13 +696,13 @@ function wp_http_validate_url( $url ) {
* @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 );
$allowed_ports = apply_filters( 'http_allowed_safe_ports', array( 80, 443, 8080 ), $host, $validation_url );
if ( is_array( $allowed_ports ) && in_array( $port, $allowed_ports, true ) ) {
return $url;
return $original_url;
}

if ( $parsed_home && $same_host && isset( $parsed_home['port'] ) && $parsed_home['port'] === $port ) {
return $url;
return $original_url;
}

return false;
Expand Down
100 changes: 100 additions & 0 deletions tests/phpunit/tests/http/http.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,9 @@ public function data_wp_http_validate_url_should_validate() {
'url' => 'https://example.com:81/caniload.php',
'cb_safe_ports' => 'callback_custom_safe_ports',
),
'a webcal url' => array(
'url' => 'webcal://example.com/caniload.php',
),
);
}

Expand Down Expand Up @@ -577,6 +580,103 @@ public function callback_remove_safe_ports( $ports ) {
return array();
}

/**
* @ticket 49385
*
* @covers ::wp_http_normalize_url
*/
public function test_wp_http_normalize_url_rewrites_webcal_to_https() {
$this->assertSame(
'https://example.com/feed.ics',
wp_http_normalize_url( 'webcal://example.com/feed.ics' )
);
$this->assertSame(
'https://example.com/feed.ics',
wp_http_normalize_url( 'WEBCAL://example.com/feed.ics' )
);
$this->assertSame(
'https://example.com/feed.ics',
wp_http_normalize_url( 'webcals://example.com/feed.ics' )
);
}

/**
* @ticket 49385
*
* @covers ::wp_http_validate_url
*/
public function test_wp_http_validate_url_accepts_webcal_url() {
$url = 'webcal://example.com/caniload.php';
$this->assertSame( $url, wp_http_validate_url( $url ) );
}

/**
* @ticket 49385
*
* @covers ::wp_http_validate_url
*/
public function test_wp_http_validate_url_still_rejects_ftp() {
$this->assertFalse( wp_http_validate_url( 'ftp://example.com/caniload.php' ) );
}

/**
* @ticket 49385
*
* @covers ::wp_http_validate_url
*/
public function test_http_allowed_protocols_filter_runs_in_validate_url() {
$filter_ran = false;

add_filter(
'http_allowed_protocols',
function ( $protocols, $url ) use ( &$filter_ran ) {
$filter_ran = true;
$this->assertSame( 'http://example.com/caniload.php', $url );
return $protocols;
},
10,
2
);

wp_http_validate_url( 'http://example.com/caniload.php' );

$this->assertTrue( $filter_ran );
}

/**
* @ticket 49385
*
* @covers ::wp_remote_get
* @covers ::wp_http_normalize_url
*/
public function test_wp_remote_get_normalizes_webcal_url_before_request() {
$request_url = null;

add_filter(
'pre_http_request',
function ( $response, $parsed_args, $url ) use ( &$request_url ) {
$request_url = $url;
return array(
'headers' => array(),
'body' => '',
'response' => array(
'code' => 200,
'message' => 'OK',
),
'cookies' => array(),
'filename' => null,
);
},
10,
3
);

$result = wp_remote_get( 'webcal://example.com/feed.ics' );

$this->assertNotWPError( $result );
$this->assertSame( 'https://example.com/feed.ics', $request_url );
}

/**
* Test HTTP Redirects with multiple Location headers specified.
*
Expand Down
Loading