-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 NEFARIOUSPLAN-CANONICAL-V1 {"body_md":"The Everest Forms free plugin defines a filter hook called `everest_forms_process_filter`. Line 584 of `includes/class-evf-form-task.php` dispatches it. The Pro plugin's Calculation Addon attaches a callback named `process_filter()` to that hook. The callback concatenates every submitted form field value into a PHP arithmetic expression and hands the assembled string to `eval()`. Each field value, on the way in, passes through `sanitize_text_field()`.\n\nCVE-2026-3300 is what happens when the sanitizer's name encodes one destination and the code wires it into another.\n\n## The dispatch is in the free plugin. The eval() is in the Pro one.\n\nLine 584 of `class-evf-form-task.php`, in the free plugin shipped from `wordpress.org/plugins/everest-forms`, reads:\n\n```php\n// Process hooks/filter - this is where most addons should hook\n// because at this point we have completed all field validation and\n// formatted the data.\n$this->form_fields = apply_filters( 'everest_forms_process_filter', $this->form_fields, $entry, $this->form_data );\n$logger->notice( sprintf( 'Everest Form Process: %s', evf_print_r( $this->form_fields, true ) ) );\n```\n\nThe comment names the contract. By the time the dispatch runs, every submitted field has cleared validation and per-type formatting. Any addon that subscribes to `everest_forms_process_filter` receives the full validated set, gets a chance to transform it, and returns the transformed set into the submission pipeline.\n\nThe \"Complex Calculation\" feature lives in the commercial Everest Forms Pro bundle. WPEverest's Pro plugin registers a callback against this filter to evaluate formula fields at submission time. The callback's design problem is small and ordinary: turn a designer-authored formula like `{field_1} + {field_2}` and a runtime set of field values into a single computed number. The callback's implementation is the bug. NVD writes it down:\n\n> The Calculation Addon's `process_filter()` function concatenating user-submitted form field values into a PHP code string without proper escaping before passing it to `eval()`. The `sanitize_text_field()` function applied to input does not escape single quotes or other PHP code context characters.\n\nField values arrive as visitor-controlled strings. The callback substitutes them into a PHP expression string. The expression string is handed to `eval()`. The substitution is wrapped in single quotes to keep numeric-looking values inside PHP's string grammar. The visitor's escape opportunity is the one character `sanitize_text_field()` does not touch.\n\n## `sanitize_text_field()` strips tags. It does not escape PHP.\n\nWordPress core's `sanitize_text_field()` is enumerated in the developer reference. Its transformation list, in order:\n\n- Check for invalid UTF-8.\n- Convert a standalone `<` to its HTML entity.\n- Strip all HTML tags.\n- Remove line breaks, tabs, and extra whitespace.\n- Strip percent-encoded characters.\n\nEvery transformation on the list presupposes a destination that reads tags as instructions, line breaks as structure, or percent-encoded bytes as URL-encoded data. The function's docstring is explicit: \"Sanitizes a string from user input or from the database,\" for use where the data \"may be stored in a database, rendered in HTML, or used in queries.\" That sentence enumerates the destinations the function's author had in front of them when they wrote it.\n\nWhat is not on the list: the single quote. The double quote. The semicolon. The backtick. The parenthesis. The dollar sign. The backslash. Every character a PHP parser reads as a token boundary.\n\nThe PoC published as `HORKimhab/CVE-2026-3300` pays attention. Its `get_payload` helper builds:\n\n```python\ndef get_payload(command, payload_type=\"system\"):\n cmd_escaped = command.replace('\"', '\\\\\"').replace(\"'\", \"\\\\'\")\n if payload_type == \"system\":\n return f\"1'; system(\\\"{cmd_escaped}\\\"); echo 'PWNED'; //\"\n```\n\nEleven non-alphanumeric characters in the leading payload (`1'; system(\"`, etc.). Not one of them appears in `sanitize_text_field()`'s transformation table. The string the visitor sends is the string the addon hands to `eval()`. The `1` is the leading numeric that satisfies the formula's outer arithmetic. The `'` closes whatever single-quoted string context the Pro addon wrapped the value in. The `;` ends the assignment statement the addon's interpolation built. `system(\"...\")` is a fresh statement in the same `eval()` argument. The trailing `//` comments out whatever the addon was going to append after the field's closing quote.\n\nThe PoC's submission payload is correspondingly simple:\n\n```python\ndata = {\n \"everest_forms[form_id]\": form_id,\n f\"everest_forms[fields][{field_name}]\": payload,\n \"everest_forms[submit]\": \"1\",\n}\npost_data[\"action\"] = \"everest_forms_process_submission\"\nr = requests.post(target + \"/wp-admin/admin-ajax.php\", data=post_data, ...)\n```\n\n`admin-ajax.php` with `action=everest_forms_process_submission` is the standard unauthenticated submission endpoint. The PoC's `--field` flag advertises that the payload-bearing field can be any string-type field in the form: `text_field`, `email`, `url`, `text_1`. The CVE description confirms it: \"any string-type form field (text, email, URL, select, radio).\" Every string field the form designer added becomes a delivery channel for the same primitive.\n\n## WordPress has an escape function for every legal destination.\n\nWordPress's escape API is destination-typed. Every function's name encodes the parser it was written to defend against:\n\n- `esc_html()` for HTML element bodies.\n- `esc_attr()` for HTML attribute values.\n- `esc_url()` and `esc_url_raw()` for URLs, output and storage.\n- `esc_js()` for JavaScript string literals.\n- `esc_textarea()` for textarea content.\n- `esc_xml()` for XML.\n- `esc_sql()` for SQL literals.\n- `wp_kses()`, `wp_kses_post()`, `wp_kses_data()` for HTML with an allowlist.\n\nThe function the Calculation Addon needed is not on the list. There is no `esc_php()`. The omission is the platform's threat model written down. The WordPress core team, the people who wrote and maintain every escape primitive a plugin author can reach for, declined to ship a function that encodes user-supplied bytes for a PHP source-code sink. Their refusal to provide the primitive is the assertion that visitor bytes do not belong inside `eval()`'s argument under any circumstance.\n\nThe Pro plugin's Calculation Addon needed `esc_php()`. The platform's API refused to provide it. The addon reached for the function with the most generic-sounding name in the sanitization vicinity, `sanitize_text_field()`, and used it as if it covered the destination the platform's API consciously omits.\n\n`sanitize_text_field()` does not cover that destination. The platform's silence about how to escape for `eval()` is the warning that the destination is not supposed to exist.\n\n## The patch reads \"Fix: Security issue.\"\n\nWPEverest's 1.9.13 changelog entry, the one that closed CVE-2026-3300, reads in full:\n\n> Fix: Security issue.\n\nFour words. The NVD record for the same release names the addon by name, names the function by name, names the unsafe primitive by name (`eval()`), and names the misapplied sanitizer by name (`sanitize_text_field()`). The two descriptions cover the same change to the same file in the same release. The vendor wrote one. Wordfence's bug-bounty intake wrote the other. The bug-bounty intake names every part of the failure. The vendor names that there was one.\n\nThe wider disclosure record fills in the timing. The CVE was reserved on March 18, 2026, the day the patched release shipped. NVD published it on March 30. Wordfence credits the report to a researcher using the handle `h0xilo`. Active exploitation began on April 13, fourteen days after public disclosure, when whoever was watching the diff finished reading what `1.9.13` had changed and started spraying the patch's previous shape against the install base. Over the following month, Wordfence blocked 29,300 exploitation attempts. One source IP, `202.56.2.126`, accounts for 26,300 of them. The administrator account the campaign tries to register is named `diksimarina`, email `diksimarina@gmail.com`. One IP. One account name. One campaign.\n\nThe PoC at `HORKimhab/CVE-2026-3300` was published in June, two months into that campaign. Its README cites Wordfence's threat-intelligence post and a Hacker News writeup in the Credit section, then links a Sansec article about Magento Magecart skimming that does not mention Everest Forms or WordPress at all. The PoC ships with `scan`, `exploit`, and `reverse` modes, a `ThreadPoolExecutor` with a five-thread default, and a `--file targets.txt` flag for batch operation. The `targets.txt` in the repo carries three lab entries. The architecture is built around it carrying more.\n\n## The Shell Was Not The Sink, in the WordPress dialect.\n\nThe broadest class the catalog tracks names what the bug does. [Content Is Command](/patterns/content-is-command): the visitor's submitted bytes reach an interpreter (PHP) that reads them as instructions, because the boundary the addon thought it was holding lived in a sanitizer written for a different interpreter. The narrower class names how the boundary failed. The Shell Was Not The Sink is the catalog's standing exhibit collection for a sanitizer named for one parser and applied to another.\n\nThe catalog already carries this shape four times. [Cacti's `cacti_escapeshellarg`](/posts/cacti-escapeshellarg-no-shell) quote-wrapping for a POSIX shell that never runs in front of rrdtool's batch parser. [Next.js's `ESCAPE_REGEX`](/posts/nextjs-cve-2026-44581-escape-regex-was-for-json) written for JSON-string content inside a `