Elementor Ally plugin SQL injection (CVE-2026-2413) leaves 250k+ WordPress sites exposed
Unauthenticated time-based SQLi in the Ally accessibility plugin (up to 4.0.3) is fixed in 4.1.0, but about 64% of 400k sites hadn't updated. Patch and check.

Elementor's Ally accessibility plugin, installed on more than 400,000 WordPress sites, had an unauthenticated SQL injection flaw in versions up to 4.0.3. A fix shipped in February, but weeks later only about a third of sites had applied it. The bug comes from a common mistake: treating a URL-sanitizing function as if it were SQL escaping. Anyone who writes PHP against $wpdb should learn from it.
What happened
Drew Webber (mcdruid), an offensive security engineer at Acquia, reported the flaw through Wordfence's bug bounty program, and Wordfence validated it. It's tracked as CVE-2026-2413 (CVSS 3.1 score 7.5, High; CWE-89). Some earlier reports gave the number as CVE-2026-2313, which is wrong. The timeline:
- 13 February 2026. Vendor notified.
- 23 February 2026. Ally 4.1.0 released with the fix.
- 11 March 2026. CVE published. Webber received an $800 bounty.
By then WordPress.org data showed about 36% of Ally installations on 4.1.0, which left more than 250,000 sites running vulnerable versions.
Who is affected
- Sites running Ally - Web Accessibility & Usability (WordPress.org slug
pojo-accessibility) version 4.0.3 or earlier. - Exploitation requires the plugin to be connected to an Elementor account with the Remediation module active. Sites that installed Ally but never connected it aren't exploitable through this path.
- Agencies and hosts that manage many WordPress installs, where plugin updates often wait for scheduled maintenance.
What it means for developers and engineering teams
The bug is in get_global_remediations(). A URL value supplied by the user went through esc_url_raw() and was then concatenated into a SQL JOIN clause. esc_url_raw() makes a string safe to use as a URL. It doesn't neutralise quotes or other SQL metacharacters in a way that makes the string safe inside a query. An attacker could therefore extend the query and use time-based blind techniques, such as conditional SLEEP() calls, to extract data one bit at a time. That includes user records and password hashes stored in the database.
Two details make this bug worse than its CVSS score suggests. First, the injection point is reachable without logging in, so automated scanners can find it at scale once a proof of concept circulates. Second, time-based blind extraction is slow but reliable. It needs no error messages or visible output, so hardened error handling doesn't stop it. A WAF that rate-limits or flags long-running requests helps, but it doesn't replace the patch.
The rule is simple: sanitise for the context where the value is used. In WordPress, anything that reaches SQL goes through $wpdb->prepare() with placeholders, wherever else it has been cleaned:
// Vulnerable pattern
$url = esc_url_raw( $_GET['url'] );
$sql = "SELECT ... JOIN {$table} r ON r.url = '{$url}'";
// Safe pattern
$url = esc_url_raw( wp_unslash( $_GET['url'] ) );
$sql = $wpdb->prepare(
"SELECT ... JOIN {$table} r ON r.url = %s",
$url
);
Static analysis catches this class of bug well. PHPCS with the WordPress Coding Standards flags unprepared queries (WordPress.DB.PreparedSQL), and it's worth running in CI for any plugin or theme you maintain.
What to do now
- Update Ally to 4.1.0 or later. With WP-CLI:
wp plugin list --name=pojo-accessibility --fields=name,status,version wp plugin update pojo-accessibility - Update WordPress core. Version 6.9.2, released around the same time, fixed 10 vulnerabilities including XSS, an authorization bypass and SSRF.
- Turn on automatic updates for low-risk plugins like accessibility widgets, or put managed sites on a weekly patch cadence.
- Check for signs of exploitation: unusually slow requests carrying the remediation URL parameter in access logs, and new or changed admin accounts.
- Rotate admin passwords and salts if you ran a vulnerable, connected version on a public site.
- Add
$wpdb->prepare()checks to code review and CI for your own plugins, for example withphpcs --standard=WordPress. - Audit other plugins that take URLs as input. Accessibility, SEO and redirect plugins all handle page URLs, and the same
esc_url_raw()-then-concatenate mistake can turn up anywhere.


