CVE-2026-2987

Simple Ajax Chat <= 20260217 - Unauthenticated Stored Cross-Site Scripting via 'c'

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

Description

The Simple Ajax Chat plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'c' parameter in versions up to, and including, 20260217 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:R/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=20260217
PublishedMarch 12, 2026
Last updatedMarch 12, 2026
Affected pluginsimple-ajax-chat

What Changed in the Fix

Changes introduced in v20260301

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This plan outlines the steps required to demonstrate an unauthenticated Stored Cross-Site Scripting (XSS) vulnerability in the **Simple Ajax Chat** plugin (CVE-2026-2987). ### 1. Vulnerability Summary The Simple Ajax Chat plugin fails to sanitize user-provided chat messages (parameter `c`) before s…

Show full research plan

This plan outlines the steps required to demonstrate an unauthenticated Stored Cross-Site Scripting (XSS) vulnerability in the Simple Ajax Chat plugin (CVE-2026-2987).

1. Vulnerability Summary

The Simple Ajax Chat plugin fails to sanitize user-provided chat messages (parameter c) before storing them in the database and fails to escape them when displaying the chat history. An unauthenticated attacker can submit a chat message containing malicious JavaScript. When any user (including administrators) views the chat box, the payload executes in their browser.

The vulnerability resides in the interaction between the chat submission handler (likely sac_shout_post or similar) and the retrieval handler sac_getData in simple-ajax-chat.php, which echoes raw database content.

2. Attack Vector Analysis

  • Endpoint: The site root index.php (or simple-ajax-chat-core.php) acting as an AJAX endpoint.
  • Action Trigger: GET or POST parameters sacSendChat=yes (for injection) and sacGetChat=yes (for execution).
  • Vulnerable Parameter: c (the chat message).
  • Authentication: None required (unauthenticated).
  • Preconditions: The chat box must be active on a page, and "Restrict chat to logged-in users" must be disabled (default).

3. Code Flow

  1. Injection Path:
    • A POST request is sent to /?sacSendChat=yes.
    • simple-ajax-chat.php processes the request via an init hook.
    • $sac_user_text is assigned from $_POST['c'] (Line 50).
    • The message is inserted into the $wpdb->prefix . 'ajax_chat' table in the text column (Logic in sac_shout_post, inferred from sac_create_table at Line 147).
  2. Execution Path:
    • A GET request is sent to /?sacGetChat=yes&sac_nonce_receive=[NONCE]&sac_lastID=0.
    • sac_getData($sac_lastID) is called via init hook (Line 185).
    • The function queries the database: SELECT * FROM ... ajax_chat WHERE id > ... (Line 167).
    • The message text is retrieved into $text (Line 174).
    • The text is concatenated into the $loop string without escaping: $loop = $id .'---'. $name .'---'. $text . ... (Line 177).
    • echo $loop; sends the raw payload to the browser (Line 183).
    • The plugin's JavaScript (resources/sac.php) receives the string and likely inserts it into the DOM using a method like .innerHTML.

4. Nonce Acquisition Strategy

The plugin uses nonces to protect both sending and receiving chat data. These must be obtained by visiting a page where the chat is rendered.

  • Shortcode: [sac_chat]
  • Execution Agent Strategy:
    1. Create a public page with the shortcode: wp post create --post_type=page --post_title="Chat" --post_status=publish --post_content='[sac_chat]'.
    2. Navigate to the new page using browser_navigate.
    3. Extract nonces using browser_eval.
  • JS Variables/Selectors:
    • Send Nonce: document.getElementById('sac_nonce_send')?.value or check window.sac_lan?.sac_nonce_send.
    • Receive Nonce: document.getElementById('sac_nonce_receive')?.value or check window.sac_lan?.sac_nonce_receive.
    • Note: The nonce action names are sac_nonce_send and sac_nonce_receive.

5. Exploitation Strategy

Step 1: Obtain Nonces
Send a GET request via the browser to the page containing [sac_chat] and extract the nonces from the hidden input fields.

Step 2: Inject Payload
Send a POST request to submit the malicious chat.

  • URL: http://<target>/index.php?sacSendChat=yes
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    n=Attacker&c=<img src=x onerror=alert(document.domain)>&u=http://example.com&sac_nonce_send=[EXTRACTED_SEND_NONCE]
    

Step 3: Verify Storage and Execution
Poll the chat data endpoint to confirm the payload is returned unescaped.

  • URL: http://<target>/index.php?sacGetChat=yes&sac_nonce_receive=[EXTRACTED_RECEIVE_NONCE]&sac_lastID=0
  • Expected Response: A string containing ---<img src=x onerror=alert(document.domain)>---.

6. Test Data Setup

  1. Plugin Activation: Ensure simple-ajax-chat is active.
  2. Configuration: Ensure "Restrict chat to logged-in users" is not checked in the SAC settings.
  3. Page Creation:
    wp post create --post_type=page --post_title="Chat Room" --post_status=publish --post_content='[sac_chat]'
    

7. Expected Results

  • The injection request should return a successful response (often the newly created chat ID or a partial string).
  • The retrieval request (sacGetChat) will return the raw HTML <img> tag.
  • In a real browser, the onerror event will trigger, executing alert(document.domain).

8. Verification Steps

After performing the HTTP requests, verify the database state using WP-CLI:

# Check the text column for the payload
wp db query "SELECT name, text FROM $(wp db prefix)ajax_chat ORDER BY id DESC LIMIT 1;"

Confirm the output shows the literal <img ...> string without HTML entity encoding.

9. Alternative Approaches

If the sac_nonce_send is not found in the HTML, check the enqueued JavaScript file resources/sac.php. Since this file is generated dynamically by PHP (Line 11), it may contain localized data or direct variable assignments. Use browser_eval("window") to inspect all global objects for properties containing "nonce".

If the payload <img ...> is blocked by a basic filter, try:

  • c=<svg/onload=alert(1)>
  • c=javascript:alert(1) (If the make_links function at resources/sac.php:203 processes the message body directly).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Simple Ajax Chat plugin for WordPress is vulnerable to unauthenticated Stored Cross-Site Scripting via the 'c' (chat message) parameter. The plugin fails to sanitize this input when storing it in the database and subsequently echoes the raw data in its AJAX retrieval handler, allowing arbitrary JavaScript to execute in the browser of any user viewing the chat box.

Vulnerable Code

// simple-ajax-chat.php:50
$sac_user_text = isset($_POST['c']) ? $_POST['c'] : '';

---

// simple-ajax-chat.php:174-183
					if (isset($query[$row]) && !empty($query[$row]) && is_array($query[$row])) {
						
						$id   = isset($query[$row]['id'])   ? $query[$row]['id']   : '';
						$time = isset($query[$row]['time']) ? $query[$row]['time'] : '';
						$name = isset($query[$row]['name']) ? $query[$row]['name'] : '';
						$text = isset($query[$row]['text']) ? $query[$row]['text'] : '';
						$url  = isset($query[$row]['url'])  ? $query[$row]['url']  : '';
						
						$time = sac_time_since($time);
						
						$loop = $id .'---'. $name .'---'. $text .'---'. $time .' '. esc_html__('ago', 'simple-ajax-chat') .'---'. $url .'---';
						
					}

---

// resources/sac.php:198-201
function make_links(s) {
	var url = s.replace(/[\'\"\<\>\%\{\}\|\^\`\(\)\[\]]/g, '');
	return '<a target="_blank" rel="noopener noreferrer" href="'+ url +'" class="sac-chat-link">&laquo;link&raquo;</a>';
};

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/simple-ajax-chat/20260225/readme.txt /home/deploy/wp-safety.org/data/plugin-versions/simple-ajax-chat/20260301/readme.txt
--- /home/deploy/wp-safety.org/data/plugin-versions/simple-ajax-chat/20260225/readme.txt	2026-02-25 20:07:24.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/simple-ajax-chat/20260301/readme.txt	2026-03-27 18:12:10.000000000 +0000
@@ -9,9 +9,9 @@
 Donate link: https://monzillamedia.com/donate.html
 Contributors: specialk
 Requires at least: 4.7
-Tested up to: 6.9
-Stable tag: 20260225
-Version:    20260225
+Tested up to: 7.0
+Stable tag: 20260301
+Version:    20260301
 Requires PHP: 5.6.20
 Text Domain: simple-ajax-chat
 Domain Path: /languages
@@ -380,10 +380,9 @@
 > 👉 [Pro version](https://plugin-planet.com/simple-ajax-chat-pro/) supports unlimited chats, user blocking/muting, advanced chat management, emojis, and much more!
 
 
-**20260225**
+**20260301**
 
-* Improves sanitization of malformed URLs
-* Adds `_blank` attribute to chat links
+* Fixes bug with chat messages appearing as links
 * Tests on WordPress 6.9 + 7.0 (beta)
 
 
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/simple-ajax-chat/20260225/resources/sac.php /home/deploy/wp-safety.org/data/plugin-versions/simple-ajax-chat/20260301/resources/sac.php
--- /home/deploy/wp-safety.org/data/plugin-versions/simple-ajax-chat/20260225/resources/sac.php	2026-02-25 20:07:24.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/simple-ajax-chat/20260301/resources/sac.php	2026-03-01 21:28:50.000000000 +0000
@@ -196,8 +196,9 @@
 
 // links
 function make_links(s) {
-	var url = s.replace(/[\'\"\<\>\%\{\}\|\^\`\(\)\[\]]/g, '');
-	return '<a target="_blank" rel="noopener noreferrer" href="'+ url +'" class="sac-chat-link">&laquo;link&raquo;</a>';
+	var re = /((http|https|ftp):\/\/[^\s\'\"\%]*)/gi;
+	var text = s.replace(re, '<a target="_blank" rel="noopener noreferrer" href="$1" class="sac-chat-link">&laquo;link&raquo;</a>');
+	return text;
 };
 
 // sound alerts
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/simple-ajax-chat/20260225/simple-ajax-chat.php /home/deploy/wp-safety.org/data/plugin-versions/simple-ajax-chat/20260301/simple-ajax-chat.php
--- /home/deploy/wp-safety.org/data/plugin-versions/simple-ajax-chat/20260225/simple-ajax-chat.php	2026-02-25 20:07:24.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/simple-ajax-chat/20260301/simple-ajax-chat.php	2026-03-27 18:12:10.000000000 +0000
@@ -9,9 +9,9 @@
 	Donate link: https://monzillamedia.com/donate.html
 	Contributors: specialk
 	Requires at least: 4.7
-	Tested up to: 6.9
-	Stable tag: 20260225
-	Version:    20260225
+	Tested up to: 7.0
+	Stable tag: 20260301
+	Version:    20260301
 	Requires PHP: 5.6.20
 	Text Domain: simple-ajax-chat
 	Domain Path: /languages
@@ -36,7 +36,7 @@
 if (!defined('ABSPATH')) exit;
 
 if (!defined('SIMPLE_AJAX_CHAT_WP_VERS'))   define('SIMPLE_AJAX_CHAT_WP_VERS',   '4.7');
-if (!defined('SIMPLE_AJAX_CHAT_VERSION'))   define('SIMPLE_AJAX_CHAT_VERSION',   '20260225');
+if (!defined('SIMPLE_AJAX_CHAT_VERSION'))   define('SIMPLE_AJAX_CHAT_VERSION',   '20260301');
 if (!defined('SIMPLE_AJAX_CHAT_NAME'))      define('SIMPLE_AJAX_CHAT_NAME',      'Simple Ajax Chat');
 if (!defined('SIMPLE_AJAX_CHAT_HOME'))      define('SIMPLE_AJAX_CHAT_HOME',      'https://perishablepress.com/simple-ajax-chat/');
 if (!defined('SIMPLE_AJAX_CHAT_FILE'))      define('SIMPLE_AJAX_CHAT_FILE',      __FILE__);

Exploit Outline

1. **Identify the Chat Page**: Locate a page on the target WordPress site where the `[sac_chat]` shortcode is embedded. 2. **Extract Nonces**: View the page source or use browser developer tools to find the `sac_nonce_send` (for posting) and `sac_nonce_receive` (for polling) nonces. These are usually in hidden input fields. 3. **Inject Stored XSS**: Send an unauthenticated HTTP POST request to `/?sacSendChat=yes` with the following parameters: - `n`: Any display name - `c`: The XSS payload (e.g., `<img src=x onerror=alert(document.domain)>`) - `u`: An optional URL - `sac_nonce_send`: The extracted send nonce 4. **Trigger Execution**: When any user (including administrators) views the chat page, the plugin's frontend JavaScript fetches new messages via `sac_getData`. The server returns the raw payload, which is then dynamically inserted into the DOM (typically using `.innerHTML`), causing the browser to execute the malicious script.

Check if your site is affected.

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