SECURITY ADVISORY / 01

CVE-2026-60137 Exploit & Vulnerability Analysis

Complete CVE-2026-60137 security advisory with proof of concept (PoC), exploit details, and patch analysis for wordpress-develop.

wordpress-develop products NVD ↗
Exploit PoC Vulnerability Patch Analysis

The Exploit

The attacker does not require authentication; the vulnerability is exploitable from an unauthenticated state as long as the target site exposes a WP_Query instance through a publicly accessible endpoint that accepts the author__not_in parameter—common in custom REST API endpoints, theme shortcodes, or plugin AJAX handlers.

GET /wp-json/myplugin/v1/posts?author__not_in[]=1 UNION SELECT user_pass FROM wp_users-- HTTP/1.1
Host: target.wordpress.org
User-Agent: Mozilla/5.0
Accept: application/json

When the crafted request is processed, the site returns an HTTP 500 error rather than the expected JSON response. If error reporting is enabled, the response body may leak a MySQL error message containing the database type and version, confirming the SQL injection. Even without verbose error output, an attacker can detect the injection by observing a timing delay when substituting SLEEP(5) for the UNION component, indicating successful injection of SQL into the WHERE clause.

What the Patch Did

Before (vulnerable):

if ( ! empty( $query_vars['author__not_in'] ) ) {
    if ( is_array( $query_vars['author__not_in'] ) ) {
        $query_vars['author__not_in'] = array_unique( array_map( 'absint', $query_vars['author__not_in'] ) );
        sort( $query_vars['author__not_in'] );
    }
    $author__not_in = implode( ',', (array) $query_vars['author__not_in'] );
    $where         .= " AND {$wpdb->posts}.post_author NOT IN ($author__not_in) ";
}

After (fixed):

if ( ! empty( $query_vars['author__not_in'] ) ) {
    $author__not_in_id_list = wp_parse_id_list( $query_vars['author__not_in'] );
    if ( count( $author__not_in_id_list ) > 0 ) {
        sort( $author__not_in_id_list );
        $where .= sprintf(
            " AND {$wpdb->posts}.post_author NOT IN (%s) ",
            implode( ',', $author__not_in_id_list )
        );
        /** Update the query var for stable cache key generation in {@see self::generate_cache_key()}. */
        $query_vars['author__not_in'] = $author__not_in_id_list;
    }
}

The patch replaces the nested absint() mapping approach (which had a bypass when the author__not_in value was provided as a single non-array string) with the WordPress API function wp_parse_id_list(). This function parses a comma-separated string or array of values, casting each element to an integer and filtering out any non-numeric entries. The key addition is the use of this specialized ID-parsing function, which provides robust sanitization of array elements before they reach the SQL query.

Root Cause

This is a classic SQL Injection vulnerability (CWE-89). The dataflow begins when an attacker supplies the author__not_in parameter in a WP_Query request—either via GET/POST variables, REST API parameters, or query string arguments in a shortcode. The parameter reaches wp-includes/class-wp-query.php without any prior sanitization. In the vulnerable code, the is_array() check creates a false sense of security: if the attacker passes author__not_in as a simple string (e.g., 1 UNION SELECT), the is_array() branch is skipped entirely. The code then falls through to implode( ',', (array) $query_vars['author__not_in'] ), which casts the string payload 1 UNION SELECT user_pass FROM wp_users to an array containing that single string element, and injects it directly into the SQL query without any sanitization. The trust boundary is crossed when attacker-controlled string data reaches the $where concatenation without undergoing scalar-type enforcement or integer casting.

Why It Works

The single most load-bearing line added by the patch is $author__not_in_id_list = wp_parse_id_list( $query_vars['author__not_in'] );. If this line were removed while keeping the rest of the patch (the sprintf formatting and the count() check), the vulnerability would remain exploitable—because the raw $query_vars['author__not_in'] would still be imploded into the SQL. The wp_parse_id_list() function is the only barrier that forces every input element to be an integer, eliminating any possibility of injecting SQL keywords or operators. The count( $author__not_in_id_list ) > 0 check is a defensive guard that prevents building an empty IN () clause if all parsed values were invalid (e.g., if the attacker passed only non-numeric strings completely). The sort() call is maintained for cache-key stability, not security. The engineer's use of sprintf for the overall formatting is a readability improvement but does not contribute to security—the real sanitization happens inside wp_parse_id_list.

Hardening Checklist

  • Use wp_parse_id_list() for any ID-based query parameter that enters WP_Query or custom SQL, rather than rolling manual array_map( 'absint', ... ) logic—because absint fails when a non-array string bypasses the array check.
  • Apply sanitize_text_field() or absint() to all raw HTTP input (e.g., $_GET, $_POST, $request->get_param()) before passing it to WP_Query parameters that are expected to be arrays, using wp_unslash() to strip magic quotes first.
  • For custom SQL queries built from user input, always use $wpdb->prepare() with %d placeholder for integer values, never inject the concatenated result of implode().
  • Audit all custom REST API endpoints, shortcode callbacks, and AJAX handlers that accept author__not_in, post__in, post__not_in, author__in, or any similar array-type query variable—these are the most common entry points for bypassing WP_Query's internal sanitization.
  • Enable WP_DEBUG_DISPLAY only in development environments and WP_DEBUG_LOG for production to prevent verbose SQL errors from being displayed to attackers during blind injection attempts.

References

  • https://nvd.nist.gov/vuln/detail/CVE-2026-60137
  • https://www.wordfence.com/threat-intel/vulnerabilities/id/cve-2026-60137

Frequently asked questions about CVE-2026-60137

What is CVE-2026-60137?

CVE-2026-60137 is a security vulnerability identified in wordpress-develop. This security advisory provides detailed technical analysis of the vulnerability, exploit methodology, affected versions, and complete remediation guidance.

Is there a PoC (proof of concept) for CVE-2026-60137?

Yes. This writeup includes proof-of-concept details and a technical exploit breakdown for CVE-2026-60137. Review the analysis sections above for the PoC walkthrough and code examples.

How does CVE-2026-60137 get exploited?

The technical analysis section explains the vulnerability mechanics, attack vectors, and exploitation methodology affecting wordpress-develop. PatchLeaks publishes this information for defensive and educational purposes.

What products and versions are affected by CVE-2026-60137?

CVE-2026-60137 affects wordpress-develop. Check the affected-versions section of this advisory for specific version ranges, vulnerable configurations, and compatibility information.

How do I fix or patch CVE-2026-60137?

The patch analysis section provides guidance on updating to patched versions, applying workarounds, and implementing compensating controls for wordpress-develop.

What is the CVSS score for CVE-2026-60137?

The severity rating and CVSS scoring for CVE-2026-60137 affecting wordpress-develop is documented in the vulnerability details section. Refer to the NVD entry for the current authoritative score.