The office mail-sorting robot accepts a note that says "deliver a bomb" and dutifully obeys
Control-M/Server is BMC's enterprise workload-automation brain — the scheduler that dispatches batch jobs, file transfers, ETL pipelines, and database maintenance to Control-M/Agent processes running across thousands of managed hosts. A communication command in the server-side listener fails to sanitize attacker-supplied input, letting an unauthenticated network attacker inject unauthorized commands that execute in the context of the Control-M/Server process itself. Per BMC, affected builds are 9.0.20.x through 9.0.21.200 inclusive, plus older unsupported branches; the fix is expected in the next 9.0.21 rollup.
Vendor severity of CRITICAL / 9.0 is fair — arguably conservative. CVSS AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H captures the ingredients, but the AC:H (some race/config precondition) is the only thing that shaved this off a 9.8. Reality is that Control-M/Server is the *definition* of a high-value-role component: successful RCE on the server yields arbitrary command execution on every agent-managed host it schedules against, plus the service accounts, DB creds, and file-transfer material stored on the scheduler. This is a fleet-pivot bug, not a single-host bug.
5 steps from start to impact.
Reach the Control-M/Server communication port
7005 for the server-agent CTM protocol, plus configuration/utility ports). This is normally an internal-only service, so the attacker is typically an already-inside actor: pentester, insider, malware on a jump host, or a compromised Control-M/Agent host pivoting back up-stream. Reconnaissance tools: nmap -sV -p 7005-7030, masscan, or a Control-M/EM console fingerprint.- Network path to Control-M/Server management VLAN
- Server exposes the CTM communication port (default listener enabled)
- Control-M/Server is almost never internet-exposed — GreyNoise/Shodan population is effectively noise-floor
- Mature shops segment the batch-automation VLAN from user desktops
7005/tcp from non-agent sources; EDR on the jump host that originated the probeCraft the injection payload against the vulnerable command
AC:H in the vector implies a non-trivial precondition — likely a specific message ordering, a length or encoding trick, or a race with a legitimate agent connection. Expect a PoC from watchTowr, Assetnote, or SSD Secure Disclosure within 2-6 weeks once a patch diff is available.- Understanding of the CTM message framing
- Ability to satisfy the AC:H precondition (timing/state)
- Proprietary binary protocol raises the reverse-engineering cost
- No public PoC at time of disclosure
Achieve RCE as the Control-M service account
controlm domain service account or LocalSystem; on Linux it's the controlm UNIX user with sudo entries to run job-plan scripts. Either way the attacker now controls the scheduler brain. Post-exploit tools: Impacket, Cobalt Strike, Sliver, or a simple in-memory Cs2Bus/reverse shell.- Vulnerable Control-M/Server 9.0.20.x – 9.0.21.200
- Some hardened deployments run Control-M/Server as a non-privileged local account with restricted sudo — limits initial blast on the box itself
- EDR on the scheduler host may catch classic post-ex tooling
controlm service spawning cmd.exe, powershell.exe, /bin/sh, or curl; sudden outbound C2Weaponize the scheduler to push jobs to every agent
Control-M/Agent on managed hosts using an authenticated channel established at agent-registration time — the server is trusted by every agent. An attacker with RCE on the server can insert or modify job definitions to run arbitrary commands on any agent-managed host: Run-As fields, pre/post scripts, or SMART Folder job actions. Tooling: native ctmcreate, ctmpsm, or direct DB writes to the Control-M schema.- Persistent agent trust relationships (the default)
- Access to the Control-M DB or
ctm*CLIs on the compromised server
- Some shops require change-management approval for new job definitions — but attacker owns the approver's authoring path
- Agent-side allow-listing of run-as accounts is rare in practice
Run-As accounts, spikes in ad-hoc job creation outside change windows; SIEM correlation against ITSM/change ticketsLoot creds, pivot to identity plane, ransomware or data theft
Mimikatz, Rubeus, SharpChromium on scheduler-adjacent admin hosts, rclone for egress.- Steps 1-4 completed
- Agents deployed on regulated / high-value targets
- Vaulted creds (CyberArk / HashiCorp Vault integration) reduce plaintext exposure — but agents still execute jobs on request
- Well-instrumented EDR catches Mimikatz on the scheduler host
7005/tcp (and any configured server ports) to the known agent CIDR list via host firewall (Windows Firewall inbound rules, nftables/iptables on Linux) plus upstream VLAN ACLs. Deploy within 3 days per the noisgate CRITICAL mitigation SLA. This blocks the pre-auth reachability step for any attacker who isn't already on an agent-registered host.- WAF or reverse proxy in front of Control-M/Server — the CTM protocol is proprietary binary, not HTTP; a web WAF has no signatures for it.
- MFA on Control-M/EM console — the vulnerability is in the server-agent communication path, not in the EM web UI, so console MFA does not close the hole.
- Disabling unused agents — the listener still accepts connections; the vulnerable command is exposed regardless of how many agents are registered.
- EDR on the scheduler host alone — helpful for detecting post-ex, but the initial RCE happens inside the trusted
controlmservice process; EDR is a detective control, not preventive against the injection itself.
The supporting signals.
| In-the-wild exploitation | None observed as of 2026-07-01. No GreyNoise tag, no CISA KEV listing, no public IR reports. |
|---|---|
| Public PoC | No public PoC at disclosure. Expect diffing within 2-6 weeks; watch watchTowr Labs, Assetnote, and SSD Secure Disclosure for follow-ups on BMC advisories. |
| EPSS | Not yet scored (disclosure day); expected initial band 0.1–1% rising after PoC drops. |
| CISA KEV | Not listed. No federal due-date. |
| CVSS 3.1 | AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H → 9.0 CRITICAL. AC:H reflects a non-trivial precondition; scope change reflects impact beyond the vulnerable component (agents). |
| Affected versions | Control-M/Server 9.0.20.x through 9.0.21.200 (inclusive) plus older unsupported branches (e.g., 9.0.19.x). |
| Fixed versions | 9.0.21.201 rollup / equivalent hotfix per BMC advisory. SaaS-managed Control-M tenants receive vendor-side patching. |
| Attack surface exposure | Control-M/Server is an *internal-only* orchestration component. Shodan/Censys show <100 exposed listeners globally — the realistic exposure is post-initial-access lateral, not internet-scan. |
| Disclosure | 2026-07-01 by BMC Software via customer support portal + docs.bmc.com announcement. |
| Reporting researcher | Coordinated disclosure via BMC PSIRT; original reporter not yet publicly credited. |
Sources.
Why this verdict
- Role multiplier (decisive): Control-M/Server occupies the fleet-orchestration high-value role by *definition* — ~100% of installs sit in this role. RCE on the scheduler yields command execution on every agent host it manages, plus the credential/vault material used to run jobs. This is the same class of blast radius as compromising a Jenkins controller, an SCCM primary, or a Rundeck server. That alone floors the verdict at CRITICAL.
- Unauthenticated, network-reachable, no user interaction:
AV:N/PR:N/UI:N. Any attacker with L3 reachability to the CTM listener can attempt exploitation. - Friction adjustment (does NOT break the floor): The scheduler is internal-only in ~99% of deployments, so the attacker must already have a foothold on the corporate network — this narrows attacker population but does not reduce blast radius. Per the noisgate role-multiplier rule this can pull the verdict ABOVE the floor but not BELOW it; here it holds at CRITICAL rather than pushing beyond.
- AC:H precondition: Non-trivial timing/state requirement will slow public PoC development by 2-6 weeks. This trims the score from 9.8 → 9.2 but does not change the bucket.
- No KEV / no PoC yet: Justifies not weighting this as an active-exploitation emergency, but for a scheduler that owns the batch plane of a Fortune-500, the pre-PoC window IS the patch window.
Why not higher?
No public PoC, no active exploitation, and AC:H reflects a real precondition attackers must satisfy. The listener is not internet-exposed in any meaningful population, so the immediate 'ransomware group scanning tomorrow' scenario doesn't fit — that's what separates a 9.2 from a 9.8 emergency like a public-facing edge-appliance RCE.
Why not lower?
Downgrading past CRITICAL would require evidence that Control-M/Server is NOT a fleet-orchestration component — it is, by design. Every credible deployment ends the chain at fleet command execution and credential loot. 'Requires internal access' is not sufficient friction to break the floor for a scheduler; the role multiplier rule explicitly overrides friction-only downgrades here.
Crowdsourced verification payload.
Run this on each Windows Control-M/Server host as a local administrator (Linux operators can adapt using ctm_menu + rpm -q/dpkg -l). Example invocation from an elevated PowerShell prompt: .\Check-CVE-2026-10539.ps1 -CtmHome 'C:\ProgramData\BMC Software\Control-M Server'. Reports VULNERABLE, PATCHED, or UNKNOWN.
#requires -Version 5.1
<#
Check-CVE-2026-10539.ps1
Detects Control-M/Server builds in the vulnerable range 9.0.20.x .. 9.0.21.200.
Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
#>
param(
[string]$CtmHome = $env:CONTROLM_SERVER_HOME
)
function Get-CtmServerVersion {
param([string]$Home)
# 1) Preferred: ctm_menu / ctmgetcm output
$ctmgetcm = Join-Path $Home 'exe\ctmgetcm.exe'
if (Test-Path $ctmgetcm) {
try {
$out = & $ctmgetcm '-version' 2>$null
if ($out -match '(\d+\.\d+\.\d+\.\d+)') { return $Matches[1] }
} catch {}
}
# 2) Fallback: file version on the main server binary
$bin = Join-Path $Home 'exe\ctmsys.exe'
if (Test-Path $bin) {
return (Get-Item $bin).VersionInfo.FileVersion
}
# 3) Registry fallback
$reg = 'HKLM:\SOFTWARE\BMC Software\Control-M/Server'
if (Test-Path $reg) {
$v = (Get-ItemProperty $reg -ErrorAction SilentlyContinue).Version
if ($v) { return $v }
}
return $null
}
function Compare-Version {
param([string]$V)
# Vulnerable: 9.0.20.0 <= V <= 9.0.21.200
$parts = $V.Split('.') | ForEach-Object { [int]$_ }
if ($parts.Count -lt 4) { return 'UNKNOWN' }
$vObj = [Version]::new($parts[0],$parts[1],$parts[2],$parts[3])
$low = [Version]'9.0.20.0'
$high = [Version]'9.0.21.200'
$fixed = [Version]'9.0.21.201'
if ($vObj -ge $fixed) { return 'PATCHED' }
if ($vObj -ge $low -and $vObj -le $high) { return 'VULNERABLE' }
if ($vObj -lt $low) { return 'VULNERABLE' } # unsupported older branches per BMC advisory
return 'UNKNOWN'
}
if (-not $CtmHome -or -not (Test-Path $CtmHome)) {
Write-Output 'UNKNOWN: Control-M/Server home not found. Pass -CtmHome explicitly.'
exit 2
}
$ver = Get-CtmServerVersion -Home $CtmHome
if (-not $ver) {
Write-Output 'UNKNOWN: could not determine Control-M/Server version.'
exit 2
}
$state = Compare-Version -V $ver
Write-Output ("{0}: Control-M/Server version {1}" -f $state, $ver)
switch ($state) {
'PATCHED' { exit 0 }
'VULNERABLE' { exit 1 }
default { exit 2 }
}