BeyondTrust Remote Support CVE-2026-102764 — ARMORY CASTLE Exploitation and Sentinel Hunts

Threat Overview

On 15 October 2026, BeyondTrust disclosed CVE-2026-102764 (CVSS v4.0 9.3), an origin-validation weakness in internal Remote Support messaging. BeyondTrust confirms that affected appliances permit remote file operations; Google Threat Intelligence Group (GTIG)/Mandiant documented unauthenticated exploitation as a zero-day in late August 2026 by the suspected China-nexus actor ARMORY CASTLE (formerly UNC5221). The observed intrusion used an initial PHP staging path, the SAWFLY (mangoapi.php) webshell, SUID-root escalation through /usr/bin/dbus-authd, attempted BeyondTrust Vault extraction with bt-secrets, persistent sudo privileges through post_boot.d/zz-root-persist, and the BARECLAW WebSocket SOCKS5 tunneler. Mandiant observed unauthorized Jump Item role changes, remote support sessions into internal hosts, file transfers, and log deletion. These are observed behaviors from one investigated intrusion, not universal exploitation signatures. GTIG assesses further widespread exploitation is possible; it is not confirmed as widespread as of this report.

References

Impacted Systems

  • Vendor/product: BeyondTrust Remote Support (formerly Bomgar), physical/virtual self-hosted appliances and vendor-hosted SaaS; Remote Support versions up to and including 26.2.3.
  • Affected role and prerequisite: Reachable vulnerable Remote Support appliance; unauthenticated remote exploitation was confirmed by Mandiant. Internet-facing appliances are the highest-exposure group. The precise vulnerable service port is deployment-dependent; no universal port is asserted.
  • Fixed: Apply BT26-05-RS to versions 24.3.1–26.2.3; versions older than 24.3.1 must upgrade first. BeyondTrust says Remote Support SaaS was patched by 9 Oct 2026.
  • Related product: Privileged Remote Access (PRA) shares affected code and has a precautionary patch. BeyondTrust says it has not identified an exploitable path in PRA; do not label PRA exploitation confirmed.
  • Platform: BeyondTrust appliance operating environment; downstream Windows/Linux endpoints may be reachable through support sessions, but are not themselves vulnerable to this CVE.

Why this matters

Remote-support appliances broker privileged credentials and access to many internal systems. A compromised appliance can become a pivot into multiple customer networks and may not expose host-level artifacts through conventional endpoint telemetry.

Exploitation Status

Confirmed exploitation. GTIG/Mandiant observed exploitation in late August 2026; public disclosure 15 October. Attribution to ARMORY CASTLE is a GTIG assessment. Broader exploitation after disclosure is a forward-looking assessment, not a confirmed fact.

What this hunt looks for

Prepared hunts for known SAWFLY/BARECLAW request paths, suspicious appliance web requests, role elevation, administrator login, session/file-transfer events, unusual appliance egress, internal connections, and downstream Windows logons. These are proposed Sentinel queries, not evidence of executed workspace review. Queries 02, 06, 07 and 08 require replacing the appliance-IP placeholder before execution.

Required logs

Hunt content targets only data ingested into Microsoft Sentinel: CommonSecurityLog HTTP/WAF and firewall records, appliance Syslog (when forwarded), and Windows SecurityEvent logons from known appliance IPs. Native appliance access, message, thin-scc, brain, and SSL logs on encrypted local storage are not presumed available. Endpoint logs from the proprietary appliance are not assumed to exist in Device* tables. Conclusions are bounded by ingestion, retention, parser fidelity, and asset-IP mapping; no match does not establish absence of activity on unmonitored systems.

Hunt 01 — First pass — Mandiant-confirmed SAWFLY and BARECLAW request paths

let Lookback = 60d;
CommonSecurityLog
| where TimeGenerated >= ago(Lookback)
| extend Url = tostring(column_ifexists('RequestURL','')), Msg = tostring(column_ifexists('Message',''))
| where Url has_any ('mangoapi.php','app_84304470','uidchanarge') or Msg has_any ('mangoapi.php','app_84304470','uidchanarge','_c94mfe')
| project TimeGenerated, DeviceVendor, DeviceProduct, DeviceName, SourceIP, DestinationIP, DestinationPort, DeviceAction, Url, Msg
| order by TimeGenerated desc

Hunt 02 — HTTP/WAF — unexpected PHP or WebSocket requests to identified Remote Support appliances

let Lookback = 30d;
let ApplianceIPs = dynamic(['REPLACE_WITH_APPLIANCE_PUBLIC_IP']);
CommonSecurityLog
| where TimeGenerated >= ago(Lookback)
| where DestinationIP in (ApplianceIPs)
| extend Url = tostring(column_ifexists('RequestURL','')), Msg = tostring(column_ifexists('Message',''))
| where Url contains '.php' or Url contains 'wsproxy' or Url contains 'uidchanarge' or Msg contains 'Upgrade: websocket'
| summarize Hits=count(), Sources=dcount(SourceIP), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Actions=make_set(DeviceAction,5) by DestinationIP, Url
| order by Hits desc

Hunt 03 — Appliance syslog — Jump Item administrator grants and user permission changes

let Lookback = 60d;
Syslog
| where TimeGenerated >= ago(Lookback)
| where SyslogMessage has 'user_changed'
| where SyslogMessage has_any ('jump_item_role','Administrator','team')
| project TimeGenerated, Computer, ProcessName, SyslogMessage
| order by TimeGenerated desc

Hunt 04 — Appliance syslog — successful Remote Support admin logins

let Lookback = 60d;
Syslog
| where TimeGenerated >= ago(Lookback)
| where SyslogMessage has 'login' and SyslogMessage has 'success'
| where SyslogMessage has_any ('web/login','who_ip','bomgar','BeyondTrust')
| project TimeGenerated, Computer, ProcessName, SyslogMessage
| order by TimeGenerated desc

Hunt 05 — Appliance syslog — file transfers and support-session use after role changes

let Lookback = 60d;
Syslog
| where TimeGenerated >= ago(Lookback)
| where SyslogMessage has_any ('file_transfer','file transfer','jump_item','session_started','session start')
| where SyslogMessage has_any ('bomgar','BeyondTrust','Jump','file_transfer','jump_item')
| project TimeGenerated, Computer, ProcessName, SyslogMessage
| order by TimeGenerated desc

Hunt 06 — Perimeter — new outbound destinations from a Remote Support appliance

let ApplianceIPs = dynamic(['REPLACE_WITH_APPLIANCE_IP']);
let Recent = CommonSecurityLog
| where TimeGenerated >= ago(7d)
| where SourceIP in (ApplianceIPs)
| where isnotempty(DestinationIP)
| summarize RecentHits=count(), LastSeen=max(TimeGenerated), Ports=make_set(DestinationPort,10) by SourceIP, DestinationIP;
let Baseline = CommonSecurityLog
| where TimeGenerated between (ago(37d)..ago(7d))
| where SourceIP in (ApplianceIPs)
| summarize by SourceIP, DestinationIP;
Recent
| join kind=leftanti Baseline on SourceIP, DestinationIP
| order by RecentHits desc

Hunt 07 — Perimeter — appliance-initiated access to internal endpoints

let ApplianceIPs = dynamic(['REPLACE_WITH_APPLIANCE_IP']);
CommonSecurityLog
| where TimeGenerated >= ago(30d)
| where SourceIP in (ApplianceIPs)
| where ipv4_is_private(DestinationIP)
| summarize Connections=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Ports=make_set(DestinationPort,20), Actions=make_set(DeviceAction,10) by SourceIP, DestinationIP
| order by Connections desc

Hunt 08 — Windows authentication — network logons originating from appliance addresses

let ApplianceIPs = dynamic(['REPLACE_WITH_APPLIANCE_IP']);
SecurityEvent
| where TimeGenerated >= ago(30d)
| where EventID == 4624
| extend ClientIP = tostring(column_ifexists('IpAddress','')), LogonTypeValue = tostring(column_ifexists('LogonType',''))
| where ClientIP in (ApplianceIPs)
| where LogonTypeValue in ('3','10')
| summarize Logons=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Hosts=make_set(Computer,25) by ClientIP, Account, LogonTypeValue
| order by Logons desc

Detection Notes

  • Highest signal: Query 01 exact Mandiant paths; query 03 privilege grants; query 06 new appliance egress after appliance-IP substitution. Query 08 is a downstream pivot, not proof of compromise.
  • Do not mistake product identity: Generic firewall traffic with a Cisco/Check Point device name is not a BeyondTrust appliance log. Queries 03–05 require actual BeyondTrust syslog forwarding and may be empty or fail without the Syslog table.
  • WAF visibility: Reverse proxies may URL-decode %2E or strip URI details. Blocked HTTP requests establish an attempt, not webshell execution. mangoapi.php and /uidchanarge are case-specific indicators; absence does not exclude other payloads.
  • Noise: Support sessions, file transfers, and Jump Item changes may be authorized. Validate timing, identities, change records and device scope. New destination traffic may include updates/licensing.
  • Visibility gaps: Mandiant found local logs in an LUKS-encrypted partition and noted attacker log deletion; these are not automatically Sentinel-visible. Host persistence (/usr/bin/dbus-authd, zz-root-persist, haproxy.conf) cannot be ruled out by network-only hunts.

Leave a Reply