-
Previous Value Current Value Security: token-authenticated subject leaks to anonymous requests on Java 25 (auth bypass)
Token-authenticated subject leaks to anonymous requests on Java 25 (auth bypass)
-
State changed as build OD-8640 (16.8.5) is successful
-
OneDev
changed state to 'Closed' 2 weeks ago
Previous Value Current Value Open
Closed
-
Previous Value Current Value true
false
| Type |
Security Vulnerability
|
| Priority |
Critical
|
| Assignee | |
| Affected Versions |
16.7.3 -> 16.8.4
|
| Labels |
No labels
|
Issue Votes (0)
Affected: OneDev 16.8.4. The same code is in 16.7.3. It occurs when running on Java 25+, with Shiro 3.0.1.
Impact: after any request authenticated with an access token (HTTP Basic user:token or Bearer, REST or git over HTTPS), later unauthenticated requests on the same Jetty worker thread run as the token owner. On our instance anonymous internet users were served the admin account. Anonymous access was disabled, and no proxy injected credentials.
Reproduce:
Run OneDev on Java 25 and restart it. Anonymous GET /~api/users/me returns 401 every time. Send a few requests with Authorization: Basic <base64(user:token)> or Bearer . Repeat anonymous GET /~api/users/me. Most calls (25–60 of 60 in our tests) return 200 with the token owner. Run the same steps on Java 21: 0 of 60, even after 40 token calls. Cause:
BasicAuthenticationFilter and BearerAuthenticationFilter call ThreadContext.bind(accessToken.asSubject()) and never unbind. On Java 25+, Shiro's SubjectCallable uses ScopedValues and no longer creates a SubjectThreadState, so ThreadContext is never restored after the request. io.onedev.server.security.SecurityUtils.getSubject() checks ThreadContext.getSubject() before the scoped value. The stale subject on the pooled thread therefore wins, and the auth filters skip it because isAuthenticated() is true. Suggested fix: in ExceptionHandleFilter.cleanup:
try { super.cleanup(request, response, existing); } finally { // Shiro uses ScopedValues on Java 25+ and no longer restores ThreadContext ThreadContext.unbindSubject(); } Other ThreadContext.bind call sites may need the same audit.
Workaround: run OneDev on Java 21, e.g. wrapper.java.command in conf/wrapper.conf.