ScreenConnect Fake Crypto Sessions Bait Your Technicians


What this means for your clients: Strangers have been adding fake computers to the remote support tools IT providers use, hoping a technician clicks one, so ask yours whether their support software is updated and whether anyone opened a fake.
On November 12, an MSP asked Huntress about unfamiliar machines in its self-hosted ScreenConnect console. They had names like KUCOIN-SRV and LAPTOP-BINANCE, no company or owner, and claimed to be online. Nobody had installed them, and the server was clean. As of November 13, Huntress had found the same pattern on at least 118 self-hosted servers belonging to 109 organizations it protects, with 2,408 unauthorized sessions enrolled since November 3 and more than 1,000 distinct fake names.
Huntress has seen nothing malicious come from these fakes so far. They have not pushed files, run commands, or sent anything to technicians who joined them. The risk is your technician, because a crypto exchange server nobody owns is exactly the session a curious tech clicks, and the technician workstation is usually the most privileged machine MSPs own. This is separate from our November 11 ScreenConnect post, and Huntress found no overlap with SCKit indicators.
How ScreenConnect fake crypto sessions get into your console
Nobody had to break in. A guest client needs the server's relay address, which is embedded in the Access installer. On the first server Huntress examined, that installer was publicly downloadable. ScreenConnect makes installers available without authentication by design. With the address, a scripted emulator impersonating a Windows guest registers as an unattended access machine that looks always online.
The fakes have a fingerprint. Huntress says each claims guest version 25.3.4.9288, sends no guest public key, reports ClientType 1, randomizes hardware and OS details, and reuses seven trading desktop thumbnails. Names pair an exchange word with a role word, such as SRV-BINANCE or BACKUP-PROBIT. Across 29 servers with complete histories, the operator revisited the servers every 151 seconds, creating or reconnecting fake sessions. Individual connections typically lasted about 64 minutes, and sessions that reconnected did so about every 55 minutes. Huntress tied 97.8% of crypto-named guest connections to one IONOS-hosted address, and names no actor. Neither do we.
Why a fake computer is a real risk
Huntress's leading hypothesis is that the fakes are bait for CVE-2026-84869. ConnectWise's September 8 bulletin describes a client condition that may allow files to be transferred and executed through an active remote session without authorization or Host confirmation in certain circumstances. The flaw is in the client, not the server, and it affects every version before 26.6.5. Huntress says a separate campaign is actively exploiting it and that a file could run on the technician's machine, including with elevated rights. It has not seen these sessions used that way, and it cannot rule out simpler goals, such as capturing what a technician types.
That simpler goal may already have come close. A handful of technicians opened a fake, mostly for under 30 seconds, but one used "send text" twice, and since ScreenConnect does not retain sent text, Huntress treated it as possible credential exposure. Even unclicked, fake machines clutter the console and hide real problems.
Patch the client, then clean up carefully
ConnectWise says cloud is already on 26.6.5, and on-premises servers must run 25.4 or later to upgrade to 26.6.5. After upgrading, you must still reinstall your host clients and update your Access agents. If the upgrade must wait, ConnectWise's temporary mitigation is to edit each role under Administration > Security > Roles and deselect TransferFiles in every session group, which it says is no substitute for the update.
Preserve App_Data\Session.db and its WAL file before deleting a single fake, because that history shows whether anyone joined and what moved. Bar technicians from joining sessions they cannot tie to a customer. Keep Access installer links private, restrict installer and web access where supported, rebuild leaked installers, park unfamiliar sessions in a group nobody browses, and block the listed addresses while expecting them to move.
How to hunt
We have not run these in a live SIEM, so test each before trusting a clean result. Every test and hunt comes in Splunk, Elastic, CrowdStrike and Microsoft KQL versions. Hunts 1 and 2 read firewall logs, and Hunts 3 and 4 read EDR on technician machines. Hunts start at 2026-11-03T00:00:00Z, the day the first fake appeared; widen Hunts 3 and 4 if retention allows. Every YOUR_ name is a placeholder.
Server check first
Copy the database and WAL file together, then review the copy or the Access list for exchange and role names, empty company and site fields, claimed version 25.3.4.9288, crypto previews, and an empty guest public key with ClientType 1. ConnectWise does not publish table or column names. Separate technician actions from guest actions.
Manual read-only check (PowerShell on the ScreenConnect server, not a hunt or SIEM query)
# Copy the session database and its WAL file before deleting anything
Copy-Item -Path "YOUR_SC_INSTALL_DIR\App_Data\Session.db*" -Destination "YOUR_EVIDENCE_DIR\" -Verbose
Get-FileHash -Algorithm SHA256 "YOUR_EVIDENCE_DIR\Session.db*"Test 1: does EDR see your technicians' client
Every technician who uses ScreenConnect should appear here; a missing one is a blind spot for Hunts 3 and 4.
Field mapping: CrowdStrike matches FileName, the basename.
Splunk
index=YOUR_SYSMON_INDEX (source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" OR sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational") EventCode=1 earliest=-30d latest=now
| eval img=lower(Image)
| where mvindex(split(img, "\\"), -1)=="screenconnect.windowsclient.exe"
| stats count AS runs max(_time) AS last_seen BY host User
| convert ctime(last_seen)Elastic
FROM logs-endpoint.events.process-*
| WHERE @timestamp >= NOW() - 30 days AND event.action == "start"
| WHERE TO_LOWER(process.name) == "screenconnect.windowsclient.exe"
| STATS runs = COUNT(*), last_seen = MAX(@timestamp) BY host.name, user.name
| SORT host.nameCrowdStrike
// set the time picker to the last 30 days
#event_simpleName=ProcessRollup2
| FileName=/^ScreenConnect\.WindowsClient\.exe$/i
| default(field=UserName, value="unknown")
| groupBy([#event_simpleName, aid, UserName], function=[count(as=runs), max(@timestamp, as=last_seen)])Microsoft KQL
DeviceProcessEvents
| where Timestamp > ago(30d)
| where FileName =~ "ScreenConnect.WindowsClient.exe"
| summarize Runs = count(), LastSeen = max(Timestamp) by DeviceName, AccountName
| order by DeviceName ascTest 2: host clients below 26.6.5
Anything below 26.6.5 is a patch task today. A technician from Test 1 who is missing here is either patched or not reporting a version, so confirm it on the box.
Field mapping: Falcon process events do not carry a version, so CrowdStrike stacks SHA256 values to compare against a known 26.6.5 client. Those groups are inventory, not a verdict.
Splunk
index=YOUR_SYSMON_INDEX (source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" OR sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational") EventCode=1 earliest=-30d latest=now
| eval img=lower(Image)
| where mvindex(split(img, "\\"), -1)=="screenconnect.windowsclient.exe"
| rex field=FileVersion "^(?<maj>\d+)\.(?<min>\d+)\.(?<pat>\d+)"
| eval maj=tonumber(maj), min=tonumber(min), pat=tonumber(pat)
| where maj<26 OR (maj==26 AND min<6) OR (maj==26 AND min==6 AND pat<5)
| stats max(_time) AS last_seen BY host FileVersion
| convert ctime(last_seen)Elastic
FROM logs-endpoint.events.process-*
| WHERE @timestamp >= NOW() - 30 days AND event.action == "start"
| WHERE TO_LOWER(process.name) == "screenconnect.windowsclient.exe" AND process.pe.file_version IS NOT NULL
| EVAL v = TO_VERSION(process.pe.file_version)
| WHERE v < TO_VERSION("26.6.5")
| STATS last_seen = MAX(@timestamp) BY host.name, process.pe.file_version
| SORT host.nameCrowdStrike
// set the time picker to the last 30 days
#event_simpleName=ProcessRollup2
| FileName=/^ScreenConnect\.WindowsClient\.exe$/i
| groupBy([#event_simpleName, SHA256HashData], function=[count(aid, distinct=true, as=hosts), collect([ImageFileName]), max(@timestamp, as=last_seen)])
| sort(hosts, order=asc)Microsoft KQL
DeviceProcessEvents
| where Timestamp > ago(30d)
| where FileName =~ "ScreenConnect.WindowsClient.exe" and isnotempty(ProcessVersionInfoProductVersion)
| where parse_version(ProcessVersionInfoProductVersion) < parse_version("26.6.5")
| summarize LastSeen = max(Timestamp) by DeviceName, ProcessVersionInfoProductVersion
| order by DeviceName ascHunt 1: the three listed addresses
This checks traffic to and from the three addresses Huntress published.
Field mapping: source and destination fields follow CIM, ECS, Falcon Next-Gen SIEM and CommonSecurityLog.
False positives: Huntress saw no guest sessions from 87.106.111[.]220, so a hit there points to related infrastructure, not a fake session.
Splunk
index=YOUR_FIREWALL_INDEX earliest=1793664000 latest=now (src_ip IN ("74.208.158.106", "197.47.160.252", "87.106.111.220") OR dest_ip IN ("74.208.158.106", "197.47.160.252", "87.106.111.220"))
| stats count AS connections values(dest_port) AS ports values(action) AS actions min(_time) AS first_seen max(_time) AS last_seen BY src_ip dest_ip
| convert ctime(first_seen) ctime(last_seen)
| sort - connectionsElastic
FROM YOUR_FIREWALL_INDEX
| WHERE @timestamp >= TO_DATETIME("2026-11-03T00:00:00Z")
| WHERE CIDR_MATCH(source.ip, "74.208.158.106/32", "197.47.160.252/32", "87.106.111.220/32") OR CIDR_MATCH(destination.ip, "74.208.158.106/32", "197.47.160.252/32", "87.106.111.220/32")
| STATS connections = COUNT(*), ports = VALUES(destination.port), first_seen = MIN(@timestamp), last_seen = MAX(@timestamp) BY source.ip, destination.ip
| SORT connections DESCCrowdStrike
#repo=YOUR_FIREWALL_REPO
| test(@timestamp >= 1793664000000)
| source.ip=/^(74\.208\.158\.106|197\.47\.160\.252|87\.106\.111\.220)$/ or destination.ip=/^(74\.208\.158\.106|197\.47\.160\.252|87\.106\.111\.220)$/
| default(field=[source.ip, destination.ip], value="none")
| groupBy([source.ip, destination.ip], function=[count(as=connections), collect([destination.port]), min(@timestamp, as=first_seen), max(@timestamp, as=last_seen)])
| sort(connections, order=desc)Microsoft KQL
let iocs = dynamic(["74.208.158.106", "197.47.160.252", "87.106.111.220"]);
CommonSecurityLog
| where TimeGenerated >= datetime(2026-11-03T00:00:00Z)
| where SourceIP in (iocs) or DestinationIP in (iocs)
| summarize Connections = count(), Ports = make_set(DestinationPort), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP
| order by Connections descHunt 2: one address, many relay connections
This is Huntress's detection idea in firewall form: relay connections per external address per hour, with our starting threshold of 50 to tune. A hit is a lead, not proof of enrollment.
Field mapping: set YOUR_RELAY_PORT to your own server's relay port; we do not assume a default.
False positives: A NAT'd client site, a server restart, or a mass agent reinstall can burst too, so allowlist known sites.
Splunk
index=YOUR_FIREWALL_INDEX dest_ip="YOUR_SC_SERVER_IP" dest_port=YOUR_RELAY_PORT earliest=1793664000 latest=now
| where NOT cidrmatch("YOUR_KNOWN_CLIENT_CIDR", src_ip)
| bin _time span=1h
| stats count AS connections BY _time src_ip
| where connections >= 50
| sort - connectionsElastic
FROM YOUR_FIREWALL_INDEX
| WHERE @timestamp >= TO_DATETIME("2026-11-03T00:00:00Z") AND destination.ip == TO_IP("YOUR_SC_SERVER_IP") AND destination.port == YOUR_RELAY_PORT
| WHERE NOT CIDR_MATCH(source.ip, "YOUR_KNOWN_CLIENT_CIDR")
| EVAL hour = DATE_TRUNC(1 hour, @timestamp)
| STATS connections = COUNT(*) BY hour, source.ip
| WHERE connections >= 50
| SORT connections DESCCrowdStrike
#repo=YOUR_FIREWALL_REPO
| test(@timestamp >= 1793664000000)
| destination.ip="YOUR_SC_SERVER_IP" destination.port="YOUR_RELAY_PORT"
| !cidr(field=source.ip, subnet="YOUR_KNOWN_CLIENT_CIDR")
| hour := formatTime("%Y-%m-%d %H", field=@timestamp, timezone="UTC")
| groupBy([source.ip, hour], function=[count(as=connections)])
| test(connections >= 50)
| sort(connections, order=desc)Microsoft KQL
CommonSecurityLog
| where TimeGenerated >= datetime(2026-11-03T00:00:00Z)
| where DestinationIP == "YOUR_SC_SERVER_IP" and DestinationPort == YOUR_RELAY_PORT
| where not(ipv4_is_in_range(SourceIP, "YOUR_KNOWN_CLIENT_CIDR"))
| summarize Connections = count() by SourceIP, Hour = bin(TimeGenerated, 1h)
| where Connections >= 50
| order by Connections descHunt 3: the technician client starting a shell
Huntress calls this the host-side shape of CVE-2026-84869 exploitation: ScreenConnect.WindowsClient.exe starting cmd.exe, powershell.exe, wscript.exe or msiexec.exe with a file under Documents\*\Temp\.
Field mapping: CrowdStrike matches basenames in ParentBaseFileName and FileName.
False positives: A tech deliberately running a script through the client looks the same, so ask the tech.
Splunk
index=YOUR_SYSMON_INDEX (source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" OR sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational") EventCode=1 earliest=1793664000 latest=now
| eval parent=mvindex(split(lower(ParentImage), "\\"), -1), child=mvindex(split(lower(Image), "\\"), -1), cl=lower(CommandLine)
| where parent=="screenconnect.windowsclient.exe" AND in(child, "cmd.exe", "powershell.exe", "wscript.exe", "msiexec.exe") AND match(cl, "\\\\documents\\\\[^\\\\]+\\\\temp\\\\")
| stats count AS runs values(CommandLine) AS commands min(_time) AS first_seen max(_time) AS last_seen BY host User child
| convert ctime(first_seen) ctime(last_seen)Elastic
FROM logs-endpoint.events.process-*
| WHERE @timestamp >= TO_DATETIME("2026-11-03T00:00:00Z") AND event.action == "start"
| EVAL parent = TO_LOWER(process.parent.name), child = TO_LOWER(process.name), cl = TO_LOWER(process.command_line)
| WHERE parent == "screenconnect.windowsclient.exe" AND child IN ("cmd.exe", "powershell.exe", "wscript.exe", "msiexec.exe") AND cl LIKE """*\\documents\\*\\temp\\*"""
| STATS runs = COUNT(*), commands = VALUES(process.command_line), first_seen = MIN(@timestamp), last_seen = MAX(@timestamp) BY host.name, user.name, childCrowdStrike
#event_simpleName=ProcessRollup2
| test(@timestamp >= 1793664000000)
| ParentBaseFileName=/^ScreenConnect\.WindowsClient\.exe$/i
| FileName=/^(cmd|powershell|wscript|msiexec)\.exe$/i
| CommandLine=/\\Documents\\[^\\]+\\Temp\\/i
| default(field=UserName, value="unknown")
| groupBy([#event_simpleName, aid, UserName, FileName], function=[count(as=runs), collect([CommandLine]), min(@timestamp, as=first_seen), max(@timestamp, as=last_seen)])Microsoft KQL
DeviceProcessEvents
| where Timestamp >= datetime(2026-11-03T00:00:00Z)
| where InitiatingProcessFileName =~ "ScreenConnect.WindowsClient.exe"
| where FileName in~ ("cmd.exe", "powershell.exe", "wscript.exe", "msiexec.exe")
| where ProcessCommandLine matches regex @"(?i)\\documents\\[^\\]+\\temp\\"
| summarize Runs = count(), Commands = make_set(ProcessCommandLine, 50), FirstSeen = min(Timestamp), LastSeen = max(Timestamp) by DeviceName, AccountName, FileNameHunt 4: client files in Temp, and any new Startup entry
Huntress says to inspect the ScreenConnect Temp and Startup folders on any unpatched technician workstation that joined a fake. The Temp search requires the client as the writer. The Startup search accepts any writer, because a payload would usually add that entry, so check whether the writer descends from the client.
Field mapping: CrowdStrike names each writer from ProcessRollup2; a Startup writer started before the window shows as unresolved. The Startup folder path is the standard Windows location, not a Huntress finding.
False positives: Deliberate technician transfers land in Temp and installers add Startup shortcuts, so match each file to a session.
Splunk
index=YOUR_SYSMON_INDEX (source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" OR sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational") EventCode=11 earliest=1793664000 latest=now
| eval proc=mvindex(split(lower(Image), "\\"), -1), tf=lower(TargetFilename)
| where (proc=="screenconnect.windowsclient.exe" AND match(tf, "\\\\documents\\\\[^\\\\]+\\\\temp\\\\")) OR match(tf, "\\\\start menu\\\\programs\\\\startup\\\\")
| stats count AS writes min(_time) AS first_seen max(_time) AS last_seen BY host proc TargetFilename
| convert ctime(first_seen) ctime(last_seen)Elastic
FROM logs-endpoint.events.file-*
| WHERE @timestamp >= TO_DATETIME("2026-11-03T00:00:00Z") AND event.action IN ("creation", "modification", "overwrite", "rename")
| EVAL proc = TO_LOWER(process.name), fp = TO_LOWER(file.path)
| WHERE (proc == "screenconnect.windowsclient.exe" AND fp LIKE """*\\documents\\*\\temp\\*""") OR fp LIKE """*\\start menu\\programs\\startup\\*"""
| STATS writes = COUNT(*), first_seen = MIN(@timestamp), last_seen = MAX(@timestamp) BY host.name, proc, file.pathCrowdStrike
#event_simpleName=/^ProcessRollup2$|Written$/
| test(@timestamp >= 1793664000000)
| case { #event_simpleName=ProcessRollup2 | ContextProcessId := TargetProcessId | writer := FileName | writer_parent := ParentBaseFileName ; #event_simpleName=/Written$/ | TargetFileName=/(\\Documents\\[^\\]+\\Temp\\|\\Start Menu\\Programs\\Startup\\)/i | written := TargetFileName }
| selfJoinFilter(field=[aid, ContextProcessId], where=[{#event_simpleName=/Written$/}])
| groupBy([aid, ContextProcessId], function=[collect([writer, writer_parent, written]), count(written, as=writes), max(@timestamp, as=last_seen)], limit=max)
| written=*
| default(field=[writer, writer_parent], value="unresolved")
| splitString(field=written, by="\n", as=written)
| split(written)
| case { written=/\\Start Menu\\Programs\\Startup\\/i ; writer=/^ScreenConnect\.WindowsClient\.exe$/i }Microsoft KQL
DeviceFileEvents
| where Timestamp >= datetime(2026-11-03T00:00:00Z) and ActionType in ("FileCreated", "FileModified", "FileRenamed")
| where (InitiatingProcessFileName =~ "ScreenConnect.WindowsClient.exe" and FolderPath matches regex @"(?i)\\documents\\[^\\]+\\temp(\\|$)") or FolderPath matches regex @"(?i)\\start menu\\programs\\startup(\\|$)"
| summarize Writes = count(), FirstSeen = min(Timestamp), LastSeen = max(Timestamp) by DeviceName, InitiatingProcessFileName, InitiatingProcessParentFileName, FolderPath, FileName, SHA1Indicators from Huntress
Type | Indicator | Huntress description |
IPv4 | 74.208.158[.]106 | IONOS VPS; source of 97.8% of crypto-named guest connections (Nov 3 to 13, 2026) |
IPv4 | 197.47.160[.]252 | Created the first three unauthorized sessions on Nov 3, 2026, 21:26 UTC |
IPv4 | 87.106.111[.]220 | Related IONOS host (same JARM and TLS certificates as 74.208.158[.]106); no guest sessions observed from it |
SSH host key | SHA256:kt6iiIS3kcQ3NsUSVfk7gJoGMSSUqMdXqsmjb+/cSHg | ECDSA SSH host key fingerprint of 74.208.158[.]106, for pivoting |
Version | 25.3.4.9288 | Guest client version claimed by every guest machine (context indicator, not unique on its own) |
Session name | EXCHANGE-ROLE or ROLE-EXCHANGE, e.g. KUCOIN-SRV, LAPTOP-BINANCE, BACKUP-PROBIT, WORKSTATION-COINEX | Naming pattern |
Server fingerprint | Empty guest public key + ClientType 1 | Combined server-side fingerprint of the emulator |
SHA256 | 008839ef2a2d54e9efa47013bb23c773d3a152f351e9dfd3b2f4dd27086121df | WebP preview thumbnail (Binance-style page), most-used bait image |
SHA256 | 45136a9ab47015c4334b881df9daf824a72347daae0b5730ce3bd7d42b204bf2 | WebP preview thumbnail (bait image family 2) |
SHA256 | e73020645e7a11a90865d93bd7f3d797585f49ef72b1ac7354392a2da7fbd91f | WebP preview thumbnail (bait image family 3) |
SHA256 | c15b008b0296b1bc9e0f979e6f25b780e3e89edabb344c7ee8c284a1a140f0d7 | WebP preview thumbnail (bait image family 4) |
SHA256 | b55ce5130cd2623017cca29318341f6b4656e3a1dc5ab82ce704e912d0d17535 | WebP preview thumbnail (bait image family 5) |
SHA256 | bea82efc82672225cf29eb869972670754f15941bc61b9e5801ffd41c5c39e5f | WebP preview thumbnail (bait image family 6) |
SHA256 | f6d37291c4948b974caf2e9e7e8ca9c2173d2779d1ec0a391488706bbd1a995c | WebP preview thumbnail (bait image family 7) |
Reading the results before you escalate
A fake in your console proves reachability, not exposure, so it is a cleanup and preservation task, as is a blocked connection from a listed address. A technician who joined a fake is a question for the preserved history: which client version, how long, and whether files, commands or text moved. If they used send text, treat what they typed as exposed and rotate it.
Escalate to incident response when an unpatched host client joined a fake and Hunts 3 or 4 show a shell or new file on that workstation, or when guest-side file events appear during a technician connection, which Huntress says may indicate CVE-2026-84869 activity. Isolate and preserve the workstation, and treat what it reaches as in scope.
One next step
If your clients run on Microsoft 365, start with a free Inception Foresight M365 Assessment.



