Automated Threat Modeling

Brief Summary: SureCart <= 4.9.5 Unauthenticated Account Takeover (CVE-2026-120574)

A brief summary of CVE-2026-120574, an unauthenticated account takeover in the SureCart WordPress plugin through version 4.9.5, with a review of the 4.9.6 patch internals.

CVE Analysis

9 min read

Gaurav Sarraf
Gaurav Sarraf

2026-11-03

Brief Summary: SureCart <= 4.9.5 Unauthenticated Account Takeover (CVE-2026-120574)
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

A passwordless login feature in the SureCart WordPress plugin, built for convenience, gave unauthenticated attackers a path to administrator takeover. An attacker who never had any credentials could request a one-time code for an admin account and brute-force the 6-digit value until they held a valid session cookie. For the 80,000+ sites running an affected build of this ecommerce plugin, the login endpoint meant to make customer sign-in easier let attackers into the most privileged accounts.

SureCart is an ecommerce, payments, and subscriptions plugin for WordPress that supports physical goods, digital products, services, and subscriptions without requiring WooCommerce or Easy Digital Downloads. It has two components: the plugin itself and a hosted SureCart platform account the plugin connects to. The reviewed WordPress directory snapshot reports 80,000+ active installations, which indicates a meaningful footprint in WordPress ecommerce.

Technical information

The authoritative record classifies this issue as CWE-287, Improper Authentication, with a Wordfence-supplied CVSS 3.1 vector of CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H. That means network accessible, high attack complexity, no privileges required, no user interaction, and high confidentiality, integrity, and availability impact.

The vulnerability lives in the passwordless verification route handled by VerificationCodeController::verify(). The route registration in VerificationCodeRestServiceProvider.php uses a permission callback (verify_permissions_check()) that returns true, so any unauthenticated client can reach the endpoint. WPSec identifies the two REST endpoints as /surecart/v1/verification_codes and /surecart/v1/verification_codes/verify.

Root cause

In version 4.9.5, the verify() handler resolved an attacker-supplied login parameter to a WordPress user, forwarded that user's email and the submitted code to the SureCart platform for checking, and called $user->login() on success. User::login() establishes authentication through wp_set_auth_cookie(). The vulnerable handler looked like this:

// 4.9.5 $user = $this->getUser( $request->get_param( 'login' ) ); ... // verify the code. $verify = $model->where( $request->get_query_params() )->verify( [ 'email' => $user->user_email, 'code' => $request->get_param( 'code' ), ] ); ... // login the user. $logged_in = $user->login();

Two checks are missing from this path:

  1. Nothing counts wrong guesses. There is no per-account attempt counter and no persistent email or IP lockout. The only throttle is the upstream SureCart CDN, which Wordfence notes is bypassable through IP rotation.
  2. Nothing checks the resolved user's role. The flow is willing to log in a user of any role, including an administrator.

The controller does have a local resend window, backed by a WordPress transient, which prevents duplicate verification emails and exposes resend availability. It governs code delivery. The CVE concerns repeated submission of candidate codes against an already-requested code, and the resend window places no limit on that.

Attack flow

Based on the documented exploitation sequence, an unauthenticated attacker proceeds as follows:

  1. Request a one-time code for a registered user, including an administrator, through the companion unauthenticated endpoint (/surecart/v1/verification_codes).
  2. Repeatedly submit candidate 6-digit codes for that account against /surecart/v1/verification_codes/verify, rotating source IP addresses to evade the upstream CDN throttle.
  3. On a correct guess, the platform reports the code verified, verify() calls $user->login(), and the attacker receives an authenticated session cookie for the targeted account.

Because there is no role restriction, the end state is a full administrator session. The reviewed sources do not specify a reliable attack duration, request volume, or numerical code lifetime for the vulnerable build.

Patch information

The fixed version is SureCart 4.9.6. Wordfence lists 4.9.6 as the patched version and advises updating to it or a later release, and WPScan also marks the issue as fixed in 4.9.6. The vendor's 4.9.6 changelog, published October 16, 2026, has a single Security line stating it "Strengthened security of the passwordless login code flow (props to Wordfence)," with no further detail. To see what changed, compare app/src/Controllers/Rest/VerificationCodeController.php between the public WordPress.org SVN tags 4.9.5 and 4.9.6.

The REST route registration was not changed: verify_permissions_check() in VerificationCodeRestServiceProvider.php still returns true in 4.9.6. The entire fix is inside the controller, and it adds two separate defences.

1. Privileged accounts cannot use code login

A new helper, canLoginWithCode(), checks the target user's capabilities, not the anonymous requester's, and rejects any account that can edit posts, manage options, or manage SureCart shop settings:

protected function canLoginWithCode( \WP_User $user ): bool { $can = ! ( user_can( $user, 'edit_posts' ) || user_can( $user, 'manage_options' ) || user_can( $user, 'manage_sc_shop_settings' ) ); return (bool) apply_filters( 'surecart/verification_code/can_login', $can, $user ); }

The check runs at both ends of the flow. In create() (the request that sends a code), privileged users receive a fake success response ('email_sent' => true), but no code is generated or emailed. That stops attackers from sending codes to administrators, prevents email-bombing, and avoids revealing which accounts are privileged. In verify(), privileged users are rejected before any platform call and receive the same generic invalid_code error as a wrong guess:

// Gate privileged accounts before any platform call, using the same generic // error as a bad code so valid and invalid codes look identical. if ( ! $this->canLoginWithCode( $user->getUser() ) ) { return new \WP_Error( 'invalid_code', __( 'Invalid verification code', 'surecart' ), [ 'status' => 400 ] ); }

This is the change that blocks the full administrator takeover. Even with unlimited guesses, the code flow can no longer issue an admin session. Sites that want staff to use code login can re-enable it with the surecart/verification_code/can_login filter, which reintroduces the risk this gate removes.

2. A per-email attempt counter with a lockout

The patch adds three constants:

const ATTEMPT_TRANSIENT_PREFIX = 'sc_vcode_attempts_'; const MAX_VERIFY_ATTEMPTS = 5; // Outlasts the code's 10-minute validity so a fresh code can't reset the limit. const LOCKOUT_WINDOW = 15 * MINUTE_IN_SECONDS;

The counter is a WordPress transient keyed on an MD5 hash of the lowercased email address (verifyAttemptTransientKey()). Because it is keyed on the target account rather than the client IP, the IP rotation attackers used to get around the upstream CDN throttle does not help. verify() checks the counter before anything else and increments it before the platform call:

$key = self::verifyAttemptTransientKey( $email ); $attempts = (int) get_transient( $key ); if ( $attempts >= self::MAX_VERIFY_ATTEMPTS ) { return new \WP_Error( 'too_many_attempts', __( 'Too many attempts. Please try again later.', 'surecart' ), [ 'status' => 429 ] ); } // Count before the platform call to narrow the parallel-guess window; not atomic, so a soft cap. set_transient( $key, $attempts + 1, self::LOCKOUT_WINDOW );

The counter is cleared (delete_transient( $key )) only after a successful verification. The 15-minute lockout outlasts the 10-minute life of a code, so an attacker cannot request a fresh code to reset the counter. That cuts brute-forcing a 6-digit code from roughly a million unthrottled tries down to 5 tries every 15 minutes per account. The cap is also soft. The developers' own comment notes the check is "not atomic," so a burst of parallel requests could slip in a few extra guesses before the transient updates. That is a handful of guesses, compared with the unlimited stream possible before. The 4.9.6 patch also tidies verify() so it looks up the user once instead of twice.

Later hardening in 4.10.0 and trunk

An independent code review from Must-Have Plugins found that 4.10.0 (October 20, 2026) adds a check that the verified code belongs to the account being logged in, crediting Patchstack. The current SVN trunk includes this check:

// the code must belong to this user. if ( 0 !== strcasecmp( (string) ( $verify->email ?? '' ), $email ) ) { return $this->invalidCodeError(); }

Trunk's User::login() also destroys the user's existing sessions (WP_Session_Tokens::...->destroy_all()) before setting the new auth cookie. Must-Have Plugins describes this as a separate Patchstack-reported issue that it labels CVE-2026-112241. Wordfence uses that CVE number for a different Missing Authorization entry, so the two databases disagree. In the Must-Have Plugins reading, 4.9.6 closes the path to administrator accounts and 4.10.0 also protects customer accounts.

The trunk VerificationCodeController.php no longer includes the 4.9.6 canLoginWithCode() gate or the MAX_VERIFY_ATTEMPTS counter. A trunk comment says the platform now responds to a "wrong, expired, replaced or locked code" with a 404, suggesting the lockout moved to SureCart's hosted platform, but this could not be confirmed from the public source. Anyone auditing the code or relying on these protections should check them against the exact version installed rather than assuming the 4.9.6 logic is still present.

Affected systems and versions

  • Affected: SureCart plugin for WordPress, all versions up to and including 4.9.5.
  • Fixed for CVE-2026-120574: SureCart 4.9.6 (per Wordfence and WPScan).
  • Separate verification-code weakness (account binding) documented by WPSec as affecting versions through 4.9.6, with remediation in 4.10.0 or later.

The WordPress directory snapshot reviewed displayed version 4.8.0 and should not be used to identify the newest available release. The 80,000+ active installations figure indicates how widely the product is used. It is not a count of vulnerable or compromised sites.

Vendor security history

SureCart shipped several security-relevant releases in close succession. Version 4.9.5 (October 13, 2026) hardened data access checks and improved output escaping and input sanitization, yet Wordfence still lists 4.9.5 as affected by CVE-2026-120574. Version 4.9.6 strengthened passwordless login code security, and 4.10.0 strengthened that flow again and hardened REST API request handling. The history also includes CVE-2026-112241, a separate authentication bypass by spoofing (CWE-290) with CVSS 9.8, affecting versions through 4.9.5 and listing 4.9.6 as unaffected. These records carry different classifications and scores, so they should be evaluated by mechanism rather than collapsed into one. The history shows that a release can be the correct fix for one CVE while remaining affected by another weakness in the same authentication feature.

References

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Gerrit
  • CVS
  • Jira
  • Linear
  • Slack
  • Security Compass
Explore integrations
Security magnifying glass visualization