
Safe unauthenticated patch-state checker for Check Point CPM RCE CVE-2026-93616; probes the UpgradeSvcRemote SOAP login on TCP 19009 without exploiting.
An unauthenticated patch-state check for CVE-2026-93616, the unauthenticated directory
traversal in the Check Point Security Management (CPM) server that reaches
Runtime.exec(), fixed in
sk1000171 on 2026-09-22. CWE-22,
CVSS 9.8 Critical (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H), exploited in the
wild. Check Point is the CNA; the issue was identified from in-the-wild exploitation
rather than an external report.
The CPM server exposes SOAP web services on TCP 19009. The UpgradeSvcRemote login
method loginAsApplicationReadOnlyPublicSession forwards its targetVersion parameter
into a filesystem path passed to Runtime.exec without validation, so an unauthenticated
caller can redirect execution to an attacker-chosen script. The fix adds a Bean
Validation pattern to targetVersion that rejects a path separator.
The script reports whether that fix is present on each target. It sends one illegal
character in targetVersion, never a traversal sequence, and does not authenticate,
upload a file, or execute anything on the target. A result other than VULNERABLE does
not by itself indicate that a target is patched; see
Interpreting non-vulnerable results.
End-of-support builds are handled explicitly. On trains older than R81.10 the login method
takes fewer parameters, so the primary probe is rejected by XML unmarshalling before patch
state can be read. When a Check Point server rejects the probe that way, the script sends
one follow-up call in the older parameter shape. If the login method then dispatches, the
target is an affected build for which Check Point never published a fix, and the verdict is
VULNERABLE with the reason affected-eos-no-fix. The follow-up call carries no mode
and no targetVersion, so it too sends nothing to validate and nothing to traverse. This
closes a gap in which a legacy server would otherwise read as unidentifiable.
# single target (port defaults to 19009)
./cve_2026_93616_check.py mgmt.example.com
# explicit port
./cve_2026_93616_check.py mgmt.example.com:19009
# several targets
./cve_2026_93616_check.py mds-a.example.com mds-b.example.com
# scan a list, one target per line ('#' comments allowed), compact output
./cve_2026_93616_check.py -f targets.txt --brief
# machine-readable output for pipelines
./cve_2026_93616_check.py -f targets.txt --json > results.json
Python 3.8+, standard library only — no third-party packages.
Run the tool against the management servers (Security Management, Multi-Domain Management, Log, Multi-Domain Log, and SmartEvent), not the gateways. The check targets the CPM SOAP listener on TCP 19009, which the gateways do not run.
| Flag | Description |
|---|---|
TARGET | One or more HOST[:PORT] targets; the port defaults to 19009 |
-f, --targets-file FILE | Read targets from a file (one per line; # comments) |
-p, --port PORT | Default port when a target omits one (default: 19009) |
--timeout SECS | Per-probe timeout (default: 15) |
--workers N | Concurrent targets (default: 16); output stays in input order |
-b, --brief | Single aligned line per target, for scanning many hosts |
--json | Emit structured JSON, including the probe markers per target |
--no-color | Disable coloured output (also honours NO_COLOR and non-TTY) |
Vulnerable server (default output; the [!] marker and VULNERABLE are shown in red on
a TTY):
$ ./cve_2026_93616_check.py mgmt.example.com
[!] mgmt.example.com:19009: VULNERABLE [dispatched-without-validation]
loginAsApplicationReadOnlyPublicSession accepted an illegal targetVersion and dispatched it; the fix's input validation is absent
Patched server:
$ ./cve_2026_93616_check.py mgmt-dr.example.com
[+] mgmt-dr.example.com:19009: PATCHED [validation-constraint-present]
the server rejected an illegal targetVersion with the input-validation constraint added by the fix (Jumbo Hotfix R82.10 Take 45 or equivalent)
A Check Point management server that answered but did not dispatch the login method, so patch state could not be read:
$ ./cve_2026_93616_check.py log.example.com
[?] log.example.com:19009: INCONCLUSIVE [cpm-no-dispatch]
a Check Point management fault came back, but the UpgradeSvcRemote login method did not dispatch, so patch state could not be read
Multiple targets with --brief:
$ ./cve_2026_93616_check.py -f targets.txt --brief; echo "exit: $?"
VULNERABLE mgmt.example.com:19009 dispatched-without-validation
PATCHED mgmt-dr.example.com:19009 validation-constraint-present
INCONCLUSIVE log.example.com:19009 cpm-no-dispatch
UNAFFECTED web.example.com:19009 not-cpm
ERROR offline.example.com:19009 unreachable
exit: 1
JSON output with --json. The probe markers are included so the verdict can be checked
against what the response actually contained:
$ ./cve_2026_93616_check.py mgmt.example.com mgmt-dr.example.com --json
[
{
"target": "mgmt.example.com:19009",
"verdict": "VULNERABLE",
"reason": "dispatched-without-validation",
"detail": "loginAsApplicationReadOnlyPublicSession accepted an illegal targetVersion and dispatched it; the fix's input validation is absent",
"http_status": 500,
"probe": {
"http_status": 500,
"saw_validation": false,
"saw_dispatch": true,
"saw_cpm": true,
"note": ""
}
},
{
"target": "mgmt-dr.example.com:19009",
"verdict": "PATCHED",
"reason": "validation-constraint-present",
"detail": "the server rejected an illegal targetVersion with the input-validation constraint added by the fix (Jumbo Hotfix R82.10 Take 45 or equivalent)",
"http_status": 500,
"probe": {
"http_status": 500,
"saw_validation": true,
"saw_dispatch": true,
"saw_cpm": true,
"note": ""
}
}
]
The check is intended for use against production systems:
!) in targetVersion; that one character is enough to trip the patched build's
validation, and it cannot escape a directory the way a path separator would. The legacy
follow-up probe carries no targetVersion at all.domainId element, so on an unpatched
server the method raises a NullPointerException while building its parameters, before
ReflectionUtils reaches Runtime.exec. Even without that, the probe value resolves
to a directory that does not exist, so there is no script to run. The legacy probe sends
only an application name and timeout, so it has nothing to build a path from.| # | Call | When | Purpose |
|---|---|---|---|
| 1 | UpgradeSvcRemote.loginAsApplicationReadOnlyPublicSession (current shape) | always | The patch-state probe |
| 2 | UpgradeSvcRemote.loginAsApplicationReadOnlyPublicSession (legacy shape) | only if call 1 is rejected at unmarshalling by a Check Point server | Confirms an end-of-support build |