| @@ -53,31 +53,68 @@ | ||
| 53 | 53 | if (empty($slug)) { |
| 54 | 54 | return new WP_Error('empty_arg', __('Argument should not be empty.', 'betterdocs')); |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | - $response = wp_remote_post( | |
| 58 | - 'http://api.wordpress.org/plugins/info/1.0/', | |
| 59 | - [ | |
| 60 | - 'body' => [ | |
| 61 | - 'action' => 'plugin_information', | |
| 62 | - 'request' => serialize((object) [ | |
| 63 | - 'slug' => $slug, | |
| 64 | - 'fields' => [ | |
| 65 | - 'version' => false, | |
| 66 | - ], | |
| 67 | - ]), | |
| 68 | - ], | |
| 69 | - ] | |
| 70 | - ); | |
| 57 | + // Use core's plugins_api() instead of a hand-rolled request. The old code | |
| 58 | + // POSTed to plaintext http://api.wordpress.org and passed the response | |
| 59 | + // body straight to unserialize(), so anyone able to intercept that | |
| 60 | + // connection could inject a PHP-object-injection payload or a malicious | |
| 61 | + // download_link. plugins_api() talks to api.wordpress.org over HTTPS and | |
| 62 | + // returns a decoded object — no plaintext transport, no unserialize(). | |
| 63 | + if (!function_exists('plugins_api')) { | |
| 64 | + include_once ABSPATH . 'wp-admin/includes/plugin-install.php'; | |
| 65 | + } | |
| 71 | 66 | |
| 72 | - if (is_wp_error($response)) { | |
| 73 | - return $response; | |
| 67 | + $response = plugins_api('plugin_information', [ | |
| 68 | + 'slug' => $slug, | |
| 69 | + 'fields' => [ | |
| 70 | + 'version' => true, | |
| 71 | + ], | |
| 72 | + ]); | |
| 73 | + | |
| 74 | + if (is_wp_error($response) || !is_object($response)) { | |
| 75 | + return is_wp_error($response) ? $response : new WP_Error('plugins_api_failed', __('Could not retrieve plugin information.', 'betterdocs')); | |
| 74 | 76 | } |
| 75 | 77 | |
| 76 | - return unserialize(wp_remote_retrieve_body($response)); | |
| 78 | + // Bind the package to the requested slug and to an https WordPress.org | |
| 79 | + // host before anything installs it. | |
| 80 | + if (isset($response->slug) && $response->slug !== $slug) { | |
| 81 | + return new WP_Error('slug_mismatch', __('Plugin information did not match the requested plugin.', 'betterdocs')); | |
| 82 | + } | |
| 83 | + | |
| 84 | + if (isset($response->download_link) && !$this->is_allowed_package_url($response->download_link)) { | |
| 85 | + return new WP_Error('bad_package_host', __('Plugin download URL is not an approved WordPress.org address.', 'betterdocs')); | |
| 86 | + } | |
| 87 | + | |
| 88 | + return $response; | |
| 77 | 89 | } |
| 78 | 90 | |
| 79 | 91 | /** |
| 92 | + * Whether a package URL is safe to hand to the upgrader: https on a | |
| 93 | + * WordPress.org host. Prevents a tampered response from redirecting the | |
| 94 | + * install to an attacker-controlled archive. | |
| 95 | + * | |
| 96 | + * @param string $url | |
| 97 | + * @return bool | |
| 98 | + */ | |
| 99 | + protected function is_allowed_package_url($url) | |
| 100 | + { | |
| 101 | + if (!is_string($url) || $url === '') { | |
| 102 | + return false; | |
| 103 | + } | |
| 104 | + | |
| 105 | + $parts = wp_parse_url($url); | |
| 106 | + | |
| 107 | + if (empty($parts['scheme']) || strtolower($parts['scheme']) !== 'https' || empty($parts['host'])) { | |
| 108 | + return false; | |
| 109 | + } | |
| 110 | + | |
| 111 | + $host = strtolower($parts['host']); | |
| 112 | + | |
| 113 | + return in_array($host, ['downloads.wordpress.org', 'wordpress.org', 'www.wordpress.org'], true); | |
| 114 | + } | |
| 115 | + | |
| 116 | + /** | |
| 80 | 117 | * install_plugin |
| 81 | 118 | * |
| 82 | 119 | * @param mixed $slug |
| 83 | 120 | * @param bool $active |
| @@ -129,9 +166,9 @@ | ||
| 129 | 166 | * @return mixed bool|WP_Error |
| 130 | 167 | */ |
| 131 | 168 | public function upgrade_plugin($basename = '') |
| 132 | 169 | { |
| 133 | - if (empty($slug)) { | |
| 170 | + if (empty($basename)) { | |
| 134 | 171 | return new WP_Error('empty_arg', __('Argument should not be empty.', 'betterdocs')); |
| 135 | 172 | } |
| 136 | 173 | |
| 137 | 174 | include_once ABSPATH . 'wp-admin/includes/file.php'; |