CVE-2026-7566

LearnPress – Backup & Migration Tool <= 4.1.4 - Authenticated (Administrator+) PHP Object Injection via WXR XML File Upload

mediumDeserialization of Untrusted Data
6.6
CVSS Score
6.6
CVSS Score
medium
Severity
4.1.5
Patched in
1d
Time to patch

Description

The LearnPress – Backup & Migration Tool plugin for WordPress is vulnerable to PHP Object Injection in all versions up to, and including, 4.1.4 via deserialization of untrusted input . This makes it possible for authenticated attackers, with administrator-level access and above, to inject a PHP Object. No known POP chain is present in the vulnerable software, which means this vulnerability has no impact unless another plugin or theme containing a POP chain is installed on the site. If a POP chain is present via an additional plugin or theme installed on the target system, it may allow the attacker to perform actions like delete arbitrary files, retrieve sensitive data, or execute code depending on the POP chain present.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=4.1.4
PublishedJune 5, 2026
Last updatedJune 6, 2026

What Changed in the Fix

Changes introduced in v4.1.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

I cannot fulfill your request to produce a detailed exploitation research plan. I am prohibited from generating actionable instructions or functional material for the exploitation of software vulnerabilities, including the development of proof-of-concept plans or specific payload structures. For se…

Show full research plan

I cannot fulfill your request to produce a detailed exploitation research plan. I am prohibited from generating actionable instructions or functional material for the exploitation of software vulnerabilities, including the development of proof-of-concept plans or specific payload structures.

For security researchers and developers looking to understand and mitigate such risks, I recommend exploring the following resources:

  1. Secure Deserialization: Review the OWASP guide on Deserialization of Untrusted Data to understand why functions like unserialize() should be avoided on user-controlled input and how to implement safer alternatives like json_decode().
  2. WordPress Security Best Practices: The WordPress Plugin Handbook's Security section provides essential guidance on using nonces correctly, implementing capability checks, and sanitizing/validating all forms of input, including file uploads.
  3. WXR Import Security: When handling WordPress eXtended RSS (WXR) files, it is a best practice to use the built-in WordPress Importer logic or the maybe_unserialize() function, which performs basic checks before attempting to deserialize data.
  4. Vulnerability Remediation: If you are a user of the affected plugin, ensure you have updated to version 4.1.5 or later, which contains the necessary security patches.
Research Findings
Static analysis — not yet PoC-verified

Summary

The LearnPress – Backup & Migration Tool plugin is vulnerable to PHP Object Injection via the import of LearnPress data in WXR (XML) format. Authenticated administrators can upload a crafted XML file containing serialized PHP objects in metadata fields, which are then processed by the insecure `maybe_unserialize()` function, potentially leading to remote code execution if a suitable POP chain is present.

Vulnerable Code

// inc/admin/providers/learnpress/class-lp-import-learnpress.php

if ( $key ) {
    // export gets meta straight from the DB so could have a serialized string
    if ( ! $value ) {
        $value = maybe_unserialize( $meta['value'] );
    }

    update_post_meta( $post_id, $key, $value );
}

---

// inc/admin/providers/learnpress/class-lp-import-learnpress.php

$inserted = $wpdb->insert(
    $wpdb->prefix . 'learnpress_question_answermeta',
    array(
        'learnpress_question_answer_id' => $question_answer_id,
        'meta_key'   => $answer_meta['meta_key'],
        'meta_value' => $answer_meta['meta_value'],
    ),
    array( '%d', '%s', '%s' )
);

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/learnpress-import-export/4.1.4/config/migration-plugin.php /home/deploy/wp-safety.org/data/plugin-versions/learnpress-import-export/4.1.5/config/migration-plugin.php
--- /home/deploy/wp-safety.org/data/plugin-versions/learnpress-import-export/4.1.4/config/migration-plugin.php	2026-04-29 02:14:06.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/learnpress-import-export/4.1.5/config/migration-plugin.php	2026-05-25 03:13:18.000000000 +0000
@@ -1,59 +1,59 @@
-<?php
-
-use LPImportExport\Migration\Helpers\Plugin;
-
-$is_tutor_active = Plugin::is_tutor_active();
-$config_data     = array();
-
-if ( $is_tutor_active ) {
-	$config_data['tutor'] = array(
-		'title' => esc_html__( 'Tutor LMS', 'learnpress-import-export' ),
-		'name'  => 'tutor',
-		'icon'  => LP_ADDON_IMPORT_EXPORT_ASSETS_URL . '/images/tutor-128x128.jpg',
-		'url'   => add_query_arg(
-			array(
-				'page' => 'lp-migration-tool',
-				'tab'  => 'tutor',
-			),
-			admin_url( 'admin.php' )
-		),
-		'desc'  => esc_html__( 'Migrate the Tutor data to LearnPress with the LearnPress Migration Tool.', 'learnpress-import-export' ),
-	);
-}
-
-if ( Plugin::is_learndash_active() ) {
-	$config_data['learndash'] = array(
-		'title' => esc_html__( 'LearnDash', 'learnpress-import-export' ),
-		'name'  => 'learndash',
-		'icon'  => LP_ADDON_IMPORT_EXPORT_ASSETS_URL . '/images/learndash-128x128.png',
-		'url'   => add_query_arg(
-			array(
-				'page' => 'lp-migration-tool',
-				'tab'  => 'learndash',
-			),
-			admin_url( 'admin.php' )
-		),
-		'desc'  => esc_html__( 'Migrate the LearnDash data to LearnPress with the LearnPress Migration Tool.', 'learnpress-import-export' ),
-	);
-}
-
-if ( Plugin::is_master_study_active() ) {
-	$config_data['master_study'] = array(
-		'title' => esc_html__( 'MasterStudy', 'learnpress-import-export' ),
-		'name'  => 'master_study',
-		'icon'  => '',
-		'url'   => add_query_arg(
-			array(
-				'page' => 'lp-migration-tool',
-				'tab'  => 'master_study',
-			),
-			admin_url( 'admin.php' )
-		),
-		'desc'  => esc_html__( 'Migrate the MasterStudy data to LearnPress with the LearnPress Migration Tool.', 'learnpress-import-export' ),
-	);
-}
-
-return apply_filters(
-	'learnpress-import-export/filter/config/migration-plugin',
-	$config_data
-);
+<?php
+
+use LPImportExport\Migration\Helpers\Plugin;
+
+$is_tutor_active = Plugin::is_tutor_active();
+$config_data     = array();
+
+if ( $is_tutor_active ) {
+	$config_data['tutor'] = array(
+		'title' => esc_html__( 'Tutor LMS', 'learnpress-import-export' ),
+		'name'  => 'tutor',
+		'icon'  => LP_ADDON_IMPORT_EXPORT_ASSETS_URL . '/images/tutor-128x128.jpg',
+		'url'   => add_query_arg(
+			array(
+				'page' => 'lp-migration-tool',
+				'tab'  => 'tutor',
+			),
+			admin_url( 'admin.php' )
+		),
+		'desc'  => esc_html__( 'Migrate the Tutor data to LearnPress with the LearnPress Migration Tool.', 'learnpress-import-export' ),
+	);
+}
+
+if ( Plugin::is_learndash_active() ) {
+	$config_data['learndash'] = array(
+		'title' => esc_html__( 'LearnDash', 'learnpress-import-export' ),
+		'name'  => 'learndash',
+		'icon'  => LP_ADDON_IMPORT_EXPORT_ASSETS_URL . '/images/learndash-128x128.png',
+		'url'   => add_query_arg(
+			array(
+				'page' => 'lp-migration-tool',
+				'tab'  => 'learndash',
+			),
+			admin_url( 'admin.php' )
+		),
+		'desc'  => esc_html__( 'Migrate the LearnDash data to LearnPress with the LearnPress Migration Tool.', 'learnpress-import-export' ),
+	);
+}
+
+if ( Plugin::is_master_study_active() ) {
+	$config_data['master_study'] = array(
+		'title' => esc_html__( 'MasterStudy', 'learnpress-import-export' ),
+		'name'  => 'master_study',
+		'icon'  => '',
+		'url'   => add_query_arg(
+			array(
+				'page' => 'lp-migration-tool',
+				'tab'  => 'master_study',
+			),
+			admin_url( 'admin.php' )
+		),
+		'desc'  => esc_html__( 'Migrate the MasterStudy data to LearnPress with the LearnPress Migration Tool.', 'learnpress-import-export' ),
+	);
+}
+
+return apply_filters(
+	'learnpress-import-export/filter/config/migration-plugin',
+	$config_data
+);
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/learnpress-import-export/4.1.4/inc/admin/providers/learnpress/class-lp-export-learnpress.php /home/deploy/wp-safety.org/data/plugin-versions/learnpress-import-export/4.1.5/inc/admin/providers/learnpress/class-lp-export-learnpress.php
--- /home/deploy/wp-safety.org/data/plugin-versions/learnpress-import-export/4.1.4/inc/admin/providers/learnpress/class-lp-export-learnpress.php	2026-04-29 02:14:06.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/learnpress-import-export/4.1.5/inc/admin/providers/learnpress/class-lp-export-learnpress.php	2026-05-25 03:13:18.000000000 +0000
@@ -42,7 +42,18 @@
 				return;
 			}
 
-			$this->_exported_data = array_merge( $this->_exported_data, $_REQUEST );
+			// Patch C: Only merge known, expected export parameters — never the full $_REQUEST.
+			$allowed_export_keys  = array(
+				'courses',
+				'learn-press-export-file-name',
+				'export-nonce',
+				'save_export',
+				'download_export',
+				'step',
+				'action',
+			);
+			$filtered_request     = array_intersect_key( $_REQUEST, array_flip( $allowed_export_keys ) );
+			$this->_exported_data = array_merge( $this->_exported_data, $filtered_request );
 
 			add_action( 'lpie_export_view_step_1', array( $this, 'step_1' ) );
 			add_action( 'lpie_export_view_step_2', array( $this, 'step_2' ) );
@@ -219,12 +230,12 @@
 			$file_name    = LP_Request::get_param( 'learn-press-export-file-name' );
 			$xml_filename = $this->get_export_file_name( $file_name );
 
-			if ( isset( $_REQUEST['save_export'] ) ) {
+			if ( ! empty( LP_Request::get_param( 'save_export' ) ) ) {
 				$xml_filename = 'learnpress/export/' . $xml_filename;
 				lpie_put_contents( $xml_filename, $content );
 			}
 
-			if ( isset( $_REQUEST['download_export'] ) ) {
+			if ( ! empty( LP_Request::get_param( 'download_export' ) ) ) {
 				$download_filename = $this->get_download_export_file_name( $file_name );
 				$download_filename = 'learnpress/tmp/' . $download_filename;
 				lpie_put_contents( $download_filename, $content );
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/learnpress-import-export/4.1.4/inc/admin/providers/learnpress/class-lp-import-learnpress.php /home/deploy/wp-safety.org/data/plugin-versions/learnpress-import-export/4.1.5/inc/admin/providers/learnpress/class-lp-import-learnpress.php
--- /home/deploy/wp-safety.org/data/plugin-versions/learnpress-import-export/4.1.4/inc/admin/providers/learnpress/class-lp-import-learnpress.php	2026-04-29 02:14:06.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/learnpress-import-export/4.1.5/inc/admin/providers/learnpress/class-lp-import-learnpress.php	2026-05-25 03:13:18.000000000 +0000
@@ -116,12 +116,19 @@
 		 * @throws Exception
 		 */
 		public function do_import() {
+			if ( ! current_user_can( 'manage_options' ) ) {
+				wp_die( esc_html__( 'You do not have sufficient permissions.', 'learnpress-import-export' ) );
+			}
 
 			$import_file      = LP_Request::get_param( 'import-file' );
 			$path_import_file = realpath( lpie_root_path() . '/learnpress/' . $import_file );
-			if ( ! $path_import_file ) {
+			$jail_dir         = realpath( lpie_root_path() . '/learnpress' );
+
+			// --- Patch B: Path jail — import-file must resolve inside uploads/learnpress ---
+			if ( ! $path_import_file || ! $jail_dir || strpos( $path_import_file, $jail_dir . DIRECTORY_SEPARATOR ) !== 0 ) {
 				return;
 			}
+			// --- End Patch B path jail ---
 
 			$file_data   = $this->parse( $path_import_file );
 			$map_authors = learn_press_get_request( 'map_authors' );
@@ -229,7 +236,7 @@
 				}
 			}
 
-			if ( ! empty( $_REQUEST['save_import'] ) ) {
+			if ( ! empty( LP_Request::get_param( 'save_import' ) ) ) {
 				if ( ! file_exists( lpie_import_path() ) ) {
 					mkdir( lpie_import_path(), 0777, true );
 				}
@@ -577,8 +584,9 @@
 
 					if ( $key ) {
 						// export gets meta straight from the DB so could have a serialized string
+						// Patch B: use safe wrapper to prevent PHP Object Injection via WXR XML.
 						if ( ! $value ) {
-							$value = maybe_unserialize( $meta['value'] );
+							$value = lpie_safe_maybe_unserialize( $meta['value'] );
 						}
 
 						update_post_meta( $post_id, $key, $value );
@@ -824,12 +832,20 @@
 					$result             = $wpdb->get_results( $query );
 					$question_answer_id = $result[0]->question_answer_id;
 
+					// Patch B (extended): strip object payloads from WXR-supplied meta_value
+					// before persisting, so later maybe_unserialize() calls cannot instantiate
+					// attacker-controlled classes.
+					$meta_value = $answer_meta['meta_value'];
+					if ( is_string( $meta_value ) && is_serialized( $meta_value ) ) {
+						$meta_value = maybe_serialize( lpie_safe_maybe_unserialize( $meta_value ) );
+					}
+
 					$inserted = $wpdb->insert(
 						$wpdb->prefix . 'learnpress_question_answermeta',
 						array(
 							'learnpress_question_answer_id' => $question_answer_id,
 							'meta_key'   => $answer_meta['meta_key'],
-							'meta_value' => $answer_meta['meta_value'],
+							'meta_value' => $meta_value,
 						),
 						array( '%d', '%s', '%s' )
 					);

Exploit Outline

1. **Prerequisites**: Access to a WordPress site with the LearnPress – Backup & Migration Tool plugin (<= 4.1.4) and Administrator privileges. 2. **Malicious Payload**: Create a WXR XML file (typically used for WordPress imports) containing a serialized PHP object inside a `<wp:meta_value>` tag for a LearnPress course, lesson, or question metadata field. 3. **Upload and Trigger**: Navigate to the plugin's import tool (Admin Dashboard > LearnPress > Import/Export). Upload the crafted XML file and proceed with the import process. 4. **Deserialization**: During the import, the plugin parses the XML. In `inc/admin/providers/learnpress/class-lp-import-learnpress.php`, it retrieves the metadata values and passes them to `maybe_unserialize()`. 5. **Execution**: The `maybe_unserialize()` function executes the serialized object, triggering any available POP chains in the environment (e.g., from other installed plugins or themes) to achieve impact like remote code execution.

Check if your site is affected.

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