Share
## 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/)