# REPRO-2026-00250: JeecgBoot broken access control privilege escalation ## Summary Status: published Severity: high Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00250 CVE: CVE-2026-58377 ## Package Name: jeecgboot/JeecgBoot Ecosystem: github Affected: JeecgBoot through 3.9.2 (all versions up to and including 3.9.2) Fixed: Unknown ## Root Cause # CVE-2026-58377 — JeecgBoot Broken Access Control Privilege Escalation ## Summary JeecgBoot v3.9.2 contains a broken access control vulnerability in the OpenAPI credential management module. The `OpenApiAuthController` (`/openapi/auth/*`) and `OpenApiPermissionController` (`/openapi/permission/*`) expose full CRUD endpoints for managing OpenAPI AK/SK credential pairs without any Shiro authorization annotations (`@RequiresRoles` or `@RequiresPermissions`). In contrast, the sibling `OpenApiController` (`/openapi/*`) correctly annotates its mutating endpoints with `@RequiresRoles({"admin"})`. Because the Shiro filter chain maps `/**` to the `jwt` filter (authentication-only, no authorization), any authenticated user — including low-privileged users with only a "test" role — can list, create, edit, and delete all OpenAPI credential pairs, with the list endpoint returning secret keys (SK) in plaintext. This enables credential theft and unauthorized invocation of the OpenAPI surface, including endpoints owned by administrator accounts. ## Impact - **Package/component affected:** `jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/controller/OpenApiAuthController.java` and `OpenApiPermissionController.java` - **Affected versions:** JeecgBoot through 3.9.2 (commit `7df07a823fd558be857d0208ccae96342539fbc1`, tag `v3.9.2`) - **Risk level:** HIGH (CVSS 4.0: 8.6) - **Consequences:** - Any authenticated low-privilege user can read all OpenAPI AK/SK credential pairs in plaintext, including those belonging to administrators. - Low-privilege users can create, modify, and delete credential records without administrative authorization. - Stolen credentials can be used to invoke the OpenAPI surface (`/openapi/call/**`, which is mapped to `anon` and only checks the `appkey` header), impersonating the credential owner — including administrators. ## Impact Parity - **Disclosed/claimed maximum impact:** Privilege escalation — authenticated low-privilege users perform full CRUD on OpenAPI credentials, read admin secret keys in plaintext, and can invoke privileged API routes as the credential owner. - **Reproduced impact from this run:** A low-privileged user (ceshi, role=test) successfully: 1. Read admin's AK/SK credentials in plaintext via `GET /openapi/auth/list` (ak=`ak-pFjyNHWRsJEFWlu6`, sk=`4hV5dBrZtmGAtPdbA5yseaeKRYNpzGsS`, owner=admin) 2. Created a new OpenAPI credential via `POST /openapi/auth/add` (persisted to database) 3. Deleted an OpenAPI credential via `DELETE /openapi/auth/delete` 4. Generated a new AK/SK pair via `GET /openapi/auth/genAKSK` 5. Was correctly rejected by the protected `POST /openapi/add` endpoint (has `@RequiresRoles({"admin"})`), confirming the authorization gap is specific to `OpenApiAuthController` - **Parity:** `full` — all claimed CRUD operations on OpenAPI credentials were demonstrated, including plaintext secret key exposure and the negative control showing the sibling controller is properly protected. - **Not demonstrated:** Actual invocation of `/openapi/call/**` with stolen credentials to impersonate the admin (the second root cause described in the advisory). The primary broken access control on credential management endpoints is fully proven. ## Root Cause The Shiro security configuration (`ShiroConfig.java`) maps all URL paths (`/**`) to the `jwt` filter, which performs **authentication only** (validates JWT token existence and signature). Authorization (role/permission checks) is enforced exclusively through method-level annotations such as `@RequiresRoles` and `@RequiresPermissions`. The `OpenApiAuthController` at `/openapi/auth/*` exposes seven endpoints — `GET /list`, `POST /add`, `PUT /edit`, `DELETE /delete`, `DELETE /deleteBatch`, `GET /queryById`, and `GET /genAKSK` — none of which carry any Shiro authorization annotation. This means the Shiro authorization layer is never triggered for these endpoints; any user who passes JWT authentication can access them. In contrast, the sibling `OpenApiController` at `/openapi/*` correctly annotates its mutating endpoints (`/add`, `/edit`, `/delete`, `/deleteBatch`) with `@RequiresRoles({"admin"})`, which triggers Shiro's `AuthorizationAttributeSourceAdvisor` and enforces that only users with the "admin" role can perform those operations. The `OpenApiPermissionController` at `/openapi/permission/*` has the same absence of authorization annotations on its `POST /add` and `GET /getOpenApi` endpoints. Additionally, the `/openapi/call/**` path is mapped to `anon` (no authentication required at all), and the `OpenApiController.call()` method only reads the `appkey` header to look up the auth record without verifying the secret key signature, enabling unauthorized API invocation with stolen credentials. **Fix commit:** Not yet publicly available in the main branch as of this analysis. The fix would involve adding `@RequiresRoles({"admin"})` annotations to all mutating endpoints in `OpenApiAuthController` and `OpenApiPermissionController`, and implementing SK signature verification in the `call()` method. ## Reproduction Steps 1. **Reference:** `bundle/repro/reproduction_steps.sh` 2. **What the script does:** - Resolves the JeecgBoot v3.9.2 repository from the prepared project cache - Installs JDK 17, Maven, Redis, and MariaDB if not present - Starts MariaDB and Redis, creates the `jeecg-boot` database, and imports the seed SQL (which includes an admin-owned OpenAPI credential) - Uses the pre-built `jeecg-system-start-3.9.2.jar` (or builds it with Maven if not present) - Starts the JeecgBoot Spring Boot application with login captcha disabled (`--jeecg.firewall.enableLoginCaptcha=false`) - Logs in as the low-privileged user `ceshi` (role: `test`, password: `123456`) and obtains a JWT token - Calls `GET /openapi/auth/list` with the low-priv JWT to read all AK/SK credentials in plaintext - Calls `POST /openapi/auth/add` to create a credential as the low-priv user (verified in database) - Calls `DELETE /openapi/auth/delete` to delete a credential as the low-priv user - Calls `GET /openapi/auth/genAKSK` to generate an AK/SK pair as the low-priv user - **Negative control:** Calls `POST /openapi/add` (which has `@RequiresRoles({"admin"})`) and confirms it rejects the low-priv user with "Subject does not have role [admin]" - **Admin positive control:** Logs in as admin and confirms admin can access `POST /openapi/add` - Writes the runtime manifest with all proof artifacts 3. **Expected evidence of reproduction:** - `artifacts/http/openapi_auth_list_response.json` — shows `success: true` with admin's AK/SK in plaintext - `artifacts/http/openapi_auth_add_response.json` — shows `success: true` (low-priv user created credential) - `artifacts/http/openapi_auth_delete_response.json` — shows `success: true` (low-priv user deleted credential) - `artifacts/http/openapi_auth_genAKSK_response.json` — shows `success: true` with generated AK/SK pair - `artifacts/http/openapi_add_negcontrol_response.json` — shows `success: false` with "Subject does not have role [admin]" - `artifacts/http/openapi_add_admin_response.json` — shows `success: true` (admin can access protected endpoint) ## Evidence - **Log file locations:** - `bundle/logs/reproduction_steps.log` — full script execution log - `bundle/logs/jeecgboot_app.log` — JeecgBoot application startup and runtime log - `bundle/artifacts/http/*.json` — HTTP request/response captures for each proof - **Key excerpts proving reproduction:** PROOF 1 — Low-priv user reads admin's AK/SK in plaintext (`openapi_auth_list_response.json`): ```json { "success": true, "code": 200, "result": { "records": [{ "id": "1922164194775056386", "name": "scott", "ak": "ak-pFjyNHWRsJEFWlu6", "sk": "4hV5dBrZtmGAtPdbA5yseaeKRYNpzGsS", "systemUserId": "e9ca23d68d884d4ebb19d07889727dae", "systemUserId_dictText": "admin" }] } } ``` NEGATIVE CONTROL — Protected endpoint rejects low-priv user (`openapi_add_negcontrol_response.json`): ```json { "success": false, "message": "Subject does not have role [admin]", "code": 500 } ``` - **Environment details:** - JeecgBoot v3.9.2 (Spring Boot 3.5.5, Java 17.0.19) - MariaDB 11.8.6, Redis 8.0.5 - Low-priv user: ceshi (role: test, tenant: 1001) - Admin user: admin (role: admin, tenant: 1000) - Application context path: /jeecg-boot, port 8080 ## Recommendations / Next Steps - **Fix approach:** Add `@RequiresRoles({"admin"})` annotations to all mutating endpoints in `OpenApiAuthController` (`/add`, `/edit`, `/delete`, `/deleteBatch`) and `OpenApiPermissionController` (`/add`). Consider whether the `/list`, `/queryById`, and `/genAKSK` endpoints should also be admin-only or restricted to credentials owned by the requesting user. - **Additional fix:** Implement secret key (SK) signature verification in `OpenApiController.call()` to prevent unauthorized API invocation with stolen AK-only credentials. - **Upgrade guidance:** Upgrade to a version that includes the fix once available. In the interim, restrict access to `/openapi/auth/**` and `/openapi/permission/**` via reverse proxy rules (e.g., nginx) to admin-only users. - **Testing recommendations:** Add integration tests that verify non-admin users receive 403/510 responses when accessing OpenAPI credential management endpoints. Add tests that verify SK signature validation in the call endpoint. ## Additional Notes - **Idempotency confirmation:** The script was run twice consecutively, both runs succeeded with exit code 0 and all 4 proofs confirmed. The script drops and recreates the database on each run, ensuring a clean state. - **Captcha handling:** Login captcha was disabled via `--jeecg.firewall.enableLoginCaptcha=false` Spring Boot argument to enable automated login. This does not affect the vulnerability — the captcha is an authentication-layer control, while the vulnerability is in the authorization layer. - **Port conflict:** Apache2 was found occupying port 8080 in the test environment; the script kills it before starting JeecgBoot. - **Seed data:** The seed SQL includes one admin-owned OpenAPI credential (`name: scott`, `ak: ak-pFjyNHWRsJEFWlu6`, `sk: 4hV5dBrZtmGAtPdbA5yseaeKRYNpzGsS`, `systemUserId: admin`), which serves as the target credential for the privilege escalation proof. ## Reproduction Details Reproduced: 2026-07-06T08:39:48.833Z Duration: 1906 seconds Tool calls: 271 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00250 pruva-verify CVE-2026-58377 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00250&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00250/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-58377 - Source: https://github.com/jeecgboot/JeecgBoot ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 20124 bytes) - bundle/repro/rca_report.md (analysis, 10528 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 18048 bytes) - bundle/vuln_variant/rca_report.md (analysis, 17538 bytes) - bundle/artifact_promotion_manifest.json (other, 18273 bytes) - bundle/vuln_variant/source_identity.json (other, 1709 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 3022 bytes) - bundle/repro/runtime_manifest.json (other, 1117 bytes) - bundle/repro/validation_verdict.json (other, 769 bytes) - bundle/logs/jeecgboot_app.log (log, 67124 bytes) - bundle/vuln_variant/http/record_list_ceshi.json (other, 1925 bytes) - bundle/vuln_variant/http/record_add_ceshi.json (other, 108 bytes) - bundle/vuln_variant/http/record_delete_ceshi.json (other, 104 bytes) - bundle/vuln_variant/http/permission_add_ceshi.json (other, 102 bytes) - bundle/vuln_variant/http/openapi_add_negcontrol.json (other, 115 bytes) - bundle/vuln_variant/validation_verdict.json (other, 3114 bytes) - bundle/logs/vuln_variant/reproduction_steps.log (log, 3574 bytes) - bundle/logs/vuln_variant/latest_version.txt (other, 513 bytes) - bundle/vuln_variant/http/permission_getOpenApi_ceshi.json (other, 559 bytes) - bundle/vuln_variant/http/record_queryById_ceshi.json (other, 253 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 6817 bytes) - bundle/vuln_variant/variant_manifest.json (other, 5188 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 2354 bytes) - bundle/vuln_variant/http/login_ceshi.json (other, 28146 bytes) - bundle/vuln_variant/http/login_admin.json (other, 28847 bytes) - bundle/logs/vuln_variant/jeecgboot_app.log (log, 193115 bytes) - bundle/logs/vuln_variant/fixed_version.txt (other, 633 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00250 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00250/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00250 ## 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://pruva.dev