From 7249cffc35dfbdc57c8a7b01a6ad8283fa38a58f Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Fri, 7 May 2021 15:07:49 +0100 Subject: [PATCH 1/4] Add --insecure flag to core download & core update command --- src/Core_Command.php | 91 +++++++++++++++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 23 deletions(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index cfdba9e9a..cf5c5f9c2 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1,6 +1,7 @@ get_download_url( $version, $locale, $extension ); } else { - $offer = $this->get_download_offer( $locale ); + $offer = $this->get_download_offer( $locale, $insecure ); if ( ! $offer ) { WP_CLI::error( "The requested locale ({$locale}) was not found." ); } @@ -256,6 +261,7 @@ function () use ( $temp ) { $options = [ 'timeout' => 600, // 10 minutes ought to be enough for everybody 'filename' => $temp, + 'insecure' => $insecure, ]; $response = Utils\http_request( 'GET', $download_url, null, $headers, $options ); @@ -267,7 +273,8 @@ function () use ( $temp ) { } if ( 'nightly' !== $version ) { - $md5_response = Utils\http_request( 'GET', $download_url . '.md5' ); + unset( $options['filename'] ); + $md5_response = Utils\http_request( 'GET', $download_url . '.md5', null, [], $options ); if ( $md5_response->status_code >= 200 && $md5_response->status_code < 300 ) { $md5_file = md5_file( $temp ); @@ -299,24 +306,46 @@ function () use ( $temp ) { } if ( $wordpress_present ) { - $this->cleanup_extra_files( $from_version, $version, $locale ); + $this->cleanup_extra_files( $from_version, $version, $locale, $insecure ); } WP_CLI::success( 'WordPress downloaded.' ); } - private static function read( $url ) { - $headers = [ 'Accept' => 'application/json' ]; - $response = Utils\http_request( 'GET', $url, null, $headers, [ 'timeout' => 30 ] ); - if ( 200 === $response->status_code ) { - return $response->body; - } else { + /** + * Read content from one of the WordPress.org API endpoints. + * + * @param string $url URL of the WordPress.org API endpoint to use. + * @param bool $insecure Whether to retry without certificate validation on TLS handshake failure. + * @return string String with a set of PHP define() statements to define the salts. + * @throws ExitException If the remote request failed. + */ + private static function read( $url, $insecure ) { + $headers = [ 'Accept' => 'application/json' ]; + $options = [ + 'timeout' => 30, + 'insecure' => $insecure, + ]; + + $response = Utils\http_request( 'GET', $url, null, $headers, $options ); + + if ( 200 !== $response->status_code ) { WP_CLI::error( "Couldn't fetch response from {$url} (HTTP code {$response->status_code})." ); } + + return $response->body; } - private function get_download_offer( $locale ) { - $out = self::read( 'https://api.wordpress.org/core/version-check/1.7/?locale=' . $locale ); + /** + * Get a download offer from the WordPress.org API. + * + * @param string $locale Locale to request an offer from. + * @param bool $insecure Whether to retry without certificate validation on TLS handshake failure. + * @return stdClass|false Offer object, or false if none was returned. + * @throws ExitException If the remote request failed. + */ + private function get_download_offer( $locale, $insecure ) { + $out = self::read( 'https://api.wordpress.org/core/version-check/1.7/?locale=' . $locale, $insecure ); $out = function_exists( 'wp_json_decode' ) ? wp_json_decode( $out ) : json_decode( $out ); @@ -952,16 +981,20 @@ private static function find_var( $var_name, $code ) { /** * Security copy of the core function with Requests - Gets the checksums for the given version of WordPress. * - * @param string $version Version string to query. - * @param string $locale Locale to query. + * @param string $version Version string to query. + * @param string $locale Locale to query. + * @param bool $insecure Whether to retry without certificate validation on TLS handshake failure. * @return string|array String message on failure. An array of checksums on success. */ - private static function get_core_checksums( $version, $locale ) { + private static function get_core_checksums( $version, $locale, $insecure ) { $query = http_build_query( compact( 'version', 'locale' ), null, '&' ); $url = "https://api.wordpress.org/core/checksums/1.0/?{$query}"; - $options = [ 'timeout' => 30 ]; $headers = [ 'Accept' => 'application/json' ]; + $options = [ + 'timeout' => 30, + 'insecure' => $insecure, + ]; $response = Utils\http_request( 'GET', $url, null, $headers, $options ); @@ -1007,6 +1040,9 @@ private static function get_core_checksums( $version, $locale ) { * [--locale=] * : Select which language you want to download. * + * [--insecure] + * : Retry download without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack. + * * ## EXAMPLES * * # Update WordPress @@ -1094,7 +1130,7 @@ public function update( $args, $assoc_args ) { list( $update ) = $from_api->updates; } } - } elseif ( \WP_CLI\Utils\wp_version_compare( $assoc_args['version'], '<' ) + } elseif ( Utils\wp_version_compare( $assoc_args['version'], '<' ) || 'nightly' === $assoc_args['version'] || Utils\get_flag_value( $assoc_args, 'force' ) ) { @@ -1153,8 +1189,9 @@ public function update( $args, $assoc_args ) { $to_version = $wp_details['wp_version']; } - $locale = Utils\get_flag_value( $assoc_args, 'locale', get_locale() ); - $this->cleanup_extra_files( $from_version, $to_version, $locale ); + $locale = (string) Utils\get_flag_value( $assoc_args, 'locale', get_locale() ); + $insecure = (bool) Utils\get_flag_value( $assoc_args, 'insecure', false ); + $this->cleanup_extra_files( $from_version, $to_version, $locale, $insecure ); WP_CLI::success( 'WordPress updated successfully.' ); } @@ -1347,18 +1384,26 @@ private function get_updates( $assoc_args ) { return array_values( $updates ); } - private function cleanup_extra_files( $version_from, $version_to, $locale ) { + /** + * Clean up extra files. + * + * @param string $version_from Starting version that the installation was updated from. + * @param string $version_to Target version that the installation is updated to. + * @param string $locale Locale of the installation. + * @param bool $insecure Whether to retry without certificate validation on TLS handshake failure. + */ + private function cleanup_extra_files( $version_from, $version_to, $locale, $insecure ) { if ( ! $version_from || ! $version_to ) { WP_CLI::warning( 'Failed to find WordPress version. Please cleanup files manually.' ); return; } - $old_checksums = self::get_core_checksums( $version_from, $locale ?: 'en_US' ); + $old_checksums = self::get_core_checksums( $version_from, $locale ?: 'en_US', $insecure ); if ( ! is_array( $old_checksums ) ) { WP_CLI::warning( "{$old_checksums} Please cleanup files manually." ); return; } - $new_checksums = self::get_core_checksums( $version_to, $locale ?: 'en_US' ); + $new_checksums = self::get_core_checksums( $version_to, $locale ?: 'en_US', $insecure ); if ( ! is_array( $new_checksums ) ) { WP_CLI::warning( "{$new_checksums} Please cleanup files manually." ); return; From 4adf95515a2aeb2b1f421f9950471417aa407685 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Fri, 7 May 2021 15:43:58 +0100 Subject: [PATCH 2/4] Add $insecure argument to CoreUpgrader class --- src/Core_Command.php | 7 +++---- src/WP_CLI/Core/CoreUpgrader.php | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index cf5c5f9c2..ccd18b759 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1079,7 +1079,6 @@ public function update( $args, $assoc_args ) { global $wp_version; $update = null; - $from_api = null; $upgrader = 'WP_CLI\\Core\\CoreUpgrader'; if ( 'trunk' === Utils\get_flag_value( $assoc_args, 'version' ) ) { @@ -1169,9 +1168,10 @@ public function update( $args, $assoc_args ) { } $from_version = $wp_version; + $insecure = (bool) Utils\get_flag_value( $assoc_args, 'insecure', false ); $GLOBALS['wpcli_core_update_obj'] = $update; - $result = Utils\get_upgrader( $upgrader )->upgrade( $update ); + $result = Utils\get_upgrader( $upgrader, $insecure )->upgrade( $update ); unset( $GLOBALS['wpcli_core_update_obj'] ); if ( is_wp_error( $result ) ) { @@ -1189,8 +1189,7 @@ public function update( $args, $assoc_args ) { $to_version = $wp_details['wp_version']; } - $locale = (string) Utils\get_flag_value( $assoc_args, 'locale', get_locale() ); - $insecure = (bool) Utils\get_flag_value( $assoc_args, 'insecure', false ); + $locale = (string) Utils\get_flag_value( $assoc_args, 'locale', get_locale() ); $this->cleanup_extra_files( $from_version, $to_version, $locale, $insecure ); WP_CLI::success( 'WordPress updated successfully.' ); diff --git a/src/WP_CLI/Core/CoreUpgrader.php b/src/WP_CLI/Core/CoreUpgrader.php index e70e2a832..18ca4a060 100644 --- a/src/WP_CLI/Core/CoreUpgrader.php +++ b/src/WP_CLI/Core/CoreUpgrader.php @@ -17,6 +17,23 @@ */ class CoreUpgrader extends DefaultCoreUpgrader { + /** + * Whether to retry without certificate validation on TLS handshake failure. + * + * @var bool + */ + private $insecure; + + /** + * CoreUpgrader constructor. + * + * @param WP_Upgrader_Skin|null $skin + */ + public function __construct( $skin = null, $insecure = false ) { + $this->insecure = $insecure; + parent::__construct( $skin ); + } + /** * Caches the download, and uses cached if available. * @@ -93,6 +110,7 @@ function () use ( $temp ) { 'timeout' => 600, // 10 minutes ought to be enough for everybody. 'filename' => $temp, 'halt_on_error' => false, + 'insecure' => $this->insecure, ]; $this->skin->feedback( 'downloading_package', $package ); From 9d600a7e3c92ddafd093c784b6edd41656360b60 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 10 May 2021 15:00:44 +0100 Subject: [PATCH 3/4] Use WpOrgApi abstraction --- src/Core_Command.php | 55 ++++++-------------------------------------- 1 file changed, 7 insertions(+), 48 deletions(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index ccd18b759..378581099 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -6,6 +6,7 @@ use WP_CLI\Iterators\Table as TableIterator; use WP_CLI\Utils; use WP_CLI\Formatter; +use WP_CLI\WpOrgApi; /** * Downloads, installs, updates, and manages a WordPress installation. @@ -185,7 +186,12 @@ public function download( $args, $assoc_args ) { $download_url = $this->get_download_url( $version, $locale, $extension ); } else { - $offer = $this->get_download_offer( $locale, $insecure ); + try { + $offer = ( new WpOrgApi( [ 'insecure' => $insecure ] ) ) + ->get_core_download_offer( $locale ); + } catch ( Exception $exception ) { + WP_CLI::error( $exception ); + } if ( ! $offer ) { WP_CLI::error( "The requested locale ({$locale}) was not found." ); } @@ -312,53 +318,6 @@ function () use ( $temp ) { WP_CLI::success( 'WordPress downloaded.' ); } - /** - * Read content from one of the WordPress.org API endpoints. - * - * @param string $url URL of the WordPress.org API endpoint to use. - * @param bool $insecure Whether to retry without certificate validation on TLS handshake failure. - * @return string String with a set of PHP define() statements to define the salts. - * @throws ExitException If the remote request failed. - */ - private static function read( $url, $insecure ) { - $headers = [ 'Accept' => 'application/json' ]; - $options = [ - 'timeout' => 30, - 'insecure' => $insecure, - ]; - - $response = Utils\http_request( 'GET', $url, null, $headers, $options ); - - if ( 200 !== $response->status_code ) { - WP_CLI::error( "Couldn't fetch response from {$url} (HTTP code {$response->status_code})." ); - } - - return $response->body; - } - - /** - * Get a download offer from the WordPress.org API. - * - * @param string $locale Locale to request an offer from. - * @param bool $insecure Whether to retry without certificate validation on TLS handshake failure. - * @return stdClass|false Offer object, or false if none was returned. - * @throws ExitException If the remote request failed. - */ - private function get_download_offer( $locale, $insecure ) { - $out = self::read( 'https://api.wordpress.org/core/version-check/1.7/?locale=' . $locale, $insecure ); - $out = function_exists( 'wp_json_decode' ) - ? wp_json_decode( $out ) - : json_decode( $out ); - - $offer = $out->offers[0]; - - if ( $offer->locale !== $locale ) { - return false; - } - - return $offer; - } - /** * Checks if WordPress is installed. * From c1b11873f4d5757c6668775096778254731634ff Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 10 May 2021 15:02:17 +0100 Subject: [PATCH 4/4] Use array access into offer --- src/Core_Command.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index 378581099..b7c6b05b1 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1,7 +1,6 @@ current; - $download_url = $offer->download; + $version = $offer['current']; + $download_url = $offer['download']; if ( ! $skip_content ) { $download_url = str_replace( '.zip', '.tar.gz', $download_url ); }