2026-10-01 08:16:22 -05:00
2026-10-01 08:16:22 -05:00
2026-10-01 08:16:22 -05:00
2026-10-01 08:16:22 -05:00
2026-10-01 08:16:22 -05:00
2026-10-01 08:16:22 -05:00
2026-10-01 08:16:22 -05:00

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 (!) 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.
  • It executes nothing. The primary probe omits the 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.
  • 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-fix means 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: PATCHED applies to this CVE only and does not indicate the status of other Check Point vulnerabilities.
  • Exploitation and compromise: VULNERABLE indicates 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.

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

S
Description
Safely detect Check Point CPM RCE CVE-2026-93616
Readme MIT
39 KiB
0 Stars 1 Watchers 0 Forks
Languages
Python 100%