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().
CVE-2026-3300 is what happens when the sanitizer's name encodes one destination and the code wires it into another.
The dispatch is in the free plugin. The eval() is in the Pro one.
Line 584 of class-evf-form-task.php, in the free plugin shipped from wordpress.org/plugins/everest-forms, reads:
// Process hooks/filter - this is where most addons should hook
// because at this point we have completed all field validation and
// formatted the data.
$this->form_fields = apply_filters( 'everest_forms_process_filter', $this->form_fields, $entry, $this->form_data );
$logger->notice( sprintf( 'Everest Form Process: %s', evf_print_r( $this->form_fields, true ) ) );
The 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.
The "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:
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.
Field 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.
sanitize_text_field() strips tags. It does not escape PHP.
WordPress core's sanitize_text_field() is enumerated in the developer reference. Its transformation list, in order:
- Check for invalid UTF-8.
- Convert a standalone
< to its HTML entity.
- Strip all HTML tags.
- Remove line breaks, tabs, and extra whitespace.
- Strip percent-encoded characters.
Every 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.
What 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.
The PoC published as HORKimhab/CVE-2026-3300 pays attention. Its get_payload helper builds:
def get_payload(command, payload_type="system"):
cmd_escaped = command.replace('"', '\\"').replace("'", "\\'")
if payload_type == "system":
return f"1'; system(\"{cmd_escaped}\"); echo 'PWNED'; //"
Eleven 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.
The PoC's submission payload is correspondingly simple:
data = {
"everest_forms[form_id]": form_id,
f"everest_forms[fields][{field_name}]": payload,
"everest_forms[submit]": "1",
}
post_data["action"] = "everest_forms_process_submission"
r = requests.post(target + "/wp-admin/admin-ajax.php", data=post_data, ...)
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.
WordPress has an escape function for every legal destination.
WordPress's escape API is destination-typed. Every function's name encodes the parser it was written to defend against:
esc_html() for HTML element bodies.
esc_attr() for HTML attribute values.
esc_url() and esc_url_raw() for URLs, output and storage.
esc_js() for JavaScript string literals.
esc_textarea() for textarea content.
esc_xml() for XML.
esc_sql() for SQL literals.
wp_kses(), wp_kses_post(), wp_kses_data() for HTML with an allowlist.
The 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.
The 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.
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.
The patch reads "Fix: Security issue."
WPEverest's 1.9.13 changelog entry, the one that closed CVE-2026-3300, reads in full:
Fix: Security issue.
Four 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.
The 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.
The 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.
The Shell Was Not The Sink, in the WordPress dialect.
The broadest class the catalog tracks names what the bug does. 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.
The catalog already carries this shape four times. Cacti's cacti_escapeshellarg quote-wrapping for a POSIX shell that never runs in front of rrdtool's batch parser. Next.js's ESCAPE_REGEX written for JSON-string content inside a <script> body and reused as the gate on a value going into an HTML attribute. exiftool-vendored's htmlEncode correctly applied to tag values flowing into ExifTool's stdin reader and never applied to the keys. Open WebUI's sanitizeResponseContent written to scrub a streaming-LLM render path and reused as the only filter between attacker text and a browser URI-scheme parser.
Everest Forms Pro is the same shape at a WordPress filter callback. sanitize_text_field() was written for the destination its name encodes: HTML output and stored text. The Pro addon's process_filter() routed the same function into a destination it does not know about. The destination has its own metacharacter alphabet, and not a single character in that alphabet appears in the sanitizer's transformation table.
The Calculation Addon's design did not need to be unsafe. The formula evaluation could have parsed the formula as an expression tree and walked the tree with field values as already-typed numeric leaves, the way every other arithmetic calculator written in the last twenty years works. The choice to interpolate strings into PHP source and eval() the result is the unforced primary mistake. The choice of sanitize_text_field() as the safety belt is the smaller, derivative mistake that lets the larger one keep its job.
The patch closes CVE-2026-3300. The pattern it exploits is what every other addon author sees when they read WordPress's escape API and notice the gap.
Coverage stack