Atomic Edge analysis of CVE-2026-32484:
The weForms WordPress plugin, versions up to and including 1.6.26, contains an unauthenticated PHP Object Injection vulnerability. This vulnerability affects multiple components within the plugin that handle serialized form entry data. Attackers can exploit this flaw by submitting malicious serialized objects to the plugin’s data processing functions, potentially leading to remote code execution if a suitable POP chain exists in the WordPress environment.
Atomic Edge research identifies the root cause as unsafe deserialization of untrusted user input across seven distinct locations in the codebase. The vulnerable functions include `process_payment_data()` in `weforms/includes/admin/class-privacy.php`, `get_formatted_data()` in `weforms/includes/class-form-entry.php`, and `weforms_get_pain_text()` in `weforms/includes/functions.php`. Each location directly passes user-controlled data to PHP’s `unserialize()` function without validation. The `payment_data` property from form entries and serialized field values from the database serve as the injection vectors. No authentication checks protect these deserialization points.
Exploitation requires an attacker to submit a malicious serialized PHP object to any endpoint that processes weForms data. The primary attack vector involves the plugin’s AJAX handlers at `/wp-admin/admin-ajax.php` with action parameters like `weforms_form_entry`. REST API endpoints under `/wp-json/weforms/v1/` also process form submissions. Attackers craft serialized objects containing classes with dangerous magic methods like `__wakeup()` or `__destruct()`. The payload must bypass any serialization format checks but does not require authentication.
The patch implements a defense-in-depth approach across all vulnerable files. Developers replaced `unserialize()` calls with safe alternatives using the `allowed_classes => false` parameter. In `class-privacy.php`, `class-weforms-forms-controller.php`, and `class-ajax.php`, they added explicit `is_serialized()` checks before deserialization. The `class-form-entry.php` file received the most extensive modifications, with five separate unsafe `unserialize()` calls replaced. Developers also removed the vulnerable `weforms_get_pain_text()` function entirely, noting that WordPress’s `get_metadata()` already handles deserialization safely.
Successful exploitation enables arbitrary object instantiation in the WordPress context. While the plugin itself contains no known POP chain, attackers can leverage classes from other installed plugins or themes. Common payloads target `__destruct()` or `__wakeup()` methods to achieve file deletion, sensitive data exposure, or remote code execution. The CVSS score of 8.1 reflects the high impact potential when combined with vulnerable components elsewhere in the environment.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/weforms/includes/admin/class-privacy.php
+++ b/weforms/includes/admin/class-privacy.php
@@ -218,7 +218,10 @@
}
public static function process_payment_data( $payment_data ) {
- $field_value = unserialize( $payment_data->payment_data );
+ // Security fix: Prevent PHP Object Injection by restricting allowed classes
+ $field_value = is_serialized( $payment_data->payment_data )
+ ? @unserialize( $payment_data->payment_data, [ 'allowed_classes' => false ] )
+ : $payment_data->payment_data;
$serialized_value = [];
$transaction_data = [];
--- a/weforms/includes/api/class-weforms-forms-controller.php
+++ b/weforms/includes/api/class-weforms-forms-controller.php
@@ -1548,7 +1548,8 @@
$payment = $entry->get_payment_data();
if ( isset( $payment->payment_data ) && is_serialized( $payment->payment_data ) ) {
- $payment->payment_data = unserialize( $payment->payment_data );
+ // Security fix: Prevent PHP Object Injection by restricting allowed classes
+ $payment->payment_data = @unserialize( $payment->payment_data, [ 'allowed_classes' => false ] );
}
$has_empty = false;
--- a/weforms/includes/class-ajax.php
+++ b/weforms/includes/class-ajax.php
@@ -520,7 +520,8 @@
$payment = $entry->get_payment_data();
if ( isset( $payment->payment_data ) && is_serialized( $payment->payment_data ) ) {
- $payment->payment_data = unserialize( $payment->payment_data );
+ // Security fix: Prevent PHP Object Injection by restricting allowed classes
+ $payment->payment_data = @unserialize( $payment->payment_data, [ 'allowed_classes' => false ] );
}
if ( false === $fields ) {
--- a/weforms/includes/class-form-entry.php
+++ b/weforms/includes/class-form-entry.php
@@ -170,7 +170,10 @@
}
} elseif ( in_array( $field['type'], [ 'image_upload', 'file_upload' ] ) ) {
$file_field = '';
- $value = maybe_unserialize( $value );
+ // Security fix: Prevent PHP Object Injection by restricting allowed classes
+ $value = is_serialized( $value )
+ ? @unserialize( $value, [ 'allowed_classes' => false ] )
+ : $value;
if ( is_array( $value ) && $value ) {
foreach ( $value as $attachment_id ) {
@@ -196,7 +199,10 @@
'long' => trim( $long ),
];
} elseif ( $field['type'] == 'multiple_product' ) {
- $field_value = unserialize( $value );
+ // Security fix: Prevent PHP Object Injection by restricting allowed classes
+ $field_value = is_serialized( $value )
+ ? @unserialize( $value, [ 'allowed_classes' => false ] )
+ : $value;
$serialized_value = [];
@@ -218,7 +224,10 @@
$value = implode( '<br> <br> ', $serialized_value );
}
} elseif ( $field['type'] == 'checkbox_grid' ) {
- $entry_value = unserialize( $value );
+ // Security fix: Prevent PHP Object Injection by restricting allowed classes
+ $entry_value = is_serialized( $value )
+ ? @unserialize( $value, [ 'allowed_classes' => false ] )
+ : $value;
if ( $entry_value ) {
$return = '';
@@ -281,7 +290,10 @@
$value = $return;
}
} elseif ( $field['type'] == 'multiple_choice_grid' ) {
- $entry_value = unserialize( $value );
+ // Security fix: Prevent PHP Object Injection by restricting allowed classes
+ $entry_value = is_serialized( $value )
+ ? @unserialize( $value, [ 'allowed_classes' => false ] )
+ : $value;
if ( $entry_value ) {
$return = '';
@@ -344,7 +356,10 @@
$value = $return;
}
} elseif ( $field['type'] == 'address_field' || is_serialized( $value ) ) {
- $field_value = unserialize( $value );
+ // Security fix: Prevent PHP Object Injection by restricting allowed classes
+ $field_value = is_serialized( $value )
+ ? @unserialize( $value, [ 'allowed_classes' => false ] )
+ : $value;
$serialized_value = [];
--- a/weforms/includes/class-form.php
+++ b/weforms/includes/class-form.php
@@ -126,7 +126,10 @@
$form_fields = [];
foreach ( $fields as $key => $content ) {
- $field = maybe_unserialize( $content->post_content );
+ // Security fix: Prevent PHP Object Injection by restricting allowed classes
+ $field = is_serialized( $content->post_content )
+ ? @unserialize( $content->post_content, [ 'allowed_classes' => false ] )
+ : $content->post_content;
if ( empty( $field['template'] ) ) {
continue;
--- a/weforms/includes/functions.php
+++ b/weforms/includes/functions.php
@@ -1248,14 +1248,15 @@
* @return string
**/
function weforms_get_pain_text( $value ) {
- if ( is_serialized( $value ) ) {
- $value = unserialize( $value );
- }
+ // Security fix: Removed unsafe unserialize() call to prevent PHP Object Injection.
+ // WordPress's get_metadata() already handles deserialization safely.
+ // Any serialized strings at this point should be treated as untrusted user input.
if ( is_array( $value ) ) {
$string_value = [];
foreach ( $value as $key => $single_value ) {
- if ( is_array( $single_value ) || is_serialized( $single_value ) ) {
+ // Only recursively process arrays, not serialized strings
+ if ( is_array( $single_value ) ) {
$single_value = weforms_get_pain_text( $single_value );
}
--- a/weforms/security-tests/automated-test.php
+++ b/weforms/security-tests/automated-test.php
@@ -0,0 +1,362 @@
+<?php
+/**
+ * Automated Security Test for weForms Plugin
+ *
+ * This script tests the actual plugin code for unsafe deserialization
+ * vulnerabilities.
+ *
+ * Usage: php automated-test.php /path/to/weforms/plugin
+ *
+ * @package WeForms Security Tests
+ */
+
+// Color output for terminal
+class TestColors {
+ const RED = "