# RCA Report — CVE-2026-23958 (DataEase JWT Authentication Bypass)

## Summary

DataEase signs its authentication JWTs with an HMAC-SHA256 key derived from the admin password. In vulnerable versions, the `CommunityTokenFilter` uses `getPwd()` (which returns the raw password hash) as the JWT verification secret. Because the default admin password is the well-known constant `DataEase@123456`, an unauthenticated attacker can compute `MD5("DataEase@123456")`, forge a JWT with claims `{uid:1, oid:1}`, and present it in the `X-DE-TOKEN` header to access any protected REST endpoint as the admin user.

## Impact

- **Package/Component**: `io.dataease.auth.filter.CommunityTokenFilter` (sdk/common) and `io.dataease.xpack.permissions.login.bo.LoginUserCacheBO` (xpack-permission)
- **Affected versions**: Docker images up to and including **v2.10.10** are demonstrably vulnerable. Git commit analysis shows the fix (`getPwd` → `getSecret`) was already merged by v2.10.20, but the ticket incorrectly labels v2.10.20 as vulnerable.
- **Risk level**: High — unauthenticated remote attacker can impersonate the admin user.
- **Consequences**: Full admin takeover via forged JWT, enabling subsequent exploitation of authenticated endpoints.

## Root Cause

In the `CommunityTokenFilter.doFilter` method, when the application is running with an active `loginServer` bean (the standard Docker image configuration), the filter reaches the `else` branch and derives the JWT secret from the user cache object:

```java
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");  // vulnerable
Object pwdObj = ReflectionUtils.invokeMethod(pwdMethod, o);
secret = pwdObj.toString();
```

In the vulnerable code (`getPwd`), `secret` is simply the user's password hash (`504c8c8dfcbbe5b50d676ad65ef43909` for the default admin). This is trivially derivable by anyone who knows the default password. The fix changes `getPwd` to `getSecret`, which concatenates the password hash with the per-installation RSA public key, making the secret unpredictable and no longer derivable from public information alone.

Fix commit: `cac165ee84bb296184b9be6f5fa695af0344fa05` ("fix: JWT Token 漏洞", 2025-12-25). This commit is already present in git tag v2.10.20 and in Docker images v2.10.20+.

## Reproduction Steps

1. Run `repro/reproduction_steps.sh`
2. The script:
   - Starts a MySQL 8 container and a DataEase **v2.10.10** container (vulnerable)
   - Waits for the API to respond on `http://127.0.0.1:8100`
   - Baselines an anonymous request to `/de2api/user/personInfo` → expects **401**
   - Forges a JWT with `secret = MD5("DataEase@123456")` and sends it as `X-DE-TOKEN` → expects **200**
   - Stops the vulnerable app but **preserves the MySQL data**
   - Starts a DataEase **v2.10.21** container against the **same** MySQL data
   - Replays the **identical** forged JWT → expects **401** with `DE-GATEWAY-FLAG` header
3. Expected evidence:
   - `logs/vulnerable_attack_response.txt` shows HTTP 200
   - `logs/fixed_attack_response.txt` shows HTTP 401 and `DE-GATEWAY-FLAG: The Token's Signature resulted invalid...`

## Evidence

- `logs/repro_run1.log` — first successful execution of `reproduction_steps.sh`
- `logs/repro_run2.log` — second successful execution (idempotency confirmed)
- `logs/vulnerable_transcript.txt` — summary of v2.10.10 test results
- `logs/fixed_transcript.txt` — summary of v2.10.21 test results
- `logs/vulnerable_attack_response.txt` — raw HTTP response showing 200 on forged JWT
- `logs/fixed_attack_response.txt` — raw HTTP response showing 401 + `DE-GATEWAY-FLAG`

Key excerpts from v2.10.10 (vulnerable):
```
HTTP/1.1 200
X-DE-EXECUTE-VERSION: 2.10.10
...
{"code":60003,"msg":"缺少许可证","data":null}
```
(The 200 status proves the JWT signature was accepted; the downstream "missing license" error is irrelevant to the auth bypass.)

Key excerpts from v2.10.21 (fixed):
```
HTTP/1.1 401
X-DE-EXECUTE-VERSION: 2.10.21
DE-GATEWAY-FLAG: The%20Token%27s%20Signature%20resulted%20invalid%20when%20verified%20using%20the%20Algorithm%3A%20HmacSHA256
```

## Recommendations / Next Steps

1. **Upgrade** to DataEase v2.10.20 or later. The fix commit is already present in those builds.
2. **Rotate secrets**: If running an older vulnerable build, change the admin password and restart the application so that any cached JWT secret is regenerated.
3. **Additional hardening**: Remove the fallback MD5-based secret derivation in `SubstituleLoginConfig` entirely, or enforce a randomly generated community-edition signing key at first boot.
4. **Regression testing**: Add an integration test that attempts to authenticate with a JWT signed using only `MD5(default_password)` and asserts 401.

## Additional Notes

- **Idempotency**: `reproduction_steps.sh` was executed twice consecutively with identical results (HTTP 200 on v2.10.10, HTTP 401 on v2.10.21).
- **Version discrepancy**: The ticket specifies v2.10.20 as vulnerable and v2.10.21 as fixed. However, binary analysis of the official Docker images shows that v2.10.20 already contains the `getSecret()` fix. The last vulnerable official Docker image we could identify is **v2.10.10**, which still uses `getPwd()`. The reproduction script therefore uses v2.10.10 as the vulnerable baseline and v2.10.21 as the fixed baseline to ensure the vulnerability is actually demonstrated at runtime.
- **Environment**: Docker 27.x, Ubuntu 22.04 sandbox, images pulled from `registry.cn-qingdao.aliyuncs.com/dataease/`.
