top of page

Check Point PHP2Shell Exploited: Backdoor to ESXi Wiper

Writer: Inception Security
Inception Security
2 hours ago
11 min read
Bright cutaway comic of a gateway building: an intruder enters through the portal, rides a root elevator, leaves a backdoor crate, then reaches the hypervisor loft over SSH with a wiper, while the defender outside says patch, then check the box.

What this means for your clients: If your firewall has a remote access login page on the internet, ask your IT provider to prove it is patched and checked for a break-in, because here it became the doorway to everything behind it.


Between October 30 and November 4, someone came in through the Mobile Access portal on unpatched Check Point Security Gateway appliances and became root. In cases Check Point Research (CPR) investigated, the attackers mapped the network with the gogo scanner, logged into a VMware ESXi host over SSH as vpxuser, and ran a wiper. Check Point's advisory says fixes shipped October 23, and only a couple of dozen unpatched systems, in a single region, were compromised. CPR says the October 30 wave targeted one country and a November 2 wave reached several, with no destruction seen.


That week should worry MSPs, because patching does not show whether someone is already inside. Your firewall is a server you do not watch, and EDR almost never runs on Gaia. Our October PHP2Shell post covered web servers; this is an edge appliance, a Gaia root bug, and a pivot to the hypervisor.


How the Check Point PHP2Shell chain works


Each step worked because something trusted a caller it should not have. PHP2Shell, tracked as CVE-2026-109618 and CVE-2026-109619, gave the attackers code execution as the portal's service account without logging in. CVE-2026-123039, a local privilege escalation in the DynamicCLI service, made that root: per sk1000362, the root dynamic_server daemon takes commands on a world-writable socket without checking who is connecting.


With root, they planted zz*.tpl webshell files in the portal's login page templates, then launched a standalone backdoor CPR calls TSGA. It relaunches from the boot persistence directory, injects its TSProxy library into appliance processes so attacker traffic rides inside existing services, listens on TCP 11072, and can steal configuration, persist through backups and snapshots, and tamper with firewall logs.


The last hop abused a trusted account too. vCenter uses vpxuser to manage ESXi, so it carries hypervisor authority, and the attackers used it over SSH from the gateway. Their destroy_v6 wiper deletes files recursively, zero-fills them unless run in fast mode, and leaves DREAM_ANNEXATION.txt, a politically themed note rejecting ransom. CPR does not attribute this with confidence, and neither do we.


What this means for a company


Root on the perimeter is worse than root on a server. The gateway holds remote access configuration and the logs you would investigate with, so edits can mislead you and a poisoned backup can reinfect you. CPR saw no sector preference, so being small protects nobody.


How to mitigate


Check Point says to install the November Jumbo Hotfix on all Management Servers and Security Gateways, internet-facing Mobile Access first. sk1000302 and sk1000362 list the fixed builds:


  • Jumbo Hotfix Accumulator for R82.20, Take 14 or later

  • Jumbo Hotfix Accumulator for R82.10, Take 52 or later

  • Jumbo Hotfix Accumulator for R82, Take 132 or later

  • Jumbo Hotfix Accumulator for R81.20, Take 174 or later

  • Spark Firewalls R82.00.11, build B998002843 or later (PHP2Shell)

  • Spark Firewalls R81.10.17, build B996005005 or later (PHP2Shell)


The standalone PHP2Shell hotfix needs a reboot, and on R81.20 with Mobile Access it must sit on Jumbo Take 166 or higher. Spark boxes are exposed to PHP2Shell through portals like UserCheck, not to the DynamicCLI bug, and still need the firmware. If you cannot patch today, sk1000362's stopgap, stopping and disabling dynamic_server, covers only the DynamicCLI bug and breaks Dynamic CLI commands. For PHP2Shell, sk1000302 gives a cp-httpd.conf change and portal restart, and R82.20 gateways get an IPS signature even with IPS off. Use each SK's exact steps.


Then check for indicators even on patched boxes. Pull unused Mobile Access off the internet. On ESXi, disable SSH when idle, allow it only from management hosts, never from a gateway, and review vpxuser use outside vCenter.


How to hunt


We have not run these in a live SIEM, so test each before trusting a clean result. Searches start at 2026-10-23T07:00:00Z, the day the fixes shipped. Anything starting with YOUR_, plus the fields named under each Field mapping note, is a placeholder. In Kibana and LogScale, set the time picker from October 23 through now, since the query filters only narrow it.


On-box check first


Run these read-only commands in Expert mode on each gateway or cluster member. Adjust the R81.20 session path for your version.


Manual read-only check (shell on the box, not a hunt or SIEM query)


# Check Point gateway, Expert mode, read-only
grep -rl "upload_progress_pwn" /opt/CPcvpn-R81.20/tmp/ 2>/dev/null
ls -la /tmp/CPconfigdaemon /tmp/.bd.log /tmp/.up.tpl /tmp/.p_sock /lib/libcpfwcommon.so 2>/dev/null
find / -xdev \( -name 'zz*.tpl' -o -name 'challenge_render.tpl' -o -name 'slot_CPconfigdaemon' \) 2>/dev/null
ps auxw | grep -i '[c]pconfigdaemon'
netstat -tln 2>/dev/null | grep ':11072'
grep "Received =" /var/log/dynamicCli.log | grep -vF "CPprofile.sh >> /dev/null; export SP_DISTRIBUTE=0; set -o noglob;"
grep "Rejected connection from unauthorized uid" /var/log/dynamicCli.log
ls -l /tmp/dynamic_socket

# ESXi host shell, read-only
ls -la /tmp/des 2>/dev/null
find /vmfs/volumes -name 'DREAM_ANNEXATION.txt' 2>/dev/null
grep -h "vpxuser" /var/log/auth.log

A "Received =" line without the legitimate Clish wrapper may mean a command ran as root through the socket, which sk1000362 says requires investigation. A "Rejected connection from unauthorized uid" line means the fix blocked an escalation attempt, especially with an unexpected UID such as 99, so correlate bursts with portal and authentication logs. A fixed /tmp/dynamic_socket shows srw-rw---- root config (0660), so srwxrwxrwx (0777) means the fix is missing.


Coverage test: are the logs even there


Every gateway and ESXi host you manage should appear with recent events; a missing one is a blind spot.


Field mapping: The #Vendor value is a placeholder.


Splunk


(index=YOUR_CHECKPOINT_INDEX OR index=YOUR_ESXI_SYSLOG_INDEX OR index=YOUR_NETWORK_INDEX OR index=YOUR_GAIA_SYSLOG_INDEX) earliest=-7d latest=now
| eval device=coalesce(dvc, host)
| stats count AS events max(_time) AS last_seen BY index device
| convert ctime(last_seen)
| sort last_seen

Elastic


FROM YOUR_CHECKPOINT_INDEX, YOUR_ESXI_SYSLOG_INDEX, YOUR_NETWORK_INDEX, YOUR_GAIA_SYSLOG_INDEX METADATA _index
| WHERE @timestamp >= NOW() - 7 days
// Check Point rows group by gateway (observer.name); syslog rows fall back to host.name
| EVAL device = COALESCE(observer.name, host.name)
| STATS events = COUNT(*), last_seen = MAX(@timestamp) BY _index, device
| SORT last_seen ASC
| LIMIT 10000

CrowdStrike


// set the time picker to the last 7 days
#Vendor=/check ?point/i or #repo=YOUR_ESXI_REPO or #repo=YOUR_NETWORK_REPO or #repo=YOUR_SYSLOG_REPO
| case {
    #repo=YOUR_ESXI_REPO | source := "esxi_syslog";
    #repo=YOUR_NETWORK_REPO | source := "network";
    #repo=YOUR_SYSLOG_REPO | source := "gaia_esxi_syslog";
    * | source := "checkpoint" }
// Check Point rows group by gateway (observer.name); syslog rows fall back to host.name
| coalesce([observer.name, host.name, "unknown"], as=device)
| groupBy([source, device], function=[count(as=events), max(@timestamp, as=last_seen)])
| sort(last_seen, order=asc, limit=10000)

Microsoft KQL


union isfuzzy=true
    (CommonSecurityLog | where TimeGenerated > ago(7d) | where DeviceVendor has "Check Point" | extend Source = "Check Point", Device = DeviceName),
    (Syslog | where TimeGenerated > ago(7d) | where HostName in~ ("YOUR_ESXI_HOST_1", "YOUR_ESXI_HOST_2") | extend Source = "ESXi", Device = HostName),
    (CommonSecurityLog | where TimeGenerated > ago(7d) | where DeviceVendor has "YOUR_INTERNAL_FW_VENDOR" | extend Source = "Network", Device = DeviceName),
    (Syslog | where TimeGenerated > ago(7d) | where HostName in~ ("YOUR_GATEWAY_HOST_1", "YOUR_GATEWAY_HOST_2") | extend Source = "Gaia", Device = HostName)
| summarize Events = count(), LastSeen = max(TimeGenerated) by Source, Device
| order by LastSeen asc

Hunt 1: the two scanning addresses and port 11072


This finds traffic involving the two Check Point addresses or TSGA's TCP port 11072.


Field mapping: The #Vendor value is a placeholder; fields follow CIM, ECS, Falcon Next-Gen SIEM and CommonSecurityLog, so confirm your parser fills source and destination.


False positives: A blocked hit from either address is scanning. Port 11072 matters only when the destination is a gateway interface.


Splunk


index=YOUR_CHECKPOINT_INDEX earliest=1792738800 latest=now (src IN ("130.94.19.204", "130.94.19.168") OR dest IN ("130.94.19.204", "130.94.19.168") OR dest_port=11072)
| stats count AS events values(action) AS actions values(dest_port) AS ports min(_time) AS first_seen max(_time) AS last_seen BY src dest
| convert ctime(first_seen) ctime(last_seen)
| sort - events

Elastic


FROM YOUR_CHECKPOINT_INDEX
| WHERE @timestamp >= TO_DATETIME("2026-10-23T07:00:00.000Z")
| WHERE CIDR_MATCH(source.ip, "130.94.19.204/32", "130.94.19.168/32") OR CIDR_MATCH(destination.ip, "130.94.19.204/32", "130.94.19.168/32") OR destination.port == 11072
| STATS events = COUNT(*), actions = VALUES(event.action), ports = VALUES(destination.port), first_seen = MIN(@timestamp), last_seen = MAX(@timestamp) BY source.ip, destination.ip
| SORT events DESC
| LIMIT 10000

CrowdStrike


#Vendor=/check ?point/i
| test(@timestamp >= 1792738800000)
| source.ip=/^130\.94\.19\.(204|168)$/ or destination.ip=/^130\.94\.19\.(204|168)$/ or destination.port=11072
| default(field=[source.ip, destination.ip, event.action], value="none")
| groupBy([source.ip, destination.ip], function=[count(as=events), collect([event.action, destination.port]), min(@timestamp, as=first_seen), max(@timestamp, as=last_seen)])
| sort(events, order=desc, limit=10000)

Microsoft KQL


let iocs = dynamic(["130.94.19.204", "130.94.19.168"]);
CommonSecurityLog
| where TimeGenerated >= datetime(2026-10-23T07:00:00Z)
| where DeviceVendor has "Check Point"
| where SourceIP in (iocs) or DestinationIP in (iocs) or DestinationPort == 11072
| summarize Events = count(), Actions = make_set(DeviceAction), Ports = make_set(DestinationPort), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DeviceName
| order by Events desc

Hunt 2: SSH into the hypervisor network


This lists SSH into your ESXi range from gateways or outside the management subnet, flagging gateway sessions.


Field mapping: Use internal firewall or flow logs, because a gateway may not log connections it originates.


False positives: Allowlist backup and monitoring tools by host. We know of no benign gateway session.


Splunk


index=YOUR_NETWORK_INDEX dest_port=22 earliest=1792738800 latest=now
| where cidrmatch("YOUR_ESXI_CIDR", dest) AND (NOT cidrmatch("YOUR_MGMT_CIDR", src) OR in(src, "YOUR_GATEWAY_INTERNAL_IP_1", "YOUR_GATEWAY_INTERNAL_IP_2"))
| eval from_gateway=if(in(src, "YOUR_GATEWAY_INTERNAL_IP_1", "YOUR_GATEWAY_INTERNAL_IP_2"), "yes", "no")
| stats count AS sessions values(action) AS actions min(_time) AS first_seen max(_time) AS last_seen BY src dest from_gateway
| convert ctime(first_seen) ctime(last_seen)
| sort - from_gateway - sessions

Elastic


FROM YOUR_NETWORK_INDEX
| WHERE @timestamp >= TO_DATETIME("2026-10-23T07:00:00.000Z") AND destination.port == 22
| WHERE CIDR_MATCH(destination.ip, "YOUR_ESXI_CIDR") AND (NOT CIDR_MATCH(source.ip, "YOUR_MGMT_CIDR") OR CIDR_MATCH(source.ip, "YOUR_GATEWAY_INTERNAL_IP_1/32", "YOUR_GATEWAY_INTERNAL_IP_2/32"))
| EVAL from_gateway = CIDR_MATCH(source.ip, "YOUR_GATEWAY_INTERNAL_IP_1/32", "YOUR_GATEWAY_INTERNAL_IP_2/32")
| STATS sessions = COUNT(*), first_seen = MIN(@timestamp), last_seen = MAX(@timestamp) BY source.ip, destination.ip, from_gateway
| SORT from_gateway DESC, sessions DESC
| LIMIT 10000

CrowdStrike


#repo=YOUR_NETWORK_REPO
| test(@timestamp >= 1792738800000)
| destination.port=22
| cidr(field=destination.ip, subnet="YOUR_ESXI_CIDR")
| source.ip=*
| case {
    cidr(field=source.ip, subnet=["YOUR_GATEWAY_INTERNAL_IP_1/32", "YOUR_GATEWAY_INTERNAL_IP_2/32"]) | from_gateway := "yes";
    !cidr(field=source.ip, subnet="YOUR_MGMT_CIDR") | from_gateway := "no" }
| groupBy([source.ip, destination.ip, from_gateway], function=[count(as=sessions), min(@timestamp, as=first_seen), max(@timestamp, as=last_seen)])
| sort([from_gateway, sessions], type=[string, number], order=[desc, desc], limit=10000)

Microsoft KQL


CommonSecurityLog
| where TimeGenerated >= datetime(2026-10-23T07:00:00Z)
| where DestinationPort == 22
| where ipv4_is_in_range(DestinationIP, "YOUR_ESXI_CIDR") and (not(ipv4_is_in_range(SourceIP, "YOUR_MGMT_CIDR")) or SourceIP in ("YOUR_GATEWAY_INTERNAL_IP_1", "YOUR_GATEWAY_INTERNAL_IP_2"))
| extend FromGateway = SourceIP in ("YOUR_GATEWAY_INTERNAL_IP_1", "YOUR_GATEWAY_INTERNAL_IP_2")
| summarize Sessions = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, FromGateway, DeviceName
| order by FromGateway desc, Sessions desc

Hunt 3: vpxuser logging in over SSH


This pulls successful SSH logins as vpxuser from ESXi auth logs, excluding vCenter.


Field mapping: Every version parses the OpenSSH "Accepted" line from the raw message.


False positives: vCenter manages hosts through its own agent, not a shell, so most vpxuser SSH logins deserve a look.


Splunk


index=YOUR_ESXI_SYSLOG_INDEX earliest=1792738800 latest=now "vpxuser" "Accepted"
| rex "Accepted (?<auth_method>\S+) for vpxuser from (?<ssh_src>\d{1,3}(?:\.\d{1,3}){3})"
| where isnotnull(ssh_src) AND ssh_src!="YOUR_VCENTER_IP"
| stats count AS logins values(auth_method) AS methods min(_time) AS first_seen max(_time) AS last_seen BY host ssh_src
| convert ctime(first_seen) ctime(last_seen)

Elastic


FROM YOUR_ESXI_SYSLOG_INDEX
| WHERE @timestamp >= TO_DATETIME("2026-10-23T07:00:00.000Z")
| EVAL msg = TO_LOWER(message)
| WHERE msg LIKE "*accepted*for vpxuser from*"
| GROK msg "for vpxuser from %{IP:ssh_src}"
| WHERE ssh_src IS NOT NULL AND ssh_src != "YOUR_VCENTER_IP"
| STATS logins = COUNT(*), first_seen = MIN(@timestamp), last_seen = MAX(@timestamp) BY host.name, ssh_src
| SORT logins DESC

CrowdStrike


#repo=YOUR_ESXI_REPO
| test(@timestamp >= 1792738800000)
| regex("Accepted (?<auth_method>\\S+) for vpxuser from (?<ssh_src>\\d{1,3}(?:\\.\\d{1,3}){3})", field=@rawstring)
| ssh_src != "YOUR_VCENTER_IP"
| default(field=host.name, value="unknown")
| groupBy([host.name, ssh_src], function=[count(as=logins), collect([auth_method]), min(@timestamp, as=first_seen), max(@timestamp, as=last_seen)])
| sort(logins, order=desc, limit=10000)

Microsoft KQL


Syslog
| where TimeGenerated >= datetime(2026-10-23T07:00:00Z)
| where SyslogMessage has "vpxuser" and SyslogMessage has "Accepted"
| extend SshSrc = extract(@"for vpxuser from (\d{1,3}(?:\.\d{1,3}){3})", 1, SyslogMessage)
| where isnotempty(SshSrc) and SshSrc != "YOUR_VCENTER_IP"
| summarize Logins = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by HostName, SshSrc
| order by Logins desc

Hunt 4: the file and process names


This sweeps forwarded Gaia and ESXi logs for the CPR paths and names.


Field mapping: Every version searches the raw message, so add any Linux EDR process and file tables.


False positives: Exclude the admin account that ran the on-box check; its commands match.


Splunk


(index=YOUR_GAIA_SYSLOG_INDEX OR index=YOUR_ESXI_SYSLOG_INDEX) earliest=1792738800 latest=now
| regex _raw="(?i)(cpconfigdaemon|libcpfwcommon\.so|/tmp/\.bd\.log|/tmp/\.up\.tpl|/tmp/\.p_sock|challenge_render\.tpl|zz[^/\s]*\.tpl|/tmp/des\b|destroy_v6|dream_annexation|upload_progress_pwn)"
| rex field=_raw "(?i)(?<artifact>cpconfigdaemon|libcpfwcommon\.so|/tmp/\.bd\.log|/tmp/\.up\.tpl|/tmp/\.p_sock|challenge_render\.tpl|zz[^/\s]*\.tpl|/tmp/des\b|destroy_v6|dream_annexation|upload_progress_pwn)"
| stats count AS hits min(_time) AS first_seen max(_time) AS last_seen BY index host artifact
| convert ctime(first_seen) ctime(last_seen)

Elastic


FROM YOUR_GAIA_SYSLOG_INDEX, YOUR_ESXI_SYSLOG_INDEX
| WHERE @timestamp >= TO_DATETIME("2026-10-23T07:00:00.000Z")
| EVAL msg = TO_LOWER(message)
| WHERE msg LIKE "*cpconfigdaemon*" OR msg LIKE "*libcpfwcommon.so*" OR msg LIKE "*/tmp/.bd.log*" OR msg LIKE "*/tmp/.up.tpl*" OR msg LIKE "*/tmp/.p_sock*" OR msg LIKE "*challenge_render.tpl*" OR msg RLIKE """.*/tmp/des([^a-z0-9_].*)?""" OR msg LIKE "*destroy_v6*" OR msg LIKE "*dream_annexation*" OR msg LIKE "*upload_progress_pwn*" OR msg RLIKE """.*zz[^/ ]*\.tpl.*"""
| EVAL artifact = CASE(
    msg LIKE "*cpconfigdaemon*", "cpconfigdaemon",
    msg LIKE "*libcpfwcommon.so*", "libcpfwcommon.so",
    msg LIKE "*/tmp/.bd.log*", "/tmp/.bd.log",
    msg LIKE "*/tmp/.up.tpl*", "/tmp/.up.tpl",
    msg LIKE "*/tmp/.p_sock*", "/tmp/.p_sock",
    msg LIKE "*challenge_render.tpl*", "challenge_render.tpl",
    msg LIKE "*destroy_v6*", "destroy_v6",
    msg LIKE "*dream_annexation*", "dream_annexation",
    msg LIKE "*upload_progress_pwn*", "upload_progress_pwn",
    msg RLIKE """.*/tmp/des([^a-z0-9_].*)?""", "/tmp/des",
    "zz*.tpl")
| STATS hits = COUNT(*), first_seen = MIN(@timestamp), last_seen = MAX(@timestamp) BY host.name, artifact
| SORT hits DESC

CrowdStrike


#repo=YOUR_SYSLOG_REPO or #repo=YOUR_ESXI_REPO
| test(@timestamp >= 1792738800000)
| @rawstring=/(?<artifact>cpconfigdaemon|libcpfwcommon\.so|\/tmp\/\.bd\.log|\/tmp\/\.up\.tpl|\/tmp\/\.p_sock|challenge_render\.tpl|zz[^\/\s]*\.tpl|\/tmp\/des\b|destroy_v6|dream_annexation|upload_progress_pwn)/i
| default(field=host.name, value="unknown")
| groupBy([host.name, artifact], function=[count(as=hits), min(@timestamp, as=first_seen), max(@timestamp, as=last_seen)])
| sort(hits, order=desc, limit=10000)

Microsoft KQL


Syslog
| where TimeGenerated >= datetime(2026-10-23T07:00:00Z)
| where SyslogMessage contains "cpconfigdaemon" or SyslogMessage contains "destroy_v6"
    or SyslogMessage contains "libcpfwcommon.so" or SyslogMessage contains "/tmp/.bd.log" or SyslogMessage contains "/tmp/.up.tpl"
    or SyslogMessage contains "/tmp/.p_sock" or SyslogMessage contains "challenge_render.tpl" or SyslogMessage matches regex @"(?i)/tmp/des\b"
    or SyslogMessage contains "DREAM_ANNEXATION" or SyslogMessage contains "upload_progress_pwn"
    or SyslogMessage matches regex @"(?i)zz[^/\s]*\.tpl"
| extend Artifact = extract(@"(?i)(cpconfigdaemon|libcpfwcommon\.so|/tmp/\.bd\.log|/tmp/\.up\.tpl|/tmp/\.p_sock|challenge_render\.tpl|zz[^/\s]*\.tpl|/tmp/des\b|destroy_v6|dream_annexation|upload_progress_pwn)", 1, SyslogMessage)
| summarize Hits = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by HostName, Artifact
| order by Hits desc

Hunt 5: holes in the firewall logs


Check Point lists unexplained firewall log gaps, especially after a logging daemon crash, as a compromise indicator. This counts hours logged per gateway per full day; fewer than 24 is a gap. It only scores days with at least one event, so check each gateway's day count against the full date range for whole-day gaps.


Field mapping: The #Vendor value and the gateway fields dvc, observer.name and DeviceName are placeholders; all four count whole UTC days from 2026-10-24T00:00:00Z through yesterday.


False positives: Reboots, Log Server outages and ingestion delays also leave gaps, so check change records.


Splunk


index=YOUR_CHECKPOINT_INDEX earliest=1792800000 latest=now
| where _time < floor(now()/86400)*86400
| eval hour_utc=floor(_time/3600), day_utc=floor(_time/86400)
| stats dc(hour_utc) AS hours_with_logs count AS events BY dvc day_utc
| where hours_with_logs < 24
| eval day=strftime(day_utc*86400+43200, "%Y-%m-%d")
| sort dvc day
| fields dvc day hours_with_logs events

Elastic


FROM YOUR_CHECKPOINT_INDEX
| WHERE @timestamp >= TO_DATETIME("2026-10-24T00:00:00.000Z") AND @timestamp < DATE_TRUNC(1 day, NOW())
| EVAL hour = DATE_FORMAT("yyyy-MM-dd HH", @timestamp), day = DATE_FORMAT("yyyy-MM-dd", @timestamp)
| STATS hours_with_logs = COUNT_DISTINCT(hour), events = COUNT(*) BY observer.name, day
| WHERE hours_with_logs < 24
| SORT observer.name, day
| LIMIT 10000

CrowdStrike


#Vendor=/check ?point/i
| test(@timestamp >= 1792800000000)
// End the time picker at now so the latest UTC day is included.
| now(as=_now)
| test(@timestamp < _now - (_now % 86400000))
| hour := formatTime("%Y-%m-%d %H", field=@timestamp, timezone="UTC")
| day := formatTime("%Y-%m-%d", field=@timestamp, timezone="UTC")
| default(field=observer.name, value="unknown")
| groupBy([observer.name, day], function=[count(hour, distinct=true, as=hours_with_logs), count(as=events)])
| test(hours_with_logs < 24)
| sort(day, type=string, order=asc, limit=10000)

Microsoft KQL


CommonSecurityLog
| where TimeGenerated >= datetime(2026-10-24T00:00:00Z) and TimeGenerated < startofday(now())
| where DeviceVendor has "Check Point"
| summarize HoursWithLogs = dcount(bin(TimeGenerated, 1h)), Events = count() by DeviceName, Day = startofday(TimeGenerated)
| where HoursWithLogs < 24
| order by DeviceName asc, Day asc

Hunt 6: the CPR hash list


This sweeps EDR telemetry for the six SHA256 values CPR ties to this intrusion. On Gaia and ESXi, run sha256sum on suspect files instead.


Field mapping: Splunk matches raw hashes, and the others read Elastic Defend, Falcon SHA256HashData and Defender hash fields.


False positives: Treat a match as confirmed. A miss proves little, because a rebuilt binary has a different hash.


Splunk


(index=YOUR_EDR_INDEX OR index=YOUR_SYSMON_INDEX) earliest=1792738800 latest=now (edb3e92eb7bda7413a09edcc4b1ba3cbed4bcdf42808a8abce63b6e4c3fda2b3 OR 4171b521fa6441382aa2fa014a08e18ff9a6e0ab6096c9144072bf46b37a2ec8 OR d7549671c7189979f0624ec52dad9ea6a707d23146e3585ab8f8c4720eca0c63 OR 3b51080a909023f7d484e2443740b572307ce0054eb9e32d98f0a06bfcd2db90 OR c7d9477309fac6c638a1128442056a242de086996add8cb49cd6496c36003237 OR 0522937c0c0337be54fe25493539e5f5f8ceb9742c980aaff1cfc9515f3a96f7)
| stats count AS hits values(sourcetype) AS sources min(_time) AS first_seen max(_time) AS last_seen BY host
| convert ctime(first_seen) ctime(last_seen)

Elastic


FROM logs-endpoint.events.*
| WHERE @timestamp >= TO_DATETIME("2026-10-23T07:00:00.000Z")
| WHERE file.hash.sha256 IN ("edb3e92eb7bda7413a09edcc4b1ba3cbed4bcdf42808a8abce63b6e4c3fda2b3", "4171b521fa6441382aa2fa014a08e18ff9a6e0ab6096c9144072bf46b37a2ec8", "d7549671c7189979f0624ec52dad9ea6a707d23146e3585ab8f8c4720eca0c63", "3b51080a909023f7d484e2443740b572307ce0054eb9e32d98f0a06bfcd2db90", "c7d9477309fac6c638a1128442056a242de086996add8cb49cd6496c36003237", "0522937c0c0337be54fe25493539e5f5f8ceb9742c980aaff1cfc9515f3a96f7")
    OR process.hash.sha256 IN ("edb3e92eb7bda7413a09edcc4b1ba3cbed4bcdf42808a8abce63b6e4c3fda2b3", "4171b521fa6441382aa2fa014a08e18ff9a6e0ab6096c9144072bf46b37a2ec8", "d7549671c7189979f0624ec52dad9ea6a707d23146e3585ab8f8c4720eca0c63", "3b51080a909023f7d484e2443740b572307ce0054eb9e32d98f0a06bfcd2db90", "c7d9477309fac6c638a1128442056a242de086996add8cb49cd6496c36003237", "0522937c0c0337be54fe25493539e5f5f8ceb9742c980aaff1cfc9515f3a96f7")
| STATS hits = COUNT(*), first_seen = MIN(@timestamp), last_seen = MAX(@timestamp) BY host.name, event.dataset, file.path, process.executable

CrowdStrike


SHA256HashData=*
| test(@timestamp >= 1792738800000)
| in(field=SHA256HashData, values=["edb3e92eb7bda7413a09edcc4b1ba3cbed4bcdf42808a8abce63b6e4c3fda2b3", "4171b521fa6441382aa2fa014a08e18ff9a6e0ab6096c9144072bf46b37a2ec8", "d7549671c7189979f0624ec52dad9ea6a707d23146e3585ab8f8c4720eca0c63", "3b51080a909023f7d484e2443740b572307ce0054eb9e32d98f0a06bfcd2db90", "c7d9477309fac6c638a1128442056a242de086996add8cb49cd6496c36003237", "0522937c0c0337be54fe25493539e5f5f8ceb9742c980aaff1cfc9515f3a96f7"])
| default(field=FileName, value="unknown")
| groupBy([aid, #event_simpleName, FileName, SHA256HashData], function=[count(as=hits), min(@timestamp, as=first_seen), max(@timestamp, as=last_seen)])

Microsoft KQL


let hashes = dynamic(["edb3e92eb7bda7413a09edcc4b1ba3cbed4bcdf42808a8abce63b6e4c3fda2b3", "4171b521fa6441382aa2fa014a08e18ff9a6e0ab6096c9144072bf46b37a2ec8", "d7549671c7189979f0624ec52dad9ea6a707d23146e3585ab8f8c4720eca0c63", "3b51080a909023f7d484e2443740b572307ce0054eb9e32d98f0a06bfcd2db90", "c7d9477309fac6c638a1128442056a242de086996add8cb49cd6496c36003237", "0522937c0c0337be54fe25493539e5f5f8ceb9742c980aaff1cfc9515f3a96f7"]);
union isfuzzy=true
    (DeviceProcessEvents | where Timestamp >= datetime(2026-10-23T07:00:00Z) | where SHA256 in (hashes)),
    (DeviceFileEvents | where Timestamp >= datetime(2026-10-23T07:00:00Z) | where SHA256 in (hashes))
| summarize Hits = count(), FirstSeen = min(Timestamp), LastSeen = max(Timestamp) by DeviceName, FileName, FolderPath, SHA256

Indicators from Check Point


Type

Indicator

Source label

IPv4

130.94.19[.]204

scanning and exploitation source

IPv4

130.94.19[.]168

scanning and exploitation source

SHA256

edb3e92eb7bda7413a09edcc4b1ba3cbed4bcdf42808a8abce63b6e4c3fda2b3

tpl webshell

SHA256

4171b521fa6441382aa2fa014a08e18ff9a6e0ab6096c9144072bf46b37a2ec8

slot_CPconfigdaemon

SHA256

d7549671c7189979f0624ec52dad9ea6a707d23146e3585ab8f8c4720eca0c63

CPconfigdaemon

SHA256

3b51080a909023f7d484e2443740b572307ce0054eb9e32d98f0a06bfcd2db90

gogo

SHA256

c7d9477309fac6c638a1128442056a242de086996add8cb49cd6496c36003237

destroy_v6

SHA256

0522937c0c0337be54fe25493539e5f5f8ceb9742c980aaff1cfc9515f3a96f7

DREAM_ANNEXATION.txt

Path

/opt/CPcvpn-R81.20/tmp/ session files containing upload_progress_pwn

exploitation marker

Path

zz*.tpl, challenge_render.tpl, /tmp/.up.tpl

webshells and uploads

Path

/tmp/CPconfigdaemon, slot_CPconfigdaemon, /tmp/.bd.log

TSGA

Path

/lib/libcpfwcommon.so

TSProxy

Network

TCP 11072 on all IPv4 interfaces, Unix socket /tmp/.p_sock

TSGA listener

Path

/tmp/des, DREAM_ANNEXATION.txt

wiper and note on ESXi


Reading the results before you escalate


A clean run on a gateway whose logs never arrived is unproven, and TSGA can make a compromised box look normal. An upload_progress_pwn session file, any listed path, or a listener on 11072 is an incident. So is SSH from a gateway to ESXi, or an unexplained vpxuser login. A DynamicCLI command without the Clish wrapper may indicate root execution through the socket and, per sk1000362, requires investigation. A Hunt 5 gap alone does not prove tampering.


When something hits, isolate rather than clean. Preserve the gateway and ESXi host, treat the gateway's configuration and credentials as exposed, vet backups before restoring, and bring in incident response and Check Point Support. Patching does not remove a running implant.


One next step


Microsoft 365 is usually the other front door. If your clients run on it, start with a free Inception Foresight M365 Assessment.


bg-map-white.png

INCEPTION SECURITY™

A cybersecurity company with in depth knowledge of the threat landscape and security controls.

NAVIGATION

GET IN TOUCH

© 2025 All Rights Reserved by INCEPTION SECURITY™ .

bottom of page