Plugin Installation Vulnerability in GutenKit
We recently had what appeared to be a hacker probing for usage of the WordPress plugin GutenKit on our website. That likely is related to a vulnerability that had existed in the plugin. The developer wasn’t exactly upfront about that as what appears to be the relevant changelog entry only says “Improved: Security for external plugin installations.” The developer also still doesn’t seem to understand how to properly secure code that they have run through the WordPress REST API.
The relevant code exists in the file /includes/Admin/Api/ActivePluginData.php. That file registers a function to install another plugin in a way that makes it accessible to even those not logged in to WordPress:
19 20 21 22 23 24 25 26 27 | add_action('rest_api_init', function() { register_rest_route('gutenkit/v1', 'install-active-plugin', array( 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [$this, 'install_and_activate_plugin_from_external'], 'permission_callback' => '__return_true', ), ); }); |
The reason for that is that the permission_callback is set to “__return_true,” instead of being set to check for the relevant permission install_plugins. The change the developer made to improperly fix the vulnerability was to add a capability check to the function called:
76 77 78 79 80 81 | public function install_and_activate_plugin_from_external($request) { // Check if the user has the required capability if (!current_user_can('install_plugins')) { wp_send_json_error('You do not have permission to install plugins.'); return; } |
We have notified the developer of that issue.
If you look at the rest of the code, there isn’t a nonce check before the installation happens:
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | // The external plugin URL $plugin_url = esc_url_raw($request->get_param('plugin')); $slug = sanitize_text_field($request->get_param('slug')); $plugin_slug = "$slug/$slug.php"; $plugin_dir = WP_PLUGIN_DIR; // This points to wp-content/plugins require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/plugin.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; WP_Filesystem(); // Download the plugin ZIP file $temp_file = download_url($plugin_url); if (is_wp_error($temp_file)) { wp_send_json_error('Failed to download plugin. Error: ' . $temp_file->get_error_message()); return; } // Unzip the plugin into the wp-content/plugins directory $unzip_result = unzip_file($temp_file, $plugin_dir); |
There isn’t a cross-site request forgery(CSRF) issue there because current_user_can() requires a nonce to be provided with a REST request for it to be able to determine the current user.
Free Warning
As the vulnerability looks to be targeted by a hacker, we are adding accurate data on it to the free data that comes with our Plugin Vulnerabilities plugin.