## https://sploitus.com/exploit?id=B088BC12-BD35-50B2-BA20-290057557DE5
# 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](#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`](https://pypi.org/project/requests/) library. Install with
`pip install requests`.
## Usage
```bash
# 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:
```console
$ ./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:
```console
$ ./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:
```console
$ ./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:
```console
$ ./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`):
```text
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:
```text
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_.txt` records the throttle as
bursts of:
```text
IAMSecurityException ErrorCode: URL_ROLLING_THROTTLES_LIMIT_EXCEEDED,
RequestURI: "/showLogin.cc", RemoteAddr:
```
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](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
- [NVD: CVE-2026-11374](https://nvd.nist.gov/vuln/detail/CVE-2026-11374)
- ManageEngine service packs:
[ADSelfService Plus](https://www.manageengine.com/products/self-service-password/service-pack.html) ยท
[RecoveryManager Plus](https://www.manageengine.com/ad-recovery-manager/service-pack.html) ยท
[M365 Manager Plus](https://www.manageengine.com/microsoft-365-management-reporting/service-pack.html) ยท
[ADAudit Plus](https://www.manageengine.com/products/active-directory-audit/service-pack.html)
- [Bishop Fox Blog: Full Technical Analysis](https://bishopfox.com/blog/)