Check Point Security Management Unauthenticated RCE — Vulnerability Detection Script
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.
Usage
# 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.
Options
| 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) |
Examples
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": ""
}
}
]
Is it Safe to Run?
The check is intended for use against production systems:
- It does not authenticate. No session, credential, or client certificate is presented. It observes how the server handles one malformed parameter, which depends on the installed build.
- It never sends a traversal sequence. The primary probe puts a single illegal character
(
!) intargetVersion; 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 notargetVersionat all. - It executes nothing. The primary probe omits the
domainIdelement, so on an unpatched server the method raises aNullPointerExceptionwhile building its parameters, beforeReflectionUtilsreachesRuntime.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. - It does not upload a file. The file-write half of the exploit chain uses a different service, which this tool never calls.
- It opens one TCP connection per probe and closes it after reading the response.
Request and log footprint
| # | 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 |
The probes are logged to $MDS_FWDIR/log/cpm.elg. A patched server logs a validation
rejection; an unpatched server logs the dispatch and its NullPointerException; an
end-of-support server logs an unmarshalling error for call 1 and a dispatch for call 2.
Both probes send the application name bf-check (call 1 also sends the value bf-check!),
which is recognizable in the log as this tool rather than an attack.
How it Works
The fix adds a Bean Validation pattern, ^[A-Za-z0-9._-]*$, to the targetVersion
parameter of every UpgradeSvcRemote method that takes it. The pattern permits a version
string but not a path separator, which closes the traversal into Runtime.exec. The
check sends one illegal character and reads which way the server responds:
| Response to one illegal character | Build | Verdict |
|---|---|---|
targetVersion contains illegal characters |
fixed (Take 45 and equivalents) | PATCHED |
| method dispatched, no validation message | affected (R81.10 and later) | VULNERABLE |
| probe rejected by XML unmarshalling, then legacy shape dispatches | affected, end-of-support | VULNERABLE |
| Check Point fault, method did not dispatch | either | INCONCLUSIVE |
Dispatch is confirmed by the full method name
UpgradeSvcRemote.loginAsApplicationReadOnlyPublicSession in the response. The bare
string UpgradeSvcRemote is not usable: a Check Point fault lists every service in a
namespace map, so that substring appears in any CPM response whether or not this method
ran. The check matches only the full method name.
Older builds and the follow-up probe
The parameter shape of loginAsApplicationReadOnlyPublicSession changed across releases.
R81.10 and later accept the mode and targetVersion elements the primary probe sends;
builds older than R81.10 do not, and reject the primary probe at XML unmarshalling (a
generalRemoteFaultException whose server stack begins in the JAXB/CXF unmarshaller)
before any patch-state signal is produced. Because those older trains are all
end-of-support and were never given a fix, that rejection is itself the finding.
The script recognises it — a Check Point fault plus an unmarshaller stack — and sends one
follow-up call in the older shape (applicationName and timeout only). If the login
method dispatches for that shape, the target is confirmed as an affected, unpatchable build
and reported VULNERABLE / affected-eos-no-fix. The follow-up is sent only in this case,
so a current, supported server is still checked with a single request.
It reports patch state, not a version
The check observes the behaviour of the patched code path and does not read a version string. It does not report which Jumbo Hotfix take a patched server is running.
Interpreting non-vulnerable results
VULNERABLE and PATCHED are each based on an observed server response. INCONCLUSIVE
and ERROR mean the probe did not classify the target, so its patch state is unknown.
For this reason INCONCLUSIVE is reported separately from PATCHED.
Verdicts
Every verdict carries a short reason tag. --brief prints it as the third column and
--json carries it as reason.
| Verdict | Reason tag | Meaning |
|---|---|---|
VULNERABLE |
dispatched-without-validation |
The login method accepted an illegal targetVersion and dispatched it. The fix is not present. |
VULNERABLE |
affected-eos-no-fix |
An end-of-support build (older than R81.10): the login method dispatches for the legacy parameter shape and no fix was ever published for the train. It cannot be patched; upgrade to a supported, fixed version. |
PATCHED |
validation-constraint-present |
The server rejected the illegal value with the validation message added by the fix. Applies to this CVE only. |
INCONCLUSIVE |
cpm-no-dispatch |
A Check Point management fault came back, but the login method did not dispatch, so patch state could not be read. |
INCONCLUSIVE |
legacy-no-dispatch |
A Check Point server rejected the current login shape and the legacy follow-up did not dispatch either, so the build could not be identified. |
INCONCLUSIVE |
legacy-probe-failed |
A Check Point server rejected the current login shape, but the legacy follow-up returned nothing usable (for example, a connection dropped between the two calls). |
UNAFFECTED |
not-cpm |
The endpoint did not answer as a Check Point CPM SOAP service. |
ERROR |
unreachable |
The TCP connection, TLS handshake, or HTTP exchange failed. |
Exit codes
| Code | Meaning |
|---|---|
0 |
No target was VULNERABLE |
1 |
At least one target is VULNERABLE |
2 |
Usage error (bad arguments, invalid target, or unreadable targets file) |
130 |
Interrupted (Ctrl-C) |
Exit code 0 covers PATCHED, UNAFFECTED, INCONCLUSIVE, and ERROR. To
distinguish a patched target from one that could not be classified, read the verdict
(the first column of --brief or the verdict field of --json) rather than the exit
code.
Limitations
- Behavioural, not version based: the check confirms that the input-validation constraint
is present on
UpgradeSvcRemote. It does not test the file-upload or session-minting primitives that the full exploit chain also uses; those are out of scope for a safe check. - Branch coverage: the primary probe was validated against an R82.10 pair, one on Take 44 and one on Take 45. The same fix ships across R82, R81.20, and R81.10; the constraint is expected to behave identically there, but each branch was not separately A/B tested.
- End-of-support detection is version-based, not patch-state:
affected-eos-no-fixmeans the target is a build older than R81.10, all of which are listed as affected and none of which Check Point patched. The check confirms the build shape and dispatch; it does not and cannot read a patch state, because no patch exists for those trains. The legacy path was confirmed against R80.40; other pre-R81.10 trains share the older parameter shape but were not each tested. - Scope:
PATCHEDapplies to this CVE only and does not indicate the status of other Check Point vulnerabilities. - Exploitation and compromise:
VULNERABLEindicates that the fix is not present. The tool does not execute code on the target and does not detect prior compromise. For compromise indicators, see the sk1000171 advisory. - Reachability: results reflect what the server exposes to the network location the tool runs from. By default TCP 19009 listens on all interfaces.
Remediation
Install the fix from sk1000171: Jumbo Hotfix Accumulator R82.10 Take 45, R82 Take 127, R81.20 Take 170, R81.10 Take 192, or the R82.20 Security Hotfix. Earlier takes on those branches, and all of R80 through R81 (end of support), are affected. There is no LivePatch for this issue, so the fix changes files on disk.
Additional notes:
- Restrict TCP 19009 to trusted management hosts. This is Check Point's primary mitigation and limits exposure on systems that cannot be patched immediately.
- Code execution runs as root inside the process that holds the management policy and the internal certificate authority. Treat any period of exposure before patching as one in which that trust could have been reached, and plan credential and certificate rotation accordingly.
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.