JeecgBoot coordinated wave (76 GHSAs): annotation-absence as the authz model, self-approval workflows, and an AI module full of credential oracles¶
Wave in one line: Seventy-six advisories against JeecgBoot ≤3.9.5 (Java low-code platform, Spring Boot + Apache Shiro) published in one batch at 2026-10-11T00:30Z — almost every one is a handler that simply lacks the @RequiresPermissions annotation its siblings carry, which makes annotation-absence the authorization boundary for the whole product class: any authenticated session reaches every un-annotated route, including DB-credential, MCP-token, tenant-ownership, and audit-log-wipe endpoints.
Signal: 76 GHSAs (7 high, 69 medium, CVE-2026-108605–108680), all through 3.9.5. Representative legs below; the full set is the sweep lesson itself. JeecgBoot ships a large /sys/** + /airag/** + /openapi/** management surface out of the box and is common in the wild as internally-built business apps — a high-yield bug-bounty and engagement target.
Authorized validation only
Disposable instance, throwaway low-privileged user, synthetic rows/tenants, recorder/rollback for every mutation. Never enumerate a third-party instance, read real DB credentials, or delete audit tables outside an approved lab.
High-severity legs¶
| GHSA | CVE | Boundary |
|---|---|---|
| xqq5-rxcj-4fmx | CVE-2026-108648 | GET /sys/api/getDynamicDbSourceByCode has no Shiro permission/role annotation at all — any authenticated low-priv user supplies dbSourceCode and gets JDBC URLs, usernames, and decrypted cleartext database passwords for every configured dynamic datasource. SystemApiController is an internal aggregation API (role/department/user/query helpers) that assumed only "trusted" callers would ever address it directly. |
| ghh4-x4m2-g9rc | CVE-2026-108677 | GET /sys/api/getUserByName returns any user's stored password value, AES-CBC wrapped — with the key handed to you by /sys/getEncryptedString, an unguarded endpoint that exists to encrypt login payloads. Decrypt admin password ciphertexts offline. Response "encryption" with an app-served key is obfuscation, not protection. |
| p9cq-jf42-732x | CVE-2026-108671 | AI module reads MCP server configurations — endpoint URLs, headers, and outbound auth tokens — because the queryById permission check is commented out; record ids come from the unguarded /airag/app/queryById. A commented-out authz check that stays in the shipped source is a standing door with a label on it. |
| hh6j-j8mj-f737 | CVE-2026-108661 | POST /sys/tenant/changeOwenUserTenant [sic] transfers tenant ownership given any userId+tenantId — no check that the caller is the current owner. A low-priv member reassigns any tenant's owner to themselves and strips the real owner. (The route-name typo OwenUser is itself a fingerprint — grep bundles for misspelled handlers; they correlate with skipped review.) |
| gp9m-m3c8-jw96 | CVE-2026-108657 | Self-approval workflow: file a tenant-admin application via doApplyTenantPackUser, approve it yourself via PUT /sys/tenant/passApply — requester and approver are the same principal, any tenant. Separation-of-duties is a route-pair property: test can(requester) == can(approve). |
| 5xxx-327x-j7r9 | CVE-2026-108628 | saveDeptRolePermission — any authenticated user writes department role permissions (roleId + permissionId bindings), the direct self-grant primitive for the RBAC engine itself. |
| 6vff-68pc-g8vm | CVE-2026-108623 | SysLogController deleteBatch accepts ids=allclear, a magic sentinel that wipes the entire sys_log audit table for any authenticated caller. Bulk-mutation verbs with sentinel values are both an integrity and an anti-forensics surface. |
Notable medium legs (the class, not the count)¶
| Pattern | Legs | Detail |
|---|---|---|
| Unguarded list = ID oracle for the gated verb | ~20 of 76 | The recurring sentence "attackers can obtain ids from the unguarded /list endpoint" — list/queryById siblings of gated create/update/delete expose every row id (logs r6fq, prompts pcc8, OCR wf7x, OSS files 8fjj, announcements, positions, depart-permissions…). Gate parity must be checked pairwise: id-source route vs consuming route. |
| AI knowledge/prompt oracles | fw8c/108669, pvqc/108673, 47h3/108670 | embeddingSearch reads document text chunks from any knowledge base (no annotation); exportXls bulk-downloads every user's AI prompts (model ids, params) as one workbook; promptExperiment burns LLM quota running other users' prompt templates against their datasets. |
| Shared-state prompt poisoning | 392w/108605, wf7x/108606 | Global OCR templates live in a single shared Redis key (airag:ocr) — any user's PUT /airag/ocr/edit overwrites the LLM prompt every other user's OCR calls consume. Shared cache namespace == cross-user injection channel. |
AI record IDOR by userId param | x5p6/108672, 6rqr/108609, c23h/108608, 6wfc/108607 | Video/voice generation history read and deleted by supplying another user's userId — the session identifies the caller, the query identifies nobody. |
| Import/export verb-family bypass | jqgc/108647, xrqv/108646, ww57/108636 | importExcel handlers lack the add permission their CRUD twin enforces — upload a workbook to bulk-create validation rules (arbitrary regex!), categories, departments. Verb coverage on /import, /export, /batch, /revert, /recycleBin is routinely missed by annotation passes. |
| Trusted-internal relay abuse | cmc8/108680, cm6g/108679 | sendTemplateAnnouncement/sendBusAnnouncement accept forged sender/recipient/title — any user delivers messages rendered as admin/system-origin (phishing relay with a trusted sender badge). |
| Tenant enumeration by small integers | rghv/108658, x5p8/108659, xp5m/108656 | queryTenantAuthInfo, listPackByTenantUserId, getTenantPackApplyUsers iterate tenantId=1..N to map other tenants' records and admin applications. Multi-tenant IDs are guessable; every tenant-scoped route deserves an increment probe. |
Why this is durable operator value¶
- Annotation-absence means annotation-presence is the entire allow-list — enumerate handlers, not roles. Shiro's
@RequiresPermissionsis opt-in per method: a missing annotation is not "deny by default," it is reachable by every session. The sweep for any Spring/Shiro app: list controller methods (actuator mappings, swagger export, or source), diff annotated vs un-annotated, and treat every un-annotated one as an authenticated-user endpoint until proven otherwise. JeecgBoot'sSystemApiControllershows the sharpest shape of this: an internal aggregation API (/sys/api/*) built for trusted server-side callers, exposed on the same public port with the same session filter — the "API for the frontend/internal relay" family deserves its own route dump on every Java app you test. - Pairwise gate parity: every id-consuming verb has an id-source route; gate both. Twenty-plus legs here follow one template:
DELETE /x/deleteis annotated,GET /x/listis not, so the gate protects nothing. Audit rule: for every mutation route, find where its ids come from (list, queryById, export) and require the same minimum privilege on both. This is the Oct 10 HaiND sibling-gate-diff rule (Octop/api/providersvs/api/admin/providers) generalized to id-supply routes. - Workflow routes are authorization state machines — test requester==approver. The
doApplyTenantPackUser → passApplyself-approval and the owner-uncheckedchangeOwenUserTenanttransfer are the two generic probes for any approval/transfer/invite feature: (a) can the principal who creates a request also satisfy it? (b) does the transfer endpoint verify the caller holds the thing being transferred? Also grep for the misspelled-handler class: typo'd route names (changeOwenUserTenant,OwenforOwner) mark code paths that skipped naming review — fuzz bundles for common misspellings. - App-served crypto keys defeat app-served encryption.
/sys/getEncryptedStringexists to make logins look encrypted; the same key decrypts password ciphertexts returned by an unguarded route. For any app with a key/init/bootstrap endpoint (login encrypt keys, RSA pairs, signature secrets), assume every other use of that key is confidentiality-zero. Probe: capture the key endpoint, try it against every "protected" response field you can obtain. - Sentinel parameters on bulk verbs.
ids=allclearis a sentinel the handler interprets as "everything." Replay-test bulk delete/update endpoints with empty value,*,all,allclear, and framework-flavored sentinels against lab rows — and note audit-log deletion specifically: it changes the cost of noisy testing on any platform you operate against with scope. - The AI sidecar module is the least-reviewed attack surface. AI/LLM features bolted onto mature CRUD platforms reuse the platform's (broken) authz and add new shared-state classes: per-feature shared Redis keys as global prompt stores, MCP/datasource config tables as outbound-token vaults, embedding search as cross-tenant corpus read, prompt export as bulk IP/credential harvest, and generation history keyed by a caller-supplied
userId. When a platform gains anairag-style module, diff its controllers against the core app's annotation coverage first — new module, old bug. - Commented-out checks are live intelligence. The MCP-config leg's check is
// permissionin the shipped source. For open-source Java apps,git log -S 'RequiresPermissions'(comment-out diffs) and grep for commented authz lines finds siblings the advisory wave didn't name. Same-product re-sweep applies: one missing-annotation wave predicts the next.
Replayable validation boundaries (lab/authorized scope only)¶
- Annotation-absence sweep (own JeecgBoot-class lab): dump route inventory (
/v2/api-docsswagger export or actuator mappings) with a throwaway low-priv session; request every/sys/api/*,/airag/*,/openapi/*route; table status codes + payload presence. Evidence = route→privilege matrix. Never run this against a third-party host without written scope. - Id-source parity probe (lab rows): for one resource family, confirm list returns ids the gated mutation consumes, with the lowest-tier token. Marker rows only; record field names, not values.
- Self-approval decision table (two disposable tenants): apply → approve as same principal → assert privilege delta (tenant-admin pack granted). Revert after capture.
- Key-endpoint decryption check (lab user set): fetch
/sys/getEncryptedString, request your own user's record via the unguarded read, decrypt locally to prove the wrap. Never target another real user's material outside the lab. - Sentinel bulk-verb test (scratch rows only): bulk-delete one scratch row, then
ids=allclearagainst a seeded scratch table in an isolated schema, with audit export first. Never point this atsys_logof a shared instance. - Shared-key prompt poisoning (lab Redis): snapshot the
airag:ocr-style key, write a marker template via the user route, observe another lab user's call consuming it, restore. Never touch production caches.
October 11 (03:30Z) same-template legs from the following wave¶
The Oct 11 03:30Z batch repeated this page's templates verbatim on other products — evidence the annotation-absence/id-source-parity sweep generalizes beyond JeecgBoot: eladmin GET /api/s3Storage/download/{id} sequential-id enumeration returns every stored object URL without storage permission (GHSA-8j2c-pwgm-76r5) and POST /api/localStorage/pictures image-named upload bypasses storage:add + leaks absolute paths (GHSA-2hch-3q25-w3g9); MultiVendorX marketplace settings REST route gated only by edit_stores lets any store_owner rewrite commission/payout settings site-wide — verb-scope drift on a settings namespace (GHSA-h37j-r28p-597w); Wukong AICRM POST /chat/send accepts an arbitrary sessionId, letting any user append into and stream replies from a victim's AI conversation (AI-module id-source parity again, GHSA-2p74-f492-56xr). Wukong_HRM/CordysCRM aspect/annotation waves folded on the Sept 21 flat-authz drift page.
October 11 (15:3xZ) second wave: ~24 more GHSAs — config-writes, row-rule tampering, and forged system announcements¶
Fifteen hours after the 76-GHSA dump, a second JeecgBoot ≤3.9.5 batch (CVE-2026-108866–108891) landed with the same annotation-absence template plus a few new verb families — same-product re-sweep in action; expect a third. Legs with new axes:
| GHSA | CVE | Boundary |
|---|---|---|
| cc33-2w5g-wpvx | CVE-2026-108883 (high) | editThirdAppConfig — any authenticated user rewrites the DingTalk/WeCom/Feishu integration's client id, client secret, agent id, and corp id. That is a standing redirect of directory sync + all platform messaging to attacker-controlled apps (credential swap → message-path takeover), not just a config write. Config-write routes that re-point integrations are message-relay and identity-sync primitives — test them before theorizing about the messaging routes themselves. |
| 898h-4prp-w6gp / w6fw-28rx-2577 | CVE-2026-108870 / 108871 | saveDatarule on both the role and department-role controllers lets any user overwrite data_rule_ids — direct row-level filter tampering: clear a rule to widen readable rows, or set one to steer which rows any role sees. RBAC engines with a data-rule layer put the row-scope policy behind the same annotation gate as the feature itself; probe the rule-write route even when the feature route is gated. |
| 4qg9-8c9f-qcv4 / vxv9-jpp4-r7f7 | CVE-2026-108869 / 108868 | POST /sys/api/sendSysAnnouncement and sendBusTemplateAnnouncement accept forged fromUser/toUser/title/content — any user delivers announcements rendered as system-origin over WebSocket/WeCom. The /sys/api/* internal-relay family from the first wave gains the message-spoofing verb set; phishing relay with a trusted sender badge, same template as the first wave's cmc8/cm6g. |
| 623c-j748-v6w2 | CVE-2026-108881 | getTenantPackInfo with fixed packCode values (superAdmin, accountAdmin, appAdmin) discloses which tenants hold which admin packs — a tenant→role map from a single guessed-code read. Enumerate well-known pack/plan/role code constants on any multi-tenant platform; the code list often ships in the frontend bundle. |
| 2274-mgcg-m5j6 | CVE-2026-108880 | PUT /sys/dict/editDictByLowAppId — dictionary edit gated (if at all) by a client-supplied lowAppId parameter or X-Low-App-ID header. Where the "scope" selector is attacker-chosen, the gate checks the selector, not ownership — same supplied-scope-vs-owned-scope rule as the Oct 8 DolphinScheduler legs. |
| 5w4w-m38r-7r7r | CVE-2026-108879 | /airag/api/getChatVariable reads any user's AI chat variables by supplying a username param — the AI-module caller-supplied-identity IDOR family extends to chat state. |
| 7xj8-xwgj-5prc / h752-2r87-c7w9 | CVE-2026-108866 / 108867 | queryUserAuths and getUserRoleSetById return any user's full permission set / role codes by userId — admin-account discovery without touching a user-list route. Privilege-map oracles make every later targeted probe cheaper; sweep *Auth*/*Role* read routes independently of user enumeration. |
| pmhm-r7h7-cxvj | CVE-2026-108876 | putCancelQuit restores ended/refused tenant memberships with a caller-supplied tenantId or X-Tenant-Id header — tenant-header re-selection on a state-change verb. |
Updated rules: (1) the annotation-absence sweep is exhaustive, not one-shot — two waves fifteen hours apart against the same band means the first dump is a floor, not a ceiling; re-poll the vendor and re-run the handler diff before closing a JeecgBoot-class engagement. (2) Integration config-write routes (editThirdAppConfig-style) are higher-value than they score: swapping IdP/messaging app credentials converts a low-priv write into a standing directory-sync + message-path interception position. (3) Row-level data-rule stores are authorization data, behind feature-gated writes — audit rule-write routes separately from feature gates.
Related pages¶
- Oct 10 HaiND MCP wave — sibling-gate diff rule this wave generalizes to id-source routes.
- Oct 9 Vikunja coordinated wave — sibling-surface drift family (v1/v2, REST/CalDAV) with the same "parallel surfaces disagree" genealogy.
- Sept 21 auth-method precedence page — check-order/absence failures across RBAC engines.
- Aug 6 agent tool policy/file-fetch boundaries — prior JeecgBoot leg (anonymous chat-attachment SSRF); same-product re-sweep now catches the whole annotation-absence wave.
- Oct 10 agent-harness evening wave — MCP config tables as credential vaults (JeecgBoot leg is the server-side twin of the client-side leaks).