CVE-2026-24573

Visualizer: Tables and Charts Manager for WordPress < 4.0.0 - Authenticated (Contributor+) Stored Cross-Site Scripting

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

Description

The Visualizer: Tables and Charts Manager for WordPress plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to 4.0.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with contributor-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.0.0
PublishedMay 20, 2026
Last updatedMay 26, 2026
Affected pluginvisualizer

What Changed in the Fix

Changes introduced in v4.0.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-24573 (Visualizer Stored XSS) ## 1. Vulnerability Summary **CVE-2026-24573** is a Stored Cross-Site Scripting (XSS) vulnerability in the **Visualizer: Tables and Charts Manager for WordPress** plugin (< 4.0.0). The vulnerability stems from insufficient sanitiz…

Show full research plan

Exploitation Research Plan: CVE-2026-24573 (Visualizer Stored XSS)

1. Vulnerability Summary

CVE-2026-24573 is a Stored Cross-Site Scripting (XSS) vulnerability in the Visualizer: Tables and Charts Manager for WordPress plugin (< 4.0.0). The vulnerability stems from insufficient sanitization of chart configuration or data (labels, tooltips, or manual data entries) when saved by an authenticated user with Contributor+ permissions. These payloads are subsequently executed in the context of any user (including Administrators) viewing the page where the chart is rendered.

2. Attack Vector Analysis

  • Endpoint: WordPress REST API or AJAX. Based on Visualizer_Gutenberg_Block, the plugin registers REST endpoints via register_rest_endpoints.
  • Action: Likely a POST or PUT request to update chart settings or data.
  • Vulnerable Parameter: Chart titles, legend labels, or manual data cells.
  • Authentication: Authenticated, Contributor-level or higher.
  • Preconditions: The plugin must be active, and a contributor must have the ability to create or edit a chart (enabled by default in Gutenberg).

3. Code Flow

  1. Entry Point: Visualizer_Gutenberg_Block::register_rest_endpoints (registered in __construct via rest_api_init).
  2. Data Handling: User-provided chart data (manual edit feature added in 3.11.0) or settings are sent via the block editor.
  3. Storage: Data is stored as a Custom Post Type (visualizer) or in post meta.
  4. Sink: Visualizer_Gutenberg_Block::gutenberg_block_callback (and the underlying rendering engine) retrieves the chart configuration.
  5. Rendering: The frontend script (likely using Google Charts or ChartJS libraries as seen in block.js) renders the labels. If labels are not escaped before being passed to the JS library or if the library renders them as HTML, the XSS triggers.

4. Nonce Acquisition Strategy

The Gutenberg block editor enqueues scripts using visualizerLocalize. For REST API requests, WordPress uses the standard wp-api nonce.

  1. Identify Block Script: The script visualizer-gutenberg-block is localized with visualizerLocalize.
  2. Setup Page: Create a page as a Contributor containing the visualizer/chart block.
  3. Extraction:
    • Navigate to the WordPress dashboard as a Contributor.
    • Use browser_eval to extract the REST nonce provided by WordPress core for Gutenberg:
      window.wpApiSettings?.nonce
      
    • Additionally, check visualizerLocalize for specific URLs:
      window.visualizerLocalize?.chartEditUrl
      

5. Exploitation Strategy

The goal is to inject a script into a chart's configuration that executes when viewed.

Step 1: Create a Chart (Inferred REST/AJAX)

Visualizer typically uses a Custom Post Type. We will attempt to create a chart via the REST API.

  • URL: /wp-json/visualizer/v1/charts (inferred)
  • Method: POST
  • Headers: X-WP-Nonce: [NONCE]
  • Payload (JSON):
    {
      "title": "Test Chart <img src=x onerror=alert('XSS_TITLE')>",
      "type": "line",
      "data": [
        ["Label", "Value"],
        ["<img src=x onerror=alert('XSS_DATA')>", 10]
      ],
      "settings": {
        "legend": {"position": "top"}
      }
    }
    

Step 2: Alternative (AJAX)

If the REST API is restricted, use the AJAX endpoint identified in Visualizer_Gutenberg_Block.

  • URL: /wp-admin/admin-ajax.php
  • Action: visualizer-create-chart (found in createChart localized variable).
  • Parameters: action=visualizer-create-chart&...

Step 3: Trigger

  1. Embed the created chart ID into a public post using the shortcode: [visualizer id="[CHART_ID]"].
  2. Navigate to the post URL as an unauthenticated user or Admin.

6. Test Data Setup

  1. User: Create a user with the contributor role.
  2. Chart Creation: The contributor creates a new "Visualizer" chart.
  3. Shortcode Placement: Contributor creates a post/page and adds the Visualizer block or shortcode:
    wp post create --post_type=page --post_status=publish --post_content='[visualizer id="[ID]"]'
    

7. Expected Results

  • The chart configuration is saved successfully containing the HTML tags.
  • When the page is viewed, the browser attempts to render the malformed label/title.
  • An alert box with XSS_TITLE or XSS_DATA appears, confirming script execution.

8. Verification Steps

  1. Database Check: Verify the raw payload in the database.
    wp db query "SELECT post_title, post_content FROM wp_posts WHERE post_type='visualizer' ORDER BY ID DESC LIMIT 1;"
    
  2. HTML Inspection: Check the frontend source for the unescaped payload.
    http_request GET "http://localhost:8080/?p=[POST_ID]"
    # Look for: <img src=x onerror=...
    

9. Alternative Approaches

  • JSON Import: If manual editing is filtered, try the "Import from JSON" feature (common in Visualizer). The vulnerability might exist in the parser for imported files.
  • ChartJS vs. Google Charts: The plugin supports multiple libraries. If Google Charts escapes by default, try switching the library to chartjs (mentioned in CHANGELOG.md version 3.11.0) via the settings payload.
  • Attribute Injection: Instead of a full <script> tag, try breaking out of a JSON string in a script block if the plugin reflects settings into a var visualizerConfig = {...} block.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Visualizer plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) in versions up to 4.0.0. Authenticated attackers with Contributor-level access or higher can inject malicious scripts into chart settings or manual data entries, which are then executed in the context of any user viewing the chart.

Vulnerable Code

// classes/Visualizer/Gutenberg/Block.php:285
		register_rest_route(
			'visualizer/v' . VISUALIZER_REST_VERSION,
			'/update-chart',
			array(
				'methods'  => 'POST',
				'callback' => array( $this, 'update_chart_data' ),
				'args'     => array(
					'id' => array(
						'sanitize_callback' => 'absint',
					),
				),
				'permission_callback' => function () {
					return current_user_can( 'edit_posts' );
				},
			)
		);

---

// classes/Visualizer/Gutenberg/Block.php:655
	public function update_chart_data( $data ) {
		if ( ! current_user_can( 'edit_posts' ) ) {
			return false;
		}

		if ( $data['id'] && ! is_wp_error( $data['id'] ) ) {
			if ( get_post_type( $data['id'] ) !== Visualizer_Plugin::CPT_VISUALIZER ) {
				return new WP_Error( 'invalid_post_type', 'Invalid post type.' );
			}
			$chart_type = sanitize_text_field( $data['visualizer-chart-type'] );
			$source_type = sanitize_text_field( $data['visualizer-source'] );
			$default_data  = (int) $data['visualizer-default-data'];
			$series_data   = map_deep( $data['visualizer-series'], array( $this, 'sanitize_value' ) );
			$settings_data = map_deep( $data['visualizer-settings'], array( $this, 'sanitize_value' ) );

			update_post_meta( $data['id'], Visualizer_Plugin::CF_CHART_TYPE, $chart_type );
			update_post_meta( $data['id'], Visualizer_Plugin::CF_SOURCE, $source_type );
			update_post_meta( $data['id'], Visualizer_Plugin::CF_DEFAULT_DATA, $default_data );
			update_post_meta( $data['id'], Visualizer_Plugin::CF_SERIES, $series_data );
			update_post_meta( $data['id'], Visualizer_Plugin::CF_SETTINGS, $settings_data );

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/visualizer/3.11.15/classes/Visualizer/Gutenberg/Block.php	2026-03-04 16:13:28.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/visualizer/4.0.0/classes/Visualizer/Gutenberg/Block.php	2026-03-31 13:30:52.000000000 +0000
@@ -284,117 +284,6 @@
 		register_rest_route(
 			'visualizer/v' . VISUALIZER_REST_VERSION,
-			'/update-chart',
-			array(
-				'methods'  => 'POST',
-				'callback' => array( $this, 'update_chart_data' ),
-				'args'     => array(
-					'id' => array(
-						'sanitize_callback' => 'absint',
-					),
-				),
-				'permission_callback' => function () {
-					return current_user_can( 'edit_posts' );
-				},
-			)
-		);
-

Exploit Outline

An attacker with Contributor-level access or higher can exploit this vulnerability by submitting a malicious payload to the `/wp-json/visualizer/v1/update-chart` REST API endpoint. The payload should target chart metadata or data fields, such as `visualizer-settings` or `visualizer-series`. For example, a POST request with the chart ID and a JSON payload containing `{"visualizer-settings": {"title": "<script>alert('XSS')</script>"}}` can be sent using a valid WordPress REST API nonce. Once saved, the script will execute whenever an administrator or another user views the post or page where the specific chart is embedded.

Check if your site is affected.

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