A four-digit door: unthrottled OTP login and full account takeover in a WordPress phone-login plugin

One-time passwords are supposed to make authentication stronger. They only do that when three properties hold at once: the code is unpredictable, an attacker cannot request one for a victim at will, and the number of guesses is strictly bounded. Drop any one of those and an OTP stops being a second factor and becomes a short numeric password that the attacker gets to brute-force at leisure. The OTP Login With Phone Number, OTP Verification plugin for WordPress, in versions before 1.8.71, drops two of the three.

The defect

The plugin lets an unauthenticated visitor request a login code for any account, and it neither limits the number of verification attempts nor invalidates a code after a wrong guess. Put those together and the attack writes itself. An attacker asks for a login code tied to a target account, including an administrator. The server issues a short numeric OTP. The attacker then submits guesses. Because there is no attempt ceiling and no invalidation on failure, the same issued code can be tried against the entire keyspace until it matches. When it matches, the plugin logs the attacker in as the target.

The keyspace is the whole problem. A short numeric OTP has a small, fixed number of possibilities. Without throttling, walking that space is a matter of seconds to minutes over the network, and nothing in the flow raises an alarm or burns the code after the first miss.

Why the pieces matter individually

It is worth separating the failures, because a deployment might partially mitigate one and still be fully exposed.

Unauthenticated code issuance for arbitrary accounts means the attacker never needs to know or control the victim's phone. They name the account, the server mints a code, and the guessing begins. This is what turns a self-service convenience feature into a remote, pre-authentication takeover primitive.

No attempt limit means the single most important control against a small-keyspace secret is simply absent. Rate limiting and lockout exist precisely because short codes are guessable. Their absence converts a second factor into a trivially breakable one.

No invalidation after a wrong guess is the quiet multiplier. Even a system that reissues codes periodically stays exploitable if a specific issued code remains valid across unlimited failed attempts, because the attacker races the keyspace against the code lifetime and, with no ceiling, wins.

What an attacker gets

Full account takeover, up to and including administrator. On WordPress, an administrator takeover is effectively site takeover: plugin and theme editing, user creation, arbitrary PHP execution through the plugin editor, and persistence that survives a password reset if the attacker moves quickly. The blast radius is not "one user account." It is the whole site and whatever the site can reach.

How to tell if you are exposed

Check the installed plugin version against 1.8.71. If you are below it and the phone-login flow is enabled, treat every account as reachable, not just privileged ones, because the code issuance is unauthenticated. Then look at your logs. A burst of verification submissions against a single account, or a wave of code-issuance requests for many accounts, is the fingerprint of this attack. The absence of lockout events where you would expect them is itself a finding.

What to do today

Update the plugin to 1.8.71 or later. If you cannot update immediately, disable the OTP phone-login path until you can, because a login mechanism with no attempt ceiling is worse than no second factor at all. Independently, put a rate limit in front of the verification endpoint at the web server or WAF layer, keyed on both source and target account, and alert on repeated failures. After patching, force a credential and session reset for any account that shows a pattern of verification attempts, and review administrator accounts for changes made during the exposure window.

The general lesson

Every OTP implementation should be read against the same three questions: can an attacker request a code for someone else, is the number of guesses bounded, and does a wrong guess retire the code. We test login flows against all three rather than trusting that the presence of an OTP means the presence of a control. A code that can be requested for anyone and guessed forever is not multi-factor authentication. It is a four-digit door with the lock removed.

Reference

CELVEX Group tracks this issue in the scanner catalog and probes the verification endpoint for missing throttling and non-invalidation behavior, not merely the plugin version string. Refer to the vendor's advisory and changelog for the patched release details.