Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CVE-2026-11374 ManageEngine AD360 Precondition Detection Script

A detection script for the exploit precondition of CVE-2026-11374, a predictable SSO-ticket flaw that leads to unauthenticated account takeover across the ManageEngine AD360 suite. The four in-scope products share the ManageEngineADSFramework:

Product Affected build Fixed build
ADSelfService Plus ≤ 6528 6529
RecoveryManager Plus ≤ 6320 6321
M365 Manager Plus ≤ 4816 4817
ADAudit Plus ≤ 8702 8703

In affected builds the SSO ticket is just System.currentTimeMillis() (a predictable timestamp) sampled at the victim's login, so it can be replayed through the CUSTOM_SSO_TICKET cookie to hijack that session. The fix replaces the ticket with UUID.randomUUID() (a sufficiently random identifier). The replay path is only reachable when the product is AD360-integrated: ADSFilter gates it on isProductIntegrated(). That gate is the precondition this tool checks for.

NOTE: this detector confirms the precondition, not the vulnerability itself. A POTENTIALLY_AFFECTED result is not a confirmed-vulnerable verdict — see below for more info.

Is it safe to run?

Yes. It's built for production and assessment use.

  • Nothing is exploited. The probe sends an invalid SSO ticket (1700000000000, a millisecond value far enough in the past that it can never be cache-resident), so no session is ever recovered. The server just tells us to clear the cookies we sent.
  • No target state changes. Every request is a plain GET, and the only cookies affected are the throwaway ones the tool sends.
  • No brute forcing or session resolution. The tool does not attempt the active timestamp-ticket resolution that would prove exploitability; that's exploitation rather than detection, and it's out of scope here (see Limitations).

What this can and cannot tell you

CVE-2026-11374's patch changed only how the ticket is generated (milliseconds to UUID); it did not change the cookie-replay path this probe exercises. As a result, a patched install responds byte-for-byte identically to a vulnerable one for any unauthenticated request.

What the tool can do, unauthenticated and non-destructively:

  • Confirm that a reachable in-scope product has the CustomSSO cookie-replay path active (that is, it is AD360-integrated), which is the CVE-2026-11374 exploit precondition.
  • Identify which of the four products it is, from the per-product session cookie.
  • Make a best-effort read of an asset build number as a hint. This is available on ADSelfService Plus, ADAudit Plus, and M365 Manager Plus; unavailable on RecoveryManager Plus.

What it can't do:

  • Tell vulnerable from patched. There is no safe, passive, unauthenticated signal for it. A POTENTIALLY_AFFECTED result means the precondition is met and you should go verify the patch level, not that the host is confirmed vulnerable.
  • Confirm exploitability. Proving a host is actually exploitable requires observing a minted ticket's format (a 13-digit number is vulnerable, a UUID is patched, both of which need authenticated or on-host visibility), reading conf/product.conf locally, or actively resolving a live ticket (real exploitation, intentionally not implemented here).
  • Fully trust a below-fixed ADSelfService Plus build number. ADSSP's ?build= is sometimes the real build and sometimes a frozen placeholder that reads below the fixed build, so a below-fixed value is ambiguous and the tool marks it inconclusive. A value at or above the fixed build is still a trustworthy patched signal, since the placeholder is always too low to reach it.

Requirements

  • Python 3.7+ and the requests library. Install with pip install requests.

Usage

# single host (prefer a URL or host:port; ports differ per product)
./cve_2026_11374_check.py https://adssp.example.com:8888

# multiple hosts (scheme optional: https is tried first, then http)
./cve_2026_11374_check.py host-a:8081 host-b:8365

# scan a list, one target per line ('#' comments allowed), compact output
./cve_2026_11374_check.py -f targets.txt --brief

# machine-readable output for pipelines
./cve_2026_11374_check.py -f targets.txt --json > results.json

Default ports differ per product (ADSelfService Plus 8888, ADAudit Plus 8081, M365 Manager Plus 8365, RecoveryManager Plus 8090), so pass a URL or host:port. A bare host defaults to 8888.

Options

Flag Description
targets One or more host, host:port, or https://host:port
-f, --targets-file FILE Read targets from a file (one per line; # comments)
--brief Single aligned line per target, good for scanning many hosts
--json Emit structured JSON results
--timeout SECS Per-request timeout (default: 15)
--no-build Skip the extra build-number request on a positive finding
--no-color Disable coloured output (also honours NO_COLOR and non-TTY)

Examples

An AD360-integrated console (verbose, the default). The second line spells out that this is the precondition and not a vulnerable verdict; the third is the best-effort build hint:

$ ./cve_2026_11374_check.py https://adssp.example.com:8888
[!] https://adssp.example.com:8888: POTENTIALLY_AFFECTED
      ADSelfService Plus: AD360-integrated, CustomSSO replay path active - precondition met. Not confirmed vulnerable; verify patch level (fixed build 6529).
      build: 6519 (below fixed 6529 - inconclusive: ?build= may be a stale placeholder or a hotfix)

A standalone install of the same product, where the replay path isn't active:

$ ./cve_2026_11374_check.py https://adssp.example.com:8888
[+] https://adssp.example.com:8888: UNAFFECTED
      ADSelfService Plus: standalone / not AD360-integrated (no cleanup), so the replay path isn't reachable here. Verify build >= 6529 regardless.

Scanning a list with one aligned line per host (--brief). Exit status is 1 if any host is POTENTIALLY_AFFECTED, otherwise 0, which is handy in scripts. The trailing note shows the identified product, plus the build number on a finding:

$ ./cve_2026_11374_check.py -f targets.txt --brief; echo "exit: $?"
POTENTIALLY_AFFECTED  https://host-a:8888  ADSelfService Plus 6519
POTENTIALLY_AFFECTED  http://host-b:8081   ADAudit Plus 8530
UNAFFECTED            http://host-c:8365   M365 Manager Plus
UNAFFECTED            https://host-d:443
INCONCLUSIVE          http://host-e:8888   ADSelfService Plus
ERROR                 host-f:8888  timeout
exit: 1

Machine-readable output (--json). Each result carries the verdict, the state and detail behind it, the identified product, and — on a finding — a build object: build is the number found, fixed_build the threshold for that product, patch_hint the directional call (likely_patched at or above the fixed build, otherwise inconclusive), and note a short explanation:

$ ./cve_2026_11374_check.py https://host-b:8081 --json
[
  {
    "target": "https://host-b:8081",
    "state": "potentially_affected",
    "detail": "ADAudit Plus: AD360-integrated, CustomSSO replay path active - precondition met ...",
    "product": "ADAudit Plus",
    "build": { "build": "8530", "fixed_build": "8703", "patch_hint": "inconclusive", "note": "below fixed 8703 - inconclusive: may be a hotfix" },
    "verdict": "POTENTIALLY_AFFECTED"
  }
]

Verdicts

Verdict Meaning
POTENTIALLY_AFFECTED AD360-integrated in-scope product; the CustomSSO cookie-replay path is active, so the CVE-2026-11374 exploit precondition is met. This is not a confirmed-vulnerable verdict; verify the patch level (see next steps).
UNAFFECTED The replay path is not reachable. Any of: a standalone in-scope product (not AD360-integrated — though it would become reachable if integrated later, so verify the build anyway); a ManageEngine ADS-framework product that isn't one of the four in scope; or no in-scope product on the response at all (not one of the four CVE-2026-11374 products, or a proxy that strips the session cookie).
INCONCLUSIVE An HTTP 400 — likely the IAM rate-limit lock masking the signal; retry after ~60 s.
ERROR Connection/timeout/TLS failure.

Exit codes

Code Meaning
0 No target returned POTENTIALLY_AFFECTED
1 At least one target is POTENTIALLY_AFFECTED
2 Usage error (bad arguments / unreadable targets file)

How it works

Send an invalid ticket plus an app tag to any CustomSSO URL (*.do):

GET /AppsHome.do
Cookie: CUSTOM_SSO_TICKET=1700000000000; CUSTOM_SSO_APP_TAG_NAME=AD360

On an AD360-integrated install, the chain ADSFilter → CustomSSOFilter → CookieSSOImpl runs its resolution branch (the tag names a different product, so appName != prodName), fails to resolve the bogus ticket, and emits cookie-cleanup response headers:

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; ...

A standalone install never runs that path and emits no cleanup. The product is identified from its fixed per-product Tomcat session cookie (JSESSIONIDADSSP for ADSelfService Plus, and so on), which is set on every response. The check keys on the headers rather than the status code, since an integrated ADSSP returns a 302 while ADAudit, M365, and RMP return 200 to the same probe.

Probe response Verdict
cleanup headers and a known in-scope product cookie POTENTIALLY_AFFECTED
product cookie, no cleanup, status ≠ 400 UNAFFECTED (standalone / not integrated)
no in-scope product cookie, status ≠ 400 UNAFFECTED (not one of the four products)
cleanup headers but an unrecognized product cookie UNAFFECTED (ManageEngine, not in scope)
HTTP 400 INCONCLUSIVE (likely the IAM rate-limit lock; see Limitations)

Limitations

  • POTENTIALLY_AFFECTED is not "confirmed vulnerable." It is the exploit precondition. Because the patch did not touch this code path, a patched build responds identically, so you have to confirm the patch level out of band (see next steps). That is the ceiling on what an unauthenticated observer can know.
  • No active confirmation. The tool never tries to resolve a real, current-time ticket. That technique can positively prove a host vulnerable, but it is active exploitation (it hijacks a live session), it is throttle-limited and probabilistic, and it needs an active victim session, so it is excluded from this detector.
  • HTTP 400 can be a rate-limit lock rather than a real answer. Zoho IAM applies a URL rolling throttle that caps a source IP at about 40 requests per 60 s, then locks that path for ~60 s. During a lock, requests return HTTP 400 and the integration signal is masked. The tool reports these as INCONCLUSIVE; retry from that IP after about a minute.
  • Behind a reverse proxy, a console that strips or rewrites the session cookie can be misreported as UNAFFECTED. Confirm directly if you expect a ManageEngine product there.
  • Build numbers are hints, not verdicts. ADAudit (?v=) and M365 (?bN=) always expose the real build; ADSSP's ?build= is sometimes real and sometimes a frozen placeholder that reads below the fixed build, so a below-fixed ADSSP value is ambiguous; RMP exposes none. In every case a build at or above the fixed build strongly indicates patched, and below it is inconclusive.

Next steps for defenders

If a target comes back POTENTIALLY_AFFECTED, treat it as exposed and worth confirming, then:

  1. Confirm the patch level directly (the tool can't). Read conf/product.conf (build_number) on the host and compare it against the fixed build for that product (ADSelfService Plus 6529, RecoveryManager Plus 6321, M365 Manager Plus 4817, ADAudit Plus 8703). Interpret it directionally: a build at or above the fixed build is a strong indicator the host is patched, since the UUID fix is in that tree. A build below the fixed build is inconclusive rather than proof of vulnerability, because out-of-band hotfixes and backports can apply the UUID fix without bumping the build number; in that case fall back to the ticket-format check below.

  2. Check the minted ticket format if you can log in. The CUSTOM_SSO_TICKET cookie isn't HttpOnly, so after authenticating you can read its value: a 13-digit number means vulnerable, a UUID means patched. This is the clearest positive check.

  3. Update to the fixed build (or later) on any host that is vulnerable or that you can't confirm is patched. The fix replaces the predictable ticket with a random UUID.

  4. Hunt for exploitation attempts. On the host, serverOut_<date>.txt records the throttle as bursts of:

    IAMSecurityException ErrorCode: URL_ROLLING_THROTTLES_LIMIT_EXCEEDED,
    RequestURI: "/showLogin.cc", RemoteAddr: <source IP>
    

    A spike of URL_ROLLING_THROTTLES_LIMIT_EXCEEDED on SSO/login dispatch paths from a single source IP is a strong indicator of ticket brute forcing. Also review recent successful logins and session activity for signs of takeover.

  5. Reduce exposure in the meantime. Restrict network access to the AD360 suite (it shouldn't be internet-facing), and if the replay path isn't needed, disabling the CustomSSO integration removes the precondition entirely.

Remediation

Update the affected product to at least the fixed build (ADSelfService Plus 6529, RecoveryManager Plus 6321, M365 Manager Plus 4817, ADAudit Plus 8703) or later. The fix replaces the predictable System.currentTimeMillis() SSO ticket with UUID.randomUUID().

License

This code is distributed under an MIT license.

Legal Disclaimer

Usage of this tool for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state, and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program.

See Also

About

Detection script for CVE-2026-11374

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages