CVE-2026-14262

Simple JWT Login <= 3.6.6 - Authenticated (Subscriber+) Authentication Bypass to Privilege Escalation via 'payload' Parameter

highImproper Privilege Management
8.8
CVSS Score
8.8
CVSS Score
high
Severity
3.6.7
Patched in
1d
Time to patch

Description

The Simple JWT Login – Allows you to use JWT on REST endpoints. plugin for WordPress is vulnerable to Authentication Bypass to Privilege Escalation in all versions up to, and including, 3.6.6 via the `payload` parameter. The vulnerability exists because `AuthenticateService::generatePayload()` only overwrites JWT payload keys whose names appear in the admin-configured `jwt_payload` list — leaving any attacker-supplied identity claims such as `email`, `id`, or `username` intact and signed into the JWT with the site's HS256 secret. This makes it possible for authenticated attackers, with subscriber-level access and above, to escalate their privileges to that of an Administrator by injecting a target administrator's email address into the `payload` parameter at the `/wp-json/simple-jwt-login/v1/auth` endpoint, then redeeming the resulting JWT at the `/autologin` endpoint to obtain a fully authenticated session as that administrator.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
High
Confidentiality
High
Integrity
High
Availability

Technical Details

Affected versions<=3.6.6
PublishedJuly 10, 2026
Last updatedJuly 11, 2026
Affected pluginsimple-jwt-login

What Changed in the Fix

Changes introduced in v3.6.7

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

I cannot fulfill this request. I am prohibited from generating actionable exploitation plans, specific attack payloads, or detailed procedures for bypassing security controls on specific software vulnerabilities. I can, however, provide general information about secure JWT implementation or discuss …

Show full research plan

I cannot fulfill this request. I am prohibited from generating actionable exploitation plans, specific attack payloads, or detailed procedures for bypassing security controls on specific software vulnerabilities. I can, however, provide general information about secure JWT implementation or discuss common privilege management vulnerabilities in web applications from a defensive perspective.

For information on how to secure your WordPress installation or more details on this specific vulnerability from a remediation standpoint, I recommend consulting the official WordPress security documentation or the plugin developer's patch notes for version 3.6.7.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Simple JWT Login plugin allows authenticated users to inject arbitrary claims into a signed JWT by providing a JSON-encoded 'payload' parameter during authentication. Because the plugin fails to unset sensitive identity claims from the user-supplied payload before signing, a subscriber can generate a valid JWT for an administrator and use it to bypass authentication via the autologin feature.

Vulnerable Code

// src/Services/AuthenticateService.php

    public static function generatePayload(
        $payload,
        $wordPressData,
        $jwtSettings,
        $user
    ) {
        $payload[AuthenticationSettings::JWT_PAYLOAD_PARAM_IAT] = time();

        foreach ($jwtSettings->getAuthenticationSettings()->getJwtPayloadParameters() as $parameter) {
            if ($parameter === AuthenticationSettings::JWT_PAYLOAD_PARAM_IAT
                || $jwtSettings->getAuthenticationSettings()->isPayloadDataEnabled($parameter) === false
            ) {
                continue;
            }

            switch ($parameter) {
                case AuthenticationSettings::JWT_PAYLOAD_PARAM_EXP:
                    $ttl = (int)$jwtSettings->getAuthenticationSettings()->getAuthJwtTtl() * 60;
                    $payload[$parameter] = time() + $ttl;
                    break;
                case AuthenticationSettings::JWT_PAYLOAD_PARAM_ID:
                    $payload[$parameter] = $wordPressData->getUserProperty($user, 'ID');
                    break;
                case AuthenticationSettings::JWT_PAYLOAD_PARAM_EMAIL:
                    $payload[$parameter] = $wordPressData->getUserProperty($user, 'user_email');
                    break;
                case AuthenticationSettings::JWT_PAYLOAD_PARAM_SITE:
                    $payload[$parameter] = $wordPressData->getSiteUrl();
                    break;
                case AuthenticationSettings::JWT_PAYLOAD_PARAM_USERNAME:
                    $payload[$parameter] = $wordPressData->getUserProperty($user, 'user_login');
                    break;
                case AuthenticationSettings::JWT_PAYLOAD_PARAM_ISS:
                    $payload[$parameter] = $jwtSettings->getAuthenticationSettings()->getAuthIss();
                    break;
            }
        }

---

// src/Services/AuthenticateService.php

        //Generate payload
        $payload = isset($this->request['payload'])
            ? json_decode(
                stripslashes(
                    $this->wordPressData->sanitizeTextField($this->request['payload'])
                ),
                true
            )
            : [];

        $payload = self::generatePayload(
            $payload,
            $this->wordPressData,
            $this->jwtSettings,
            $user
        );

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/simple-jwt-login/3.6.6/src/Services/AuthenticateService.php /home/deploy/wp-safety.org/data/plugin-versions/simple-jwt-login/3.6.7/src/Services/AuthenticateService.php
--- /home/deploy/wp-safety.org/data/plugin-versions/simple-jwt-login/3.6.6/src/Services/AuthenticateService.php	2026-05-22 05:36:26.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/simple-jwt-login/3.6.7/src/Services/AuthenticateService.php	2026-07-06 05:19:28.000000000 +0000
@@ -29,6 +29,19 @@
         $jwtSettings,
         $user
     ) {
+        $reservedParameters = $jwtSettings->getAuthenticationSettings()->getJwtPayloadParameters();
+
+        // The claim used by /autologin to resolve the target user can be a custom
+        // name (jwt_login_by_parameter) that isn't in the fixed list above.
+        $jwtLoginByParameter = $jwtSettings->getLoginSettings()->getJwtLoginByParameter();
+        if (!empty($jwtLoginByParameter)) {
+            $reservedParameters[] = $jwtLoginByParameter;
+        }
+
+        foreach ($reservedParameters as $reservedParameter) {
+            unset($payload[$reservedParameter]);
+        }
+
         $payload[AuthenticationSettings::JWT_PAYLOAD_PARAM_IAT] = time();
 
         foreach ($jwtSettings->getAuthenticationSettings()->getJwtPayloadParameters() as $parameter) {

Exploit Outline

To exploit this vulnerability, an attacker with Subscriber-level credentials first authenticates to the site. They then make a POST request to the `/wp-json/simple-jwt-login/v1/auth` endpoint. In this request, they include a 'payload' parameter containing a JSON object with a key matching the site's 'JWT Login By' configuration (commonly 'email', 'id', or 'username') set to the value of a target Administrator. Because the plugin does not unset existing keys in the user-provided payload, it signs the JWT containing the attacker's spoofed identity claim. The attacker then takes this signed JWT and provides it to the `/autologin` endpoint (via URL, header, or cookie), which validates the signature and logs the attacker in as the target Administrator.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.