Real Testimonials – Testimonial Slider, Collect Customer Reviews and Video Testimonials <= 3.1.15 - Authenticated (Administrator+) PHP Object Injection
Description
The Real Testimonials – Testimonial Slider, Collect Customer Reviews and Video Testimonials plugin for WordPress is vulnerable to PHP Object Injection in versions up to, and including, 3.1.15 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. If a POP chain is present via an additional plugin or theme installed on the target system, it could allow the attacker to delete arbitrary files, retrieve sensitive data, or execute code.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:HTechnical Details
<=3.1.15What Changed in the Fix
Changes introduced in v3.1.16
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-59521 ## 1. Vulnerability Summary The **Real Testimonials** plugin (versions <= 3.1.15) is vulnerable to **PHP Object Injection** via the deserialization of untrusted input. The vulnerability typically resides in the "Tools" or "Settings" import functionality…
Show full research plan
Exploitation Research Plan - CVE-2026-59521
1. Vulnerability Summary
The Real Testimonials plugin (versions <= 3.1.15) is vulnerable to PHP Object Injection via the deserialization of untrusted input. The vulnerability typically resides in the "Tools" or "Settings" import functionality, where the plugin uses the SPFTESTIMONIAL (Codestar-like) framework to handle configuration imports. By providing a crafted serialized string—often Base64 encoded—to the import field, an authenticated administrator can trigger the unserialize() sink. While no POP chain is identified in the plugin itself, any chain present in the WordPress environment (other plugins/themes) can be leveraged for RCE or file manipulation.
2. Attack Vector Analysis
- Target Endpoint:
/wp-admin/edit.php?post_type=spt_testimonial&page=testimonial_tools - Vulnerable Parameter: The import data field, typically named after the framework prefix:
sp_testimonial_pro_tools[import]or a similar key within the POST body. - Authentication: Required (Administrator+).
- Preconditions: The attacker must have access to the "Tools" submenu under the "Real Testimonials" menu.
3. Code Flow
- Registration: The "Tools" page is registered in
src/Admin/Views/Framework/configs/tools.phpwith the unique prefixsp_testimonial_pro_tools. - Framework Initialization: The
SPFTESTIMONIAL_Optionsclass (insrc/Admin/Views/Framework/Classes/options.class.php) initializes the settings page for this prefix. - Import Logic: When the "Import" form is submitted, the framework processes the input. In the underlying framework (Codestar derivative), the
importfield type triggers a block of code that:- Retrieves the string from the POST request or uploaded file.
- Decodes it (if Base64 encoded).
- Passes it to
unserialize()ormaybe_unserialize()to restore the option array.
- Sink: The
unserialize()function is called on the attacker-controlled string, leading to object instantiation.
4. Nonce Acquisition Strategy
The "Tools" page uses a nonce for form security.
- Identify Page: The "Tools" page is located at
edit.php?post_type=spt_testimonial&page=testimonial_tools. - Locate Nonce: The framework typically embeds a nonce in a hidden field named
_wpnonceor similar, tied to the prefixsp_testimonial_pro_tools. - Extraction:
- Navigate to the Tools page.
- Use
browser_evalto extract the nonce from the form. - Variable check: Look for
window.sp_testimonial_pro_tools_varsor simply the hidden input field.
// Example extraction via browser_eval
document.querySelector('input[name="_wpnonce"]')?.value ||
document.querySelector('input[name="sp_testimonial_pro_tools_nonce"]')?.value;
5. Exploitation Strategy
Step 1: Preparation
Construct a simple PHP Object Injection payload. Since no specific POP chain is required for the PoC, we use stdClass.
- Payload:
O:8:"stdClass":1:{s:3:"foo";s:3:"bar";} - Encoding: Base64 encode the payload if the framework expects it (likely):
Tzo4OiJzdGRDbGFzcyI6MTp7czozOiJmb28iO3M6MzoiYmFyIjt9
Step 2: HTTP Request
Use the http_request tool to send a POST request to the Tools page.
- URL:
https://<target>/wp-admin/edit.php?post_type=spt_testimonial&page=testimonial_tools - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
(Note: Parameter names likesp_testimonial_pro_tools%5Bimport%5D=Tzo4OiJzdGRDbGFzcyI6MTp7czozOiJmb28iO3M6MzoiYmFyIjt9&_wpnonce=<NONCE>&sp_testimonial_pro_tools_import_button=1sp_testimonial_pro_tools_import_buttonare inferred from standard framework behavior and should be verified viabrowser_evalon the page source.)
6. Test Data Setup
- Plugin Installation: Ensure "Real Testimonials" <= 3.1.15 is installed and activated.
- User: An Administrator user is required for the session.
- Page Creation: No specific frontend content is needed as the vulnerability is in the Admin backend.
7. Expected Results
- The server processes the request.
- If a debugger like Xdebug is active or the object has a
__wakeupmethod that triggers an error, the error will be visible. - In a successful "blind" injection, the framework might attempt to replace existing options with the "array" it expected, potentially breaking plugin settings (a sign that the data was processed).
8. Verification Steps
Since this is a deserialization vulnerability without a confirmed POP chain in the plugin, verification involves confirming the sink is reached:
- Error Logs: Check
wp-content/debug.logfor "Notice: unserialize(): Error at offset..." if the payload is slightly malformed, confirmingunserializewas called. - Database Check: If the injection was intended to overwrite options, use
wp option get sp_testimonial_pro_toolsto see if the data changed. - Dummy Class: Create a temporary file in the plugin directory containing a dummy class with a
__destructmethod that writes to a file, then try to inject that class.
9. Alternative Approaches
- File Upload: The
tools.phpfile defines the import as "Import JSON/CSV File". If the POST parameter fails, try uploading a.jsonfile containing the serialized string via amultipart/form-datarequest to the same endpoint. - Metabox Injection: Check if the
save_meta_boxfunction insrc/Admin/Views/Framework/Classes/metabox.class.php(Line 115) accepts serialized strings for fields with'data_type' => 'serialize'. If so, updating a Testimonial post via the REST API or Admin editor with a crafted meta value could trigger the vulnerability upon the next page load.
Summary
The Real Testimonials plugin for WordPress is vulnerable to PHP Object Injection in versions up to and including 3.1.15 due to the use of the unsafe `maybe_unserialize` function on user-supplied import data. Authenticated administrators can exploit this by submitting crafted serialized objects through the plugin's 'Tools' import feature, potentially leading to remote code execution or file manipulation if a suitable POP chain exists on the site.
Vulnerable Code
// src/Includes/Import_Export.php:133 $export['shortcode'][ $key ]['meta']['sp_tpro_meta_options'] = isset( $export['shortcode'][ $key ]['meta']['sp_tpro_meta_options'] ) ? maybe_unserialize( $export['shortcode'][ $key ]['meta']['sp_tpro_meta_options'] ) : ''; --- // src/Includes/Import_Export.php:335 if ( is_string( $value ) ) { $value = maybe_unserialize( str_replace( '{#ID#}', $new_shortcode_id, $value ) ); }
Security Fix
@@ -130,7 +130,7 @@ foreach ( $export['shortcode'] as $key => $value ) { $export['shortcode'][ $key ]['meta']['sp_tpro_meta_options'] = isset( $export['shortcode'][ $key ]['meta']['sp_tpro_meta_options'] ) - ? maybe_unserialize( $export['shortcode'][ $key ]['meta']['sp_tpro_meta_options'] ) + ? $this->maybe_unserialize( $export['shortcode'][ $key ]['meta']['sp_tpro_meta_options'] ) : ''; } } @@ -153,32 +153,20 @@ wp_send_json( $export, 200 ); } - /** - * Get page by title + * Maybe unserialize data if it is serialized. * - * @param string $page_title Page title. - * @param string $output Optional. - * @param string $post_type Post type. - * @return obj. + * @param mixed $data Data to maybe unserialize. + * @param mixed $options Options for unserialization. + * @return mixed */ - public function sp_testimonial_get_page_by_title( $page_title, $output = OBJECT, $post_type = 'page' ) { - global $wpdb; - $sql = $wpdb->prepare( - " - SELECT ID - FROM $wpdb->posts - WHERE post_title = %s - AND post_type = %s - ", - $page_title, - $post_type - ); - $page = $wpdb->get_var( $sql ); // phpcs:ignore -- WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching - if ( $page ) { - return get_post( $page, $output ); + public function maybe_unserialize( $data, $options = array() ) { + if ( is_serialized( $data ) ) { + $default_option = array( 'allowed_classes' => false ); + $options = wp_parse_args( $options, $default_option ); + return unserialize( $data, $options ); // phpcs:ignore -- dissallowed any classes. } - return null; + return $data; } /** @@ -332,7 +316,7 @@ if ( isset( $shortcode['meta'] ) && is_array( $shortcode['meta'] ) ) { foreach ( $shortcode['meta'] as $key => $value ) { if ( is_string( $value ) ) { - $value = maybe_unserialize( str_replace( '{#ID#}', $new_shortcode_id, $value ) ); + $value = $this->maybe_unserialize( str_replace( '{#ID#}', $new_shortcode_id, $value ) ); } update_post_meta( $new_shortcode_id,
Exploit Outline
1. Authenticate as an Administrator and navigate to the 'Tools' page under the 'Real Testimonials' menu (`/wp-admin/edit.php?post_type=spt_testimonial&page=testimonial_tools`). 2. Prepare a PHP Object Injection payload, such as a serialized object using `stdClass` or any valid POP chain present in the environment (e.g., `O:8:"stdClass":1:{s:3:"foo";s:3:"bar";}`). 3. Base64 encode the serialized string if required by the framework's import handler. 4. Extract the security nonce (`_wpnonce`) from the page's HTML form. 5. Send a POST request to the Tools page including the crafted string in the `sp_testimonial_pro_tools[import]` parameter (or the relevant framework import field) and the valid nonce. This will trigger the `maybe_unserialize` sink in the `Import_Export.php` class during processing.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.