# Patch Analysis: CVE-2026-23958

## Fix Commit

- **Commit**: `cac165ee84bb296184b9be6f5fa695af0344fa05`
- **Author**: fit2cloud-chenyw
- **Date**: 2025-12-25
- **Message**: `fix: JWT Token 漏洞`

## Files Changed

Only one file was modified:
- `sdk/common/src/main/java/io/dataease/auth/filter/CommunityTokenFilter.java`

## Diff

```diff
--- a/sdk/common/src/main/java/io/dataease/auth/filter/CommunityTokenFilter.java
+++ b/sdk/common/src/main/java/io/dataease/auth/filter/CommunityTokenFilter.java
@@ -43,7 +43,7 @@ public class CommunityTokenFilter implements Filter {
                 Object apisixCacheManage = CommonBeanFactory.getBean("apisixCacheManage");
                 Method method = DeReflectUtil.findMethod(apisixCacheManage.getClass(), "userCacheBO");
                 Object o = ReflectionUtils.invokeMethod(method, apisixCacheManage, userId);
-                Method pwdMethod = DeReflectUtil.findMethod(o.getClass(), "getPwd");
+                Method pwdMethod = DeReflectUtil.findMethod(o.getClass(), "getSecret");
                 Object pwdObj = ReflectionUtils.invokeMethod(pwdMethod, o);
                 secret = pwdObj.toString();
             }
```

## Analysis

### Before the Fix (Vulnerable)
In the xpack/enterprise code path, `CommunityTokenFilter` obtained the JWT verification secret by calling `userCacheBO.getPwd()`. This returned the raw password hash (`MD5(admin_password)`), which was also the same value used by the xpack login server to **sign** JWTs. Because the admin password defaulted to the hardcoded string `DataEase@123456`, an attacker could compute `MD5("DataEase@123456")` and forge valid admin JWTs.

### After the Fix (Neutralized)
The fix changes the reflection call from `getPwd()` to `getSecret()`. In `LoginUserCacheBO`:

```java
public String getPwd() {
    return this.pwd;  // e.g., "504c8c8dfcbbe5b50d676ad65ef43909"
}

public String getSecret() {
    return this.pwd + RsaUtils.publicKey();  // unpredictable per-instance suffix
}
```

`RsaUtils.publicKey()` returns an AES-encrypted, Base64-encoded representation of the instance's RSA public key concatenated with a random AES key. This value is unique per installation and is stored in the `core_rsa` database table. Because the attacker cannot predict this suffix, the HMAC key is no longer derivable from the default password alone.

### Why the Fix Works
1. **Key unpredictability**: The verification key now includes a cryptographically random component (`RsaUtils.publicKey()`) that is generated independently per deployment.
2. **Backward compatibility for valid users**: Legitimate tokens issued by the server after the fix are signed with the same `getSecret()` value, so they verify correctly.
3. **Attacker tokens invalidated**: Any token forged by an attacker using only the MD5 of the default password will fail signature verification because the missing `RsaUtils.publicKey()` suffix makes the signature invalid.

### Scope of the Fix
The fix commit **only** modifies the xpack/enterprise code path (the `else` branch when `loginServer` bean exists). The community path (`if` branch) still uses `SubstituleLoginConfig.getPwd()` and `Md5Utils.md5(pwd)`. However, in the official Docker images, xpack jars are present and active, so the xpack path is the one that matters for runtime exploitation.

## Verification

Our reproduction confirmed that:
- On `v2.10.18` (before the fix): A JWT forged with `MD5("DataEase@123456")` is accepted (HTTP 200).
- On `v2.10.21` (after the fix): The identical forged JWT is rejected with HTTP 401 and the `DE-GATEWAY-FLAG` header set to `The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA256`.

The one-line change in `cac165e` successfully neutralizes the authentication bypass for the xpack path.
