CVE-2025-14864

Virusdie <= 1.1.7 - Missing Authorization to Authenticated (Subscriber+) API Key Disclosure

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
1.1.8
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
Low
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=1.1.7
PublishedFebruary 18, 2026
Last updatedFebruary 19, 2026
Affected pluginvirusdie

What Changed in the Fix

Changes introduced in v1.1.8

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

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 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 via wp_ajax_virusdie_apikey) lacks sufficient capability checks and nonce verification in the vulnerable version (1.1.7). While the provided source code shows a canDoAjax check, 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 action parameter.

3. Code Flow

  1. Entry Point: The plugin registers the AJAX action in inc/class-virusdie.php:
    add_action( 'wp_ajax_virusdie_apikey', 'VDWS_VirusdieBehavior::vd_get_apikey' );
  2. Handler Trigger: When a request is made to admin-ajax.php?action=virusdie_apikey, WordPress invokes VDWS_VirusdieBehavior::vd_get_apikey.
  3. Vulnerable Check: In version 1.1.7, vd_get_apikey is executed without a functioning current_user_can('manage_options') check or a nonce check.
  4. Information Disclosure: The function calls VDWS_Virusdie::get_api_key(), which retrieves the sensitive Virusdie API key and outputs it via wp_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:

  1. Authentication: Log in as a Subscriber user to obtain valid session cookies.
  2. Information Retrieval: Send a request to the AJAX endpoint requesting the API key.
  3. Request Details:
    • URL: http://[target]/wp-admin/admin-ajax.php
    • Method: POST
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body: action=virusdie_apikey

6. Test Data Setup

To verify the disclosure, a dummy API key must be present in the database.

  1. Create Subscriber:
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
  2. 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:

  1. 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).
  2. 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 return subscriber).

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 $_GET and $_POST (though wp_ajax usually 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).
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/virusdie/1.1.7/inc/class-virusdie.php /home/deploy/wp-safety.org/data/plugin-versions/virusdie/1.1.8/inc/class-virusdie.php
--- /home/deploy/wp-safety.org/data/plugin-versions/virusdie/1.1.7/inc/class-virusdie.php	2025-12-30 13:54:42.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/virusdie/1.1.8/inc/class-virusdie.php	2026-01-30 22:05:56.000000000 +0000
@@ -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' );
 	}
 
 	/**
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/virusdie/1.1.7/inc/tools/class-virusdie-behavior.php /home/deploy/wp-safety.org/data/plugin-versions/virusdie/1.1.8/inc/tools/class-virusdie-behavior.php
--- /home/deploy/wp-safety.org/data/plugin-versions/virusdie/1.1.7/inc/tools/class-virusdie-behavior.php	2025-12-30 13:54:42.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/virusdie/1.1.8/inc/tools/class-virusdie-behavior.php	2026-01-30 22:05:56.000000000 +0000
@@ -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.