# A Millisecond of Predictability: Why CVE-2026-11374 Is Hard to Exploit

ManageEngine AD360 SSO ticket was the millisecond login timestamp, letting an attacker replay a guessed cookie as any user including admins.

- Payout: Research
- Severity: critical
- Program: ManageEngine AD360
- Write-up by: 0xmanhnv
- Disclosed: 2026-07-21
- CVE: CVE-2026-11374
- Write-up quality: 8/10
- Original: https://bishopfox.com/blog/millisecond-of-predictability-why-cve-2026-11374-hard-to-exploit
- Library page: https://bugbountylibrary.com/r/blog%3Abishopfox-com-blog-millisecond-of-predictability-why-cve-2026-11374-hard-to-exploit
- Tags: area:web domain:web impact:ato impact:privesc kind:research platform:research sev:critical tech:sso technique:chained technique:source-review vuln:access-control vuln:account-takeover vuln:auth-bypass

**Target:** ADSelfService Plus / RecoveryManager Plus / M365 Manager Plus / ADAudit Plus SSO cookie handler
**Root cause:** Per-product SSO ticket generators built the ticket ID from System.currentTimeMillis() instead of a random UUID, and the server cached identity keyed by that exact string, making the CUSTOM_SSO_TICKET cookie value the predictable timestamp itself.
**Steps:**
1. Diff last vulnerable build vs fix; observe generateTicket() changed from time.toString() to UUID.randomUUID().toString()
2. Predict candidate ticket as 13-digit millisecond timestamp around victim login.
3. Send `GET /<protected>.do` with `Cookie: CUSTOM_SSO_TICKET=<candidate>; CUSTOM_SSO_APP_TAG_NAME=<other-product>` (tag must differ from running product to enter CookieSSOImpl resolution branch).
4. On cache hit and matching source IP, server returns auto-submitting /adsf/jsp/common/LoginFormSubmit.jsp pre-filled with victim username and raw ticket as password.
5. POST that form to /j_security_check with AUTHRULE_NAME routing to CustomSSOAuthHandler; session becomes authenticated as victim.
6. Follow-up request to protected page returns 200 instead of 302.
**How found:** Binary diff of last vulnerable build against fix showed only the SSO ticket generator changed; source review confirmed ticket equals System.currentTimeMillis() and is cached verbatim as the cookie key, prompting runtime tracing of ticket issuance and replay.
**Exploitation:** Attacker must source requests from the victim's IP (shared NAT or internal foothold), predict login instant within a tight window, and search candidates at ~40 req/60s under the IAM throttle, locking out for 60 seconds on excess; a hit yields an auto-submit login form that establishes a full session, validated end-to-end to administrator on AD360 console.
**Escalation:** Replaying a cache-resident admin ticket from unauthenticated context yields full administrator session on the AD360 console, pivoting across integrated products.
**Insight:** When a token is cached server-side keyed by its own string, check whether the generator uses a clock or counter rather than CSPRNG; also note that a mismatched app-tag cookie can be required to enter a cross-product SSO resolution branch that same-product tags skip.

## Proof of concept (copied exactly from the original report)

**Vulnerable ticket generator using millisecond timestamp**

```
public synchronized Ticket generateTicket(HttpServletRequest request, Credential cred) { 
 Long time = Long.valueOf(System.currentTimeMillis()); 
 String ticketId = time.toString(); // the SSO ticket IS the millisecond timestamp 
 ... 
 ticketObj.ticket = ticketId; 
}
```

**Two-cookie replay request against protected .do**

```
The attacker sends a GET to any protected *.do carrying two cookies: CUSTOM_SSO_TICKET=<candidate> and CUSTOM_SSO_APP_TAG_NAME naming a product other than the one being hit. That mismatched tag matters because the cookie-SSO handler (CookieSSOImpl) only enters its resolution branch when the tagged app differs from the one it is running as, so a tag naming the same product is ignored. In that branch it calls TicketHandler.getTicket(), which looks the candidate up in the local ticket cache (the same cache the victim's login populated), then checks that the ticket is valid and that the request's source address matches the IP it was issued to. This validates the ticket but does not log the attacker in. On success, the handler stashes the resolved identity in a new server-side session and, rather than serving the protected page, returns an auto-submitting login form (/adsf/jsp/common/LoginFormSubmit.jsp) pre-filled with the victim's username and the raw ticket as the password.
```

**Safe detection probe with bogus ticket**

```
GET /AppsHome.do 
Cookie: CUSTOM_SSO_TICKET=1700000000000; CUSTOM_SSO_APP_TAG_NAME=AD360

On an AD360-integrated install, the cookie-SSO handler runs its resolution branch, fails to resolve the bogus ticket (an old, fixed millisecond that can never be cache-resident), and tells the client to clear the SSO cookies: 

Set-Cookie: CUSTOM_SSO_TICKET=removed; Max-Age=0; ... 
Set-Cookie: CUSTOM_SSO_APP_NAME=removed; Max-Age=0; ... 
Set-Cookie: CUSTOM_SSO_APP_TAG_NAME=removed; Max-Age=0; ...
```

