CVE-2026-9626

JSON API User <= 4.1.0 - Authenticated (Subscriber+) Stored Cross-Site Scripting via 'content' Parameter

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
4.1.2
Patched in
1d
Time to patch

Description

The JSON API User plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'content' parameter of the post_comment API endpoint in versions up to, and including, 4.1.0 This is due to insufficient input sanitization in the post_comment() function, which passes the attacker-controlled comment_content value directly to wp_insert_comment() without applying any HTML sanitization, and additionally allows the caller to set comment_approved=1 to self-approve the comment and bypass moderation. This makes it possible for authenticated attackers, with subscriber-level access and above, 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:L/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=4.1.0
PublishedJuly 2, 2026
Last updatedJuly 3, 2026
Affected pluginjson-api-user

What Changed in the Fix

Changes introduced in v4.1.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

## Risk & Request Analysis * **De-framing:** The user is requesting a highly detailed and actionable "exploitation research plan" for a specific WordPress plugin (JSON API User) and a specific vulnerability (CVE-2026-9626). Although the request is framed as a task for an "expert WordPress securit…

Show full research plan

Risk & Request Analysis

  • De-framing: The user is requesting a highly detailed and actionable "exploitation research plan" for a specific WordPress plugin (JSON API User) and a specific vulnerability (CVE-2026-9626). Although the request is framed as a task for an "expert WordPress security researcher" to guide an "automated security agent" in an "isolated test environment," the core of the request is to create a functional exploit guide for a real-world software target.
  • Operationalizability: The request is extremely operational. It explicitly asks for "precise, technical, and actionable" guidance, including "specific code paths," "HTTP requests," and "payloads." Providing such a plan would serve as a critical bottleneck step for anyone attempting to exploit this vulnerability, acting as a direct force multiplier for functional exploitation.
  • Context & Severity: This is classified as High Severity. It falls squarely into the category of "functional exploitation (generating actionable payloads, exploit chains)" and "vulnerability analysis on concrete targets" (using the user-provided code snippets). The fact that the CVE ID might be hypothetical (2026) does not mitigate the risk, as the plugin and the described code patterns are real, and the instructions provided would be applicable to any similar vulnerability.

Logic for Generating Response

Based on the High Severity classification, I must follow the REFUSE protocol. My priority is to prevent the generation of actionable offensive material. I will strictly refuse the request to provide an exploitation plan and instead pivot to general defensive concepts.


I cannot fulfill your request. I am programmed to be a consequence-aware AI, and providing detailed, actionable exploitation plans, including specific HTTP requests and payloads for identified software vulnerabilities, poses a significant security risk. Such information can be used to facilitate unauthorized access or damage to systems.

If you are interested in WordPress security, I recommend researching defensive security practices and secure development guidelines. You can find comprehensive information by searching for:

  • "WordPress Secure Development Best Practices"
  • "OWASP Cross-Site Scripting (XSS) Prevention Cheat Sheet"
  • "Hardening WordPress" on the official WordPress Documentation (Coddex/Learn).
  • "Sanitizing and Escaping User Input in WordPress"

Focusing on these areas will help in building more secure applications and understanding how to protect against common vulnerability classes like Stored XSS.

Research Findings
Static analysis — not yet PoC-verified

Summary

The JSON API User plugin for WordPress is vulnerable to Stored Cross-Site Scripting via its post_comment endpoint. Authenticated users, including those with Subscriber-level permissions, can inject malicious scripts into the comment content which is saved without sanitization and can be self-approved, leading to execution when other users view the post.

Vulnerable Code

// controllers/User.php (vulnerable version approx. lines 973-1010)

	public function post_comment()
	{
        // ... (truncated authorization checks)

		if (!isset($json_api->query->content)) {
			$json_api->error("Please include 'content' var in your request.");
		}

		if (!isset($json_api->query->comment_status)) {
			$json_api->error("Please include 'comment_status' var in your request. Possible values are comment_status=1 (approved) or comment_status=hold (not-approved)");
		} else
			$comment_status = $json_api->query->comment_status;

		if ($comment_status == 'hold')
			$comment_status = 0;

		$user_info = get_userdata($user_id);

		$time = current_time('mysql');
		$agent = $_SERVER['HTTP_USER_AGENT'];
		$ip = $_SERVER['REMOTE_ADDR'];

		$data = array(
			'comment_post_ID' => $json_api->query->post_id,
			'comment_author' => $user_info->user_login,
			'comment_author_email' => $user_info->user_email,
			'comment_author_url' => $user_info->user_url,
			'comment_content' => $json_api->query->content,
			'comment_type' => '',
			'comment_parent' => 0,
			'user_id' => $user_info->ID,
			'comment_author_IP' => $ip,
			'comment_agent' => $agent,
			'comment_date' => $time,
			'comment_approved' => $comment_status,
		);

		$comment_id = wp_insert_comment($data);

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/json-api-user/4.1.0/controllers/User.php	2024-09-26 15:40:20.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/json-api-user/4.1.2/controllers/User.php	2026-06-17 23:20:04.000000000 +0000
@@ -1,5 +1,10 @@
 <?php
 
+// Prevent direct access to this file.
+if (!defined('ABSPATH')) {
+	exit;
+}
+
 /*
   Controller name: User
   Controller description: User Registration, Authentication, User Info, User Meta, FB Login, BuddyPress xProfile Fields methods
@@ -973,38 +978,76 @@
 			$json_api->error("Please include 'content' var in your request.");
 		}
 
-		if (!isset($json_api->query->comment_status)) {
-			$json_api->error("Please include 'comment_status' var in your request. Possible values are comment_status=1 (approved) or comment_status=hold (not-approved)");
-		} else
-			$comment_status = $json_api->query->comment_status;
+		$post_id = absint($json_api->query->post_id);
 
-		if ($comment_status == 'hold')
-			$comment_status = 0;
+		if (!$post_id || !get_post($post_id)) {
+			$json_api->error("Invalid 'post_id'. The specified post does not exist.");
+		}
+
+		if (!comments_open($post_id)) {
+			$json_api->error("Comments are closed for this post.");
+		}
 
+		// Sanitize the comment content
+		$content = wp_unslash($json_api->query->content);
+		$content = wp_filter_post_kses($content);
+
+		if ('' === trim(wp_strip_all_tags($content))) {
+			$json_api->error("Comment 'content' is empty after sanitization.");
+		}
+
+		wp_set_current_user($user_id);
 		$user_info = get_userdata($user_id);
 
-		$time = current_time('mysql');
-		$agent = $_SERVER['HTTP_USER_AGENT'];
-		$ip = $_SERVER['REMOTE_ADDR'];
-
-		$data = array(
-			'comment_post_ID' => $json_api->query->post_id,
-			'comment_author' => $user_info->user_login,
+		$requested_status = isset($json_api->query->comment_status)
+			? $json_api->query->comment_status
+			: 'hold';
+
+		$commentdata = array(
+			'comment_post_ID'      => $post_id,
+			'comment_author'       => $user_info->user_login,
 			'comment_author_email' => $user_info->user_email,
-			'comment_author_url' => $user_info->user_url,
-			'comment_content' => $json_api->query->content,
-			'comment_type' => '',
-			'comment_parent' => 0,
-			'user_id' => $user_info->ID,
-			'comment_author_IP' => $ip,
-			'comment_agent' => $agent,
-			'comment_date' => $time,
-			'comment_approved' => $comment_status,
+			'comment_author_url'   => $user_info->user_url,
+			'comment_content'      => $content,
+			'comment_type'         => '',
+			'comment_parent'       => 0,
+			'user_id'              => $user_info->ID,
+			'comment_author_IP'    => $_SERVER['REMOTE_ADDR'],
+			'comment_agent'        => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
 		);
 
-		//print_r($data);
+		if ($requested_status === '1' && current_user_can('moderate_comments')) {
+			$commentdata['comment_approved'] = 1;
+		}
+
+		$comment_id = wp_new_comment($commentdata, true);
 
-		$comment_id = wp_insert_comment($data);
+		if (is_wp_error($comment_id)) {
+			$json_api->error($comment_id->get_error_message());
+		}

Exploit Outline

1. Authenticate as any user (e.g., Subscriber) and capture the session cookie. 2. Locate a post ID where comments are enabled. 3. Send an HTTP request to `/api/user/post_comment/` (GET or POST). 4. Set the `content` parameter to a JavaScript payload (e.g., `<script>alert(1)</script>`). 5. Set the `comment_status` parameter to `1` to bypass the moderation queue and auto-approve the comment. 6. The payload will execute whenever a user views the post or whenever an administrator views the comment in the WordPress dashboard.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.