# REPRO-2026-00168: DataEase: authentication bypass via password-derived HMAC JWT signing key ## Summary Status: published Severity: critical CVSS: 9.8 / 10 CWE: CWE-522 (Insufficiently Protected Credentials) Type: security Confidence: Unknown ## Identifiers REPRO ID: REPRO-2026-00168 CVE: CVE-2026-23958 ## Package Name: dataease Ecosystem: github Affected: <= v2.10.20 Fixed: v2.10.21 ## Root Cause # 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/`. ## Reproduction Details Reproduced: 2026-05-25T23:25:04.719Z Duration: 6011 seconds Tool calls: 630 Turns: 605 Handoffs: 3 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00168 pruva-verify CVE-2026-23958 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00168&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00168/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-23958 - Source: https://github.com/dataease/dataease ## Artifacts - bundle/repro/rca_report.md (analysis, 5758 bytes) - bundle/repro/reproduction_steps.sh (reproduction_script, 7300 bytes) - bundle/context.json (other, 6997 bytes) - bundle/metadata.json (other, 836 bytes) - bundle/ticket.md (ticket, 5912 bytes) - bundle/repro/my.cnf (other, 617 bytes) - bundle/repro/mysql-init/01-init.sql (other, 97 bytes) - bundle/repro/patch_analysis.md (documentation, 3798 bytes) - bundle/repro/application.yml (other, 881 bytes) - bundle/repro/validation_verdict.json (other, 1443 bytes) - bundle/repro/docker-compose-fix.yml (other, 1253 bytes) - bundle/repro/docker-compose-vuln.yml (other, 1253 bytes) - bundle/logs/v2.10.21_forged_status.txt (other, 3 bytes) - bundle/logs/full_bypass_headers.txt (other, 232 bytes) - bundle/logs/v2.10.18_forged_status.txt (other, 3 bytes) - bundle/logs/v2.10.21_baseline_status.txt (other, 3 bytes) - bundle/logs/fixed_transcript.txt (other, 285 bytes) - bundle/logs/v2.10.18_baseline_headers.txt (other, 194 bytes) - bundle/logs/v2.10.18_baseline_status.txt (other, 3 bytes) - bundle/logs/vulnerable_transcript.txt (other, 285 bytes) - bundle/logs/no_xpack_baseline_headers.txt (other, 189 bytes) - bundle/logs/no_xpack_baseline_body.txt (other, 76 bytes) - bundle/logs/fixed_attack_response.txt (other, 317 bytes) - bundle/logs/full_bypass_body.txt (other, 84 bytes) - bundle/logs/no_xpack_bypass_body.txt (other, 2805 bytes) - bundle/logs/no_xpack_bypass_headers.txt (other, 306 bytes) - bundle/logs/docker_build_no_xpack.log (log, 1471 bytes) - bundle/logs/variant_test.log (log, 10278 bytes) - bundle/logs/evidence_fixed_headers.txt (other, 232 bytes) - bundle/logs/v2.10.21_baseline_headers.txt (other, 194 bytes) - bundle/logs/repro_run1.log (log, 1171 bytes) - bundle/logs/full_baseline_body.txt (other, 76 bytes) - bundle/logs/jwt_details.txt (other, 162 bytes) - bundle/logs/vulnerable_attack_response.txt (other, 363 bytes) - bundle/logs/v2.10.18_baseline_body.json (other, 81 bytes) - bundle/logs/v2.10.18_forged_headers.txt (other, 312 bytes) - bundle/logs/v2.10.21_baseline_body.json (other, 81 bytes) - bundle/logs/v2.10.18_forged_body.json (other, 50 bytes) - bundle/logs/repro_run2.log (log, 1171 bytes) - bundle/logs/v2.10.21_forged_headers.txt (other, 232 bytes) - bundle/logs/evidence_vulnerable_headers.txt (other, 312 bytes) - bundle/logs/full_baseline_headers.txt (other, 189 bytes) - bundle/logs/evidence_fixed_body.json (other, 84 bytes) - bundle/logs/evidence_vulnerable_body.json (other, 50 bytes) - bundle/logs/v2.10.21_forged_body.json (other, 84 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00168 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00168/artifacts/bundle/repro/reproduction_steps.sh - Web: https://www.pruva.dev/reproductions/REPRO-2026-00168 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://www.pruva.dev