{"repro_id":"REPRO-2026-00252","version":6,"title":"Divi Form Builder WordPress plugin allows unauthenticated arbitrary file upload via user-controlled acceptFileTypes, leading to RCE by uploading executable PHP extensions and accessing them in /wp-content/uploads/de_fb_uploads/.","repro_type":"security","status":"published","severity":"critical","description":"The Divi Form Builder plugin for WordPress contains an arbitrary file upload vulnerability in `do_image_upload()` where the `acceptFileTypes` POST parameter is directly interpolated into a regex. Attackers can supply executable PHP extensions (e.g., `.phtml`, `.phar`, `.php5`, `.php7`) to bypass `.htaccess` protections that only block `.php`, resulting in remote code execution. On Nginx-based servers, `.htaccess` is ignored, making protection ineffective. Unauthenticated attackers can obtain a nonce from any public page containing a form and upload PHP files to `/wp-content/uploads/de_fb_uploads/` for execution.","root_cause":"## Summary\nDivi Form Builder for WordPress exposes its file-upload handler through WordPress AJAX action `de_fb_image_upload`, which is registered to the PHP method `do_image_upload()` for unauthenticated callers. In the original Divi Form Builder v5.1.2 build used in this run, the handler reads the attacker-controlled POST parameter `acceptFileTypes` and interpolates it into the server-side upload validation regex. An unauthenticated attacker who obtains a public form nonce can set `acceptFileTypes=phtml|phar|php5|php7`, upload a PHP-executable file such as `.phtml` into `/wp-content/uploads/de_fb_uploads/`, and then request that file over HTTP to execute PHP code under the web-server user.\n\n## Impact\n- **Package/component affected:** Divi Form Builder WordPress plugin (`wordpress:divi-form-builder`), specifically `includes/classes/init.class.php::do_image_upload()` and the upload storage handled by `includes/de_fb_file_upload.php`.\n- **Affected versions:** The ticket claims all versions up to and including 5.1.8. This run reproduced the issue against an original Divi Form Builder v5.1.2 plugin archive (`sha256=85bf508b12f6d6152f06f8c5eb3143963c3446a7338e90e1de180f5da8a51c34`).\n- **Risk level and consequences:** Critical. The vulnerable path is reachable by unauthenticated HTTP requests with a valid public form nonce. Successful exploitation allows arbitrary PHP payload upload and web request execution as the Apache/PHP user (`www-data` in this reproduction), which is remote code execution.\n\n## Impact Parity\n- **Disclosed/claimed maximum impact:** Remote code execution via unauthenticated arbitrary file upload.\n- **Reproduced impact from this run:** Full PHP code execution through the live WordPress/Apache product path. The uploaded `.phtml` payload executed and returned `uid=33(www-data) gid=33(www-data) groups=33(www-data)` from `system('id')`.\n- **Parity:** `full`.\n- **Not demonstrated:** No post-exploitation persistence or privilege escalation beyond the web-server user was attempted; this is not needed to prove the claimed RCE impact.\n\n## Root Cause\nThe original plugin registers the public AJAX upload action to `do_image_upload()`:\n\n- `includes/classes/init.class.php` registers `wp_ajax_de_fb_image_upload` and `wp_ajax_nopriv_de_fb_image_upload` to `do_image_upload()`.\n- `do_image_upload()` reads `$_POST['acceptFileTypes']` and constructs upload validation options as `'/\\.(' . $accept_file_types . ')$/i'`.\n- `includes/de_fb_file_upload.php` validates the uploaded filename with `preg_match($this->options['accept_file_types'], $file->name)`, then writes the file to `wp-content/uploads/de_fb_uploads/` and returns a public URL.\n\nBecause the extension allow-list comes from the request, the attacker controls the server-side validation pattern. Supplying `phtml|phar|php5|php7` makes `.phtml` pass validation. In the tested Apache/PHP environment, `.phtml` is handled by PHP, so requesting `/wp-content/uploads/de_fb_uploads/payload.phtml` executes the attacker-controlled payload. The plugin’s upload-directory protection is insufficient for this class of extension bypass and is also server dependent.\n\nNo upstream fixed commit was provided in the ticket. For the negative control, `reproduction_steps.sh` applies a line-accurate local hardening patch over the same original plugin tree so that `do_image_upload()` rejects executable extension values before constructing the upload handler options. The patched control returns `{\"success\":false,\"data\":{\"message\":\"Executable file types are not allowed.\"}}` and the corresponding uploaded URL is absent/404.\n\n## Reproduction Steps\n1. Run `bundle/repro/reproduction_steps.sh` from the bundle root (or set `PRUVA_ROOT` to the bundle path).\n2. The script installs/starts a real WordPress stack with Apache, mod_php, and MariaDB, installs the original Divi Form Builder v5.1.2 plugin archive, creates a public page containing a form nonce, and sends unauthenticated multipart POST requests to `/wp-admin/admin-ajax.php` with `action=de_fb_image_upload`, `fb_nonce`, attacker-controlled `acceptFileTypes`, and a `.phtml` payload.\n3. It performs two vulnerable attempts and then swaps in a patched copy of the same original plugin tree for two fixed negative-control attempts.\n4. Expected evidence: vulnerable attempts return an upload JSON object containing `/wp-content/uploads/de_fb_uploads/payload_vuln_*.phtml`; subsequent GET requests to those URLs execute PHP and return the marker plus `id` output. Fixed attempts return the JSON rejection message and the payload URL returns 404/no marker.\n\n## Evidence\n- Main run log: `bundle/logs/reproduction_steps.log`\n- Runtime manifest: `bundle/repro/runtime_manifest.json`\n- Original source identity: `bundle/logs/source_identity.txt`\n- Vulnerable attempt artifacts:\n  - `bundle/logs/vuln_attempt_1/upload_response.json`\n  - `bundle/logs/vuln_attempt_1/execution_response.txt`\n  - `bundle/logs/vuln_attempt_1/result.json`\n  - `bundle/logs/vuln_attempt_2/upload_response.json`\n  - `bundle/logs/vuln_attempt_2/execution_response.txt`\n  - `bundle/logs/vuln_attempt_2/result.json`\n- Fixed negative-control artifacts:\n  - `bundle/logs/fixed_attempt_1/upload_response.json`\n  - `bundle/logs/fixed_attempt_1/execution_response.txt`\n  - `bundle/logs/fixed_attempt_1/result.json`\n  - `bundle/logs/fixed_attempt_2/upload_response.json`\n  - `bundle/logs/fixed_attempt_2/execution_response.txt`\n  - `bundle/logs/fixed_attempt_2/result.json`\n- Patch used for negative control: `bundle/logs/fixed_patch.diff`\n- HTTP server evidence: `bundle/logs/apache_access.log`, `bundle/logs/apache_error.log`\n\nKey vulnerable evidence excerpt from `bundle/logs/vuln_attempt_1/result.json`:\n\n```json\n{\n  \"upload_http_code\": \"200\",\n  \"execution_http_code\": \"200\",\n  \"uploaded_or_accepted\": true,\n  \"rce_observed\": true,\n  \"execution_response_excerpt\": \"PRUVA_DFB_RCE_MARKER=PRUVA_CVE_2026_5524_vuln_1\\nPHP_EXECUTED_AS=www-data\\nCMD_OUTPUT_BEGIN\\nuid=33(www-data) gid=33(www-data) groups=33(www-data)\\n\\nCMD_OUTPUT_END\\n\"\n}\n```\n\nKey fixed-control evidence excerpt from `bundle/logs/fixed_attempt_1/result.json`:\n\n```json\n{\n  \"upload_http_code\": \"200\",\n  \"execution_http_code\": \"404\",\n  \"uploaded_or_accepted\": false,\n  \"rce_observed\": false,\n  \"upload_response_excerpt\": \"{\\\"success\\\":false,\\\"data\\\":{\\\"message\\\":\\\"Executable file types are not allowed.\\\"}}\",\n  \"server_marker\": \"(no marker)\"\n}\n```\n\nThe runtime manifest confirms the API surface was exercised:\n\n```json\n{\n  \"entrypoint_kind\": \"api_remote\",\n  \"service_started\": true,\n  \"healthcheck_passed\": true,\n  \"target_path_reached\": true\n}\n```\n\n## Recommendations / Next Steps\n- Do not accept a file-extension allow-list from the client. Treat UI-supplied accepted file types as advisory only; server-side validation must use a trusted form configuration stored server-side.\n- Explicitly reject all executable PHP extensions and other server-executable types regardless of the client-supplied field configuration.\n- Store uploads in a non-executable location, or serve them through a controlled download endpoint rather than directly from the web root.\n- Harden web-server configuration for upload directories on Apache and Nginx; do not rely only on `.htaccess` rules because Nginx ignores them and extension mappings differ by server.\n- Add regression tests that POST to `admin-ajax.php?action=de_fb_image_upload` with executable extensions (`.php`, `.phtml`, `.phar`, `.php5`, `.php7`) and assert that all are rejected and no public executable file is written.\n- Upgrade to a vendor-fixed release once available and verify that the fix covers both validation and upload-directory execution controls.\n\n## Additional Notes\n- The script was run successfully twice consecutively after correcting setup and Apache cache behavior.\n- The previous rejected surrogate-plugin approach was replaced with an original Divi Form Builder v5.1.2 plugin archive and the real WordPress AJAX endpoint.\n- The AJAX action name is `de_fb_image_upload`; the original product maps that action to the requested `do_image_upload()` method through WordPress `wp_ajax_*` hooks.\n- The negative control is a local patch over the same original project path because the ticket did not provide a fixed commit hash or official patched archive.\n","cve_id":"CVE-2026-5524","cwe_id":"CWE-434 - Unrestricted Upload of File with Dangerous Type","source_url":"https://nvd.nist.gov/vuln/detail/CVE-2026-5524","package":{"name":"divi-form-builder","ecosystem":"wordpress","affected_versions":"All versions up to and including 5.1.8"},"reproduced_at":"2026-07-06T09:02:32.306197+00:00","duration_secs":1235.0,"tool_calls":221,"handoffs":3,"total_cost_usd":7.434466,"agent_costs":{"judge":0.0938594,"repro":7.27535867,"support":0.06524793000000001},"cost_breakdown":{"judge":{"gpt-5.4-mini":0.0938594},"repro":{"accounts/fireworks/routers/glm-5p2-fast":1.13268867,"gpt-5.5":6.142670000000002},"support":{"accounts/fireworks/routers/glm-5p2-fast":0.06524793000000001}},"quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-06T09:02:49.332749+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":24056,"category":"reproduction_script"},{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":8284,"category":"analysis"},{"path":"bundle/artifact_promotion_manifest.json","filename":"artifact_promotion_manifest.json","size":3979,"category":"other"},{"path":"bundle/logs/vuln/execution_response.txt","filename":"execution_response.txt","size":106,"category":"other"},{"path":"bundle/logs/vuln/htaccess.txt","filename":"htaccess.txt","size":97,"category":"other"},{"path":"bundle/logs/fixed/upload_response.txt","filename":"upload_response.txt","size":132,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":1506,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":799,"category":"other"},{"path":"bundle/logs/vuln/marker_file.txt","filename":"marker_file.txt","size":47,"category":"other"},{"path":"bundle/logs/vuln_attempt_1/result.json","filename":"result.json","size":882,"category":"other"},{"path":"bundle/logs/fixed_attempt_1/result.json","filename":"result.json","size":738,"category":"other"}]}