| @@ -59,8 +59,36 @@ | ||
| 59 | 59 | } |
| 60 | 60 | } |
| 61 | 61 | |
| 62 | 62 | /** |
| 63 | + * Exact allowlist of installed plugin files that belong to our known slugs. | |
| 64 | + * | |
| 65 | + * Returns real plugin basenames (e.g. "elementor/elementor.php") taken from the | |
| 66 | + * actual installed plugin set, filtered to the slugs we manage. Used to validate | |
| 67 | + * any $_POST plugin-file value before it reaches activate_plugin()/upgrade(), so | |
| 68 | + * arbitrary input can never select an unrelated plugin file. | |
| 69 | + * | |
| 70 | + * @return string[] | |
| 71 | + */ | |
| 72 | + protected function get_allowed_plugin_files() | |
| 73 | + { | |
| 74 | + if (!function_exists('get_plugins')) { | |
| 75 | + require_once ABSPATH . 'wp-admin/includes/plugin.php'; | |
| 76 | + } | |
| 77 | + | |
| 78 | + $known_slugs = array_values(wp_list_pluck($this->plugins_list, 'slug')); | |
| 79 | + $allowed = array(); | |
| 80 | + | |
| 81 | + foreach (array_keys(get_plugins()) as $plugin_file) { | |
| 82 | + if (in_array(dirname($plugin_file), $known_slugs, true)) { | |
| 83 | + $allowed[] = $plugin_file; | |
| 84 | + } | |
| 85 | + } | |
| 86 | + | |
| 87 | + return $allowed; | |
| 88 | + } | |
| 89 | + | |
| 90 | + /** | |
| 63 | 91 | * Menu Items |
| 64 | 92 | * |
| 65 | 93 | * @author Jewel Theme <support@jeweltheme.com> |
| 66 | 94 | */ |
| @@ -288,13 +316,15 @@ | ||
| 288 | 316 | if ((is_multisite() && !is_network_admin()) || !current_user_can('install_plugins')) { |
| 289 | 317 | wp_send_json_error(array('mess' => __('Invalid access', 'master-addons'))); |
| 290 | 318 | } |
| 291 | 319 | |
| 292 | - $file = sanitize_text_field(wp_unslash($_POST['file'])); | |
| 293 | - $known_slugs = array_values(wp_list_pluck($this->plugins_list, 'slug')); | |
| 294 | - $file_slug = dirname($file); | |
| 320 | + $file = sanitize_text_field(wp_unslash($_POST['file'])); | |
| 295 | 321 | |
| 296 | - if (!in_array($file_slug, $known_slugs)) { | |
| 322 | + // Enforce an exact allowlist of real installed plugin files that | |
| 323 | + // belong to our known slugs. Arbitrary input (or a crafted basename | |
| 324 | + // whose dirname happens to match a slug) must never reach | |
| 325 | + // activate_plugin(); only an exact installed plugin file passes. | |
| 326 | + if (!in_array($file, $this->get_allowed_plugin_files(), true)) { | |
| 297 | 327 | wp_send_json_error(array('mess' => __('Invalid plugin', 'master-addons'))); |
| 298 | 328 | } |
| 299 | 329 | |
| 300 | 330 | $result = activate_plugin($file); |
| @@ -350,19 +380,29 @@ | ||
| 350 | 380 | if ((is_multisite() && !is_network_admin()) || !current_user_can('install_plugins')) { |
| 351 | 381 | wp_send_json_error(array('mess' => __('Invalid access', 'master-addons'))); |
| 352 | 382 | } |
| 353 | 383 | |
| 354 | - $plugin = sanitize_text_field(wp_unslash($_POST['plugin'])); | |
| 355 | - $plugin_links = array_values(wp_list_pluck($this->plugins_list, 'download_link')); | |
| 356 | - $known_slugs = array_values(wp_list_pluck($this->plugins_list, 'slug')); | |
| 357 | - $plugin_slug = dirname($plugin); | |
| 358 | - $is_valid = in_array($plugin, $plugin_links) || ($plugin_slug !== '.' && in_array($plugin_slug, $known_slugs)); | |
| 384 | + $plugin = sanitize_text_field(wp_unslash($_POST['plugin'])); | |
| 385 | + $type = isset($_POST['type']) ? sanitize_text_field(wp_unslash($_POST['type'])) : 'install'; | |
| 359 | 386 | |
| 360 | - if (!$is_valid) { | |
| 361 | - wp_send_json_error(array('mess' => __('Invalid plugin', 'master-addons'))); | |
| 387 | + // Validate against an exact allowlist that depends on the operation: | |
| 388 | + // - install: $plugin is a package source; must be exactly one of our | |
| 389 | + // known download links. | |
| 390 | + // - upgrade: $plugin is a plugin file; must be exactly one of the real | |
| 391 | + // installed plugin files under our known slugs. | |
| 392 | + // dirname()-based matching is intentionally NOT used — it would accept a | |
| 393 | + // crafted value whose directory merely resembles a known slug. | |
| 394 | + if ('install' === $type) { | |
| 395 | + $plugin_links = array_values(wp_list_pluck($this->plugins_list, 'download_link')); | |
| 396 | + if (!in_array($plugin, $plugin_links, true)) { | |
| 397 | + wp_send_json_error(array('mess' => __('Invalid plugin', 'master-addons'))); | |
| 398 | + } | |
| 399 | + } else { | |
| 400 | + if (!in_array($plugin, $this->get_allowed_plugin_files(), true)) { | |
| 401 | + wp_send_json_error(array('mess' => __('Invalid plugin', 'master-addons'))); | |
| 402 | + } | |
| 362 | 403 | } |
| 363 | 404 | |
| 364 | - $type = isset($_POST['type']) ? sanitize_text_field(wp_unslash($_POST['type'])) : 'install'; | |
| 365 | 405 | $skin = new \WP_Ajax_Upgrader_Skin(); |
| 366 | 406 | $upgrader = new \Plugin_Upgrader($skin); |
| 367 | 407 | |
| 368 | 408 | if ('install' === $type) { |