WordPress plugin · High, 8.6 of 10 · no patch
The 2FA plugin that answered questions about your password hashes
A plugin built to add a second factor to WordPress logins took a value from an anonymous request, cleaned it as an email address, and pasted it into a SQL query. Cleaning an email address does not make it safe for SQL. Anyone, with no account, could ask the site’s users table yes-or-no questions, and the password hashes live in that table. The plugin had not been updated since 2018. It is public as CVE-2026-89213, scored 8.6 of 10 by WPScan, and credited to RIA Labs.
Read this part first
If your site uses a plugin that adds a second factor to WordPress logins through an outside service, read the advisory. It names the plugin and the affected versions. As of 9 October 2026 there was no patch, and the plugin’s wordpress.org listing had been closed since 15 September 2026, pending a full review. A closed listing does not remove the plugin from a site that already has it. Deactivate it; there is no update to wait for.
If it was active, assume that at least the password hashes of every account that used its two-factor login were readable, and have those accounts change their passwords. Deactivating it also removes their second factor, so put a maintained one in its place.
The rest of this is about the pattern. There is no exploit code below, and none of the names that would make one easy to write. On 9 November 2026 WPScan plans to show the proof of concept we sent them. It will never appear here.
How bad it is
8.6 of 10, WPScan’s score: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N. Anyone on the internet, no account, no click. It reads rather than writes, so I:N and A:N. What it reads is the table that holds every account on the site, and the password hashes are in it.
One condition, ours rather than the publisher’s: the yes-or-no signal appears when the row being matched belongs to an account that uses the plugin’s own two-factor login. On a site running this plugin, that is the point of installing it.
WPScan scored it higher than we did. Our report said 7.5, with scope unchanged. WPScan scored a scope change, which is how it reaches 8.6. Theirs is the number of record, and we print it.
An email cleaner is not a SQL escaper
WordPress has a function that cleans email addresses. It keeps the characters an email address may legally contain, and the standard allows a few that mean something to SQL. That is correct for an email field. It is the wrong tool for a query.
The plugin took a login value from an anonymous request, cleaned it as an email address, and joined it into a query as text, with no prepared statement and no SQL escaping. The same code appeared twice, copied byte for byte, behind two handlers that answer requests from anyone.
The handler never printed what the query found. It did not need to. It answered in one of two shapes depending on what the lookup matched, and one bit per request, asked enough times, could spell out a column.
How we know
A description is not a finding. The engine built a disposable WordPress site with the plugin’s official 0.1.4 release, verified by hash, and turned on the database’s query log. A request for an address that did not exist came back as no match: the control. A crafted request then ran exactly the SQL we predicted, and the query log shows it. The response changed shape depending on whether a condition about a lab account’s password hash was true or false. The vendor’s own service was never contacted, and no one else’s site was touched.
The first attempt failed. The test made a wrong assumption about how WordPress stores password hashes, so a true answer read as false. The query log showed why, and the second run worked. A lab that only runs once would have filed a real bug as a false alarm. Before reporting it, we checked four vulnerability databases: no earlier record for this plugin on any of them.
Three questions for your own plugins
None of this is specific to one plugin. Any code that builds a query from something a stranger sent is worth asking these about.
- Is any value cleaned for one job reused for another? A sanitizer makes data fit a format. It does not make it safe for SQL, HTML or a shell, and each of those needs its own handling.
- Does every query that touches user input use a prepared statement? If a query is built by joining strings, it is one missed escape away from this post.
- What does a harmless-looking difference in responses tell a stranger? A reply that changes shape based on a database lookup is an answer to a question, even if it never prints the data.
None of it needs exotic tooling. It needs someone to read the query, not just the sanitizer in front of it.
What it proves, and what it doesn’t
It proves the engine does not skip a plugin for being small. This one had about ten installs and still got a hash-verified lab with a query log.
It does not prove we pulled a full hash out of a database, or cracked one. The lab confirmed the yes-or-no answer about a lab account’s hash; repeating it is a matter of time, and we did not run it to the end. In the lab the plugin could not reach its vendor’s service, and we did not test how a site that can reach it answers. And it does not say how many sites still run the plugin: wordpress.org listed about ten installs when we tested in August, and the listing is now closed.
If you ship a plugin, or run a site full of them, we’ll read it. Nominate a target and the first scan costs nothing.
Published 9 October 2026. CVE-2026-89213 · CVSS 3.1 base score 8.6 · CWE-89 (SQL Injection) · credited to RIA Labs, scored and published by WPScan. Status as of 9 October 2026. Corrections, or a plugin you maintain and want looked at: info@ria-labs.com.
See what a scan turns up
Nominate a target. The first scan costs nothing, and we’ll walk you through it.