| @@ -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'; |
| @@ -151,12 +188,13 @@ | ||
| 151 | 188 | if(!current_user_can( 'install_plugins' )) { |
| 152 | 189 | wp_send_json_error(__('you are not allowed to do this action', 'betterdocs')); |
| 153 | 190 | } |
| 154 | 191 | |
| 155 | - $slug = isset( $_POST['slug'] ) ? sanitize_text_field( $_POST['slug'] ) : ''; | |
| 192 | + $slug = isset( $_POST['slug'] ) ? sanitize_text_field( wp_unslash( $_POST['slug'] ) ) : ''; | |
| 156 | 193 | $result = $this->install_plugin( $slug ); |
| 157 | 194 | |
| 158 | - if( isset( $_POST['promotype'] ) && 'eb-banner' === $_POST['promotype'] ) { | |
| 195 | + $promotype = isset( $_POST['promotype'] ) ? sanitize_text_field( wp_unslash( $_POST['promotype'] ) ) : ''; | |
| 196 | + if ( 'eb-banner' === $promotype ) { | |
| 159 | 197 | wp_remote_get( 'https://essential-addons.com/essential-blocks-install-gutenberg' ); |
| 160 | 198 | } |
| 161 | 199 | |
| 162 | 200 | if ( is_wp_error( $result ) ) { |
| @@ -173,9 +211,9 @@ | ||
| 173 | 211 | if(!current_user_can( 'update_plugins' )) { |
| 174 | 212 | wp_send_json_error(__('you are not allowed to do this action', 'betterdocs')); |
| 175 | 213 | } |
| 176 | 214 | |
| 177 | - $basename = isset( $_POST['basename'] ) ? sanitize_text_field( $_POST['basename'] ) : ''; | |
| 215 | + $basename = isset( $_POST['basename'] ) ? sanitize_text_field( wp_unslash( $_POST['basename'] ) ) : ''; | |
| 178 | 216 | $result = $this->upgrade_plugin( $basename ); |
| 179 | 217 | |
| 180 | 218 | if (is_wp_error($result)) { |
| 181 | 219 | wp_send_json_error($result->get_error_message()); |
| @@ -192,9 +230,9 @@ | ||
| 192 | 230 | if(!current_user_can( 'activate_plugins' )) { |
| 193 | 231 | wp_send_json_error(__('you are not allowed to do this action', 'betterdocs')); |
| 194 | 232 | } |
| 195 | 233 | |
| 196 | - $basename = isset( $_POST['basename'] ) ? sanitize_text_field( $_POST['basename'] ) : ''; | |
| 234 | + $basename = isset( $_POST['basename'] ) ? sanitize_text_field( wp_unslash( $_POST['basename'] ) ) : ''; | |
| 197 | 235 | $result = activate_plugin( $basename, '', false, true ); |
| 198 | 236 | |
| 199 | 237 | if ( is_wp_error( $result ) ) { |
| 200 | 238 | wp_send_json_error( $result->get_error_message() ); |
| @@ -213,9 +251,9 @@ | ||
| 213 | 251 | if ( ! current_user_can( 'activate_plugins' ) ) { |
| 214 | 252 | wp_send_json_error( __( 'you are not allowed to do this action', 'betterdocs' ) ); |
| 215 | 253 | } |
| 216 | 254 | |
| 217 | - $basename = isset( $_POST['basename'] ) ? sanitize_text_field( $_POST['basename'] ) : ''; | |
| 255 | + $basename = isset( $_POST['basename'] ) ? sanitize_text_field( wp_unslash( $_POST['basename'] ) ) : ''; | |
| 218 | 256 | deactivate_plugins( $basename, true ); |
| 219 | 257 | |
| 220 | 258 | wp_send_json_success( __( 'Plugin is deactivated successfully!', 'betterdocs' ) ); |
| 221 | 259 | } |
| @@ -222,9 +260,10 @@ | ||
| 222 | 260 | |
| 223 | 261 | public function ajax_auto_active_even_not_installed() { |
| 224 | 262 | check_ajax_referer( 'betterdocs-wpdeveloper-plugins', 'security' ); |
| 225 | 263 | |
| 226 | - if ( $this->get_local_plugin_data( $_POST['basename'] ) === false ) { | |
| 264 | + $basename = isset( $_POST['basename'] ) ? sanitize_text_field( wp_unslash( $_POST['basename'] ) ) : ''; | |
| 265 | + if ( $this->get_local_plugin_data( $basename ) === false ) { | |
| 227 | 266 | $this->ajax_install_plugin(); |
| 228 | 267 | } else { |
| 229 | 268 | $this->ajax_activate_plugin(); |
| 230 | 269 | } |