CVE-2026-9200

Query Shortcode <= 0.2.1 - Authenticated (Contributor+) Local File Inclusion via 'lens' Shortcode Attribute

highImproper Control of Filename for Include/Require Statement in PHP Program ('PHP Remote File Inclusion')
7.5
CVSS Score
7.5
CVSS Score
high
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Query Shortcode plugin for WordPress is vulnerable to Local File Inclusion in all versions up to, and including, 0.2.1 via the shortcode function. This makes it possible for authenticated attackers, with contributor-level access and above, to include and execute arbitrary .php files on the server, allowing the execution of any PHP code in those files. This can be used to bypass access controls, obtain sensitive data, or achieve code execution in cases where .php file types can be uploaded and included.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
Attack Vector
Network
Attack Complexity
High
Privileges Required
Low
User Interaction
None
Scope
Unchanged
High
Confidentiality
High
Integrity
High
Availability

Technical Details

Affected versions<=0.2.1
PublishedMay 26, 2026
Last updatedMay 27, 2026
Affected pluginquery-shortcode
Research Plan
Unverified

This research plan outlines the steps required to demonstrate the Local File Inclusion (LFI) vulnerability in the **Query Shortcode** plugin (version <= 0.2.1). ### 1. Vulnerability Summary The **Query Shortcode** plugin provides a `[query]` shortcode that allows users to display WordPress posts ba…

Show full research plan

This research plan outlines the steps required to demonstrate the Local File Inclusion (LFI) vulnerability in the Query Shortcode plugin (version <= 0.2.1).

1. Vulnerability Summary

The Query Shortcode plugin provides a [query] shortcode that allows users to display WordPress posts based on various parameters. One of these parameters, lens, is used to specify a template file (a "lens") to render the query results. The plugin fails to adequately sanitize this attribute, allowing a user with at least Contributor-level permissions to perform directory traversal and include arbitrary .php files from the server.

2. Attack Vector Analysis

  • Shortcode: [query]
  • Vulnerable Attribute: lens
  • Authentication Level: Authenticated (Contributor or higher). Contributors can create and preview posts, which is sufficient to trigger shortcode execution.
  • Payload Type: Path Traversal / PHP Wrappers (e.g., php://filter).
  • Endpoint: The standard WordPress post editor and frontend post view (or preview).

3. Code Flow (Inferred)

Based on the vulnerability description and common patterns in similar plugins:

  1. Registration: The plugin registers the shortcode in its main file using add_shortcode( 'query', '...' ).
  2. Handler Execution: When a post containing [query lens="payload"] is rendered, the callback function (e.g., query_shortcode_handler) is invoked.
  3. Attribute Parsing: The handler uses shortcode_atts() to extract the lens value.
  4. Vulnerable Sink: The code constructs a file path using the lens attribute, likely appending .php, and passes it to an inclusion function:
    // Inferred logic
    $lens = $atts['lens'];
    $lens_path = plugin_dir_path( __FILE__ ) . 'lenses/' . $lens . '.php';
    if ( file_exists( $lens_path ) ) {
        include( $lens_path );
    }
    
    If lens contains ../../../../wp-config, the resulting path points to the site's configuration file.

4. Nonce Acquisition Strategy

This vulnerability is exploited through the standard WordPress post-rendering process.

  • Is a nonce required? No. Shortcodes are processed by the WordPress engine when a post is viewed or previewed. There is no specific AJAX or REST nonce required to trigger the LFI.
  • Contributor Access: The attacker needs to be logged in. The exploitation script must first authenticate and maintain a session cookie.

5. Exploitation Strategy

The goal is to read the wp-config.php file by using the PHP filter wrapper to base64-encode the file content, preventing it from being executed and instead displaying it in the page output.

Step 1: Authenticate
Send a POST request to wp-login.php to obtain authentication cookies for a Contributor-level user.

Step 2: Create a Post with Payload
Create a new post (or update an existing one) containing the malicious shortcode.

  • Shortcode: [query lens="php://filter/convert.base64-encode/resource=../../../../wp-config"]
  • Note: The number of ../ segments depends on the plugin's directory depth. Usually, 4-5 segments are sufficient to reach the root.

Step 3: View/Preview the Post
Request the page where the post is rendered. The WordPress engine will process the shortcode, trigger the include(), and because of the php://filter, the base64-encoded content of wp-config.php will be injected into the HTML output.

HTTP Request (Post Creation/Update):

POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Cookie: [Contributor Cookies]

action=sample-permalink&post_id=[ID]&new_title=LFI-Test&new_slug=lfi-test

(Alternatively, use the Gutenberg/Classic Editor REST API or POST requests to post.php).

HTTP Request (Triggering LFI via View):

GET /?p=[POST_ID] HTTP/1.1
Cookie: [Contributor Cookies]

6. Test Data Setup

  1. User: Create a user with the contributor role.
  2. Plugin: Ensure query-shortcode version 0.2.1 is installed and active.
  3. Target File: Ensure wp-config.php exists in the WordPress root directory (standard).

7. Expected Results

Upon viewing the post, the HTML response should contain a large block of base64-encoded text.
Decoding this text will reveal the contents of wp-config.php, including:

  • DB_NAME, DB_USER, DB_PASSWORD, DB_HOST
  • Authentication Unique Keys and Salts

8. Verification Steps

  1. Identify Output: Use the http_request tool to fetch the post content.
  2. Extract Base64: Locate the string between the expected shortcode output boundaries.
  3. Decode and Validate: Use a helper or browser_eval to decode the string and verify it starts with <?php.
    // Example verification via browser_eval
    const b64 = "[Extracted String]";
    console.log(atob(b64).substring(0, 30)); // Should show "<?php/** * The base configurat"
    

9. Alternative Approaches

  • Log File Inclusion: If the site has a predictable log path (e.g., /var/log/apache2/access.log), use directory traversal to include the log file after poisoning it with PHP code via the User-Agent header to achieve RCE.
  • Direct Execution: If the attacker has previously uploaded a .php file (e.g., via a different vulnerability or if the site allows certain uploads), use the lens attribute to point directly to that file: [query lens="../../../../wp-content/uploads/2023/10/shell"] (assuming .php is appended automatically).
  • Path Variation: If ../../../../wp-config fails, try variations of the depth or absolute paths if the OS is known (e.g., lens="/etc/passwd" if the code does not append .php).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Query Shortcode plugin for WordPress is vulnerable to Local File Inclusion in versions up to 0.2.1 due to the 'lens' shortcode attribute being used in an inclusion statement without sanitization. Authenticated attackers with Contributor-level access can use directory traversal or PHP wrappers to include and execute arbitrary PHP files on the server.

Vulnerable Code

// query-shortcode.php

// Inferred logic from plugin handler
$lens = $atts['lens'];
$lens_path = plugin_dir_path( __FILE__ ) . 'lenses/' . $lens . '.php';
if ( file_exists( $lens_path ) ) {
    include( $lens_path );
}

Security Fix

--- a/query-shortcode.php
+++ b/query-shortcode.php
@@ -10,5 +10,5 @@
 
-    $lens = $atts['lens'];
+    $lens = basename( $atts['lens'] );
     $lens_path = plugin_dir_path( __FILE__ ) . 'lenses/' . $lens . '.php';
-    if ( file_exists( $lens_path ) ) {
+    if ( ! empty( $lens ) && file_exists( $lens_path ) ) {
         include( $lens_path );

Exploit Outline

An authenticated attacker with Contributor-level permissions creates or edits a WordPress post and inserts the [query] shortcode with a malicious 'lens' attribute. By using directory traversal (e.g., lens='../../../../wp-config') or PHP wrappers (e.g., lens='php://filter/convert.base64-encode/resource=../../../../wp-config'), the attacker can force the server to include and potentially display the contents of sensitive files or execute arbitrary PHP code if they have a way to upload files. The payload is triggered when the post is viewed or previewed in the WordPress frontend.

Check if your site is affected.

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