Threat Overview
CISA published ICS advisory ICSA-26-274-01 on October 1, 2026 for Armatura One, a web-based physical access-control platform deployed worldwide in communications, critical manufacturing, energy, and transportation. The cluster affects Armatura One before 4.7.2 and Armatura One (USA) before 4.6.1 and can lead to database compromise, highest-privilege host code execution, or control of the physical access-control system.
The most remotely consequential issue is the embedded Apache ActiveMQ exposure to CVE-2023-46604: the OpenWire listener is exposed on the network by default, and unauthenticated deserialization can execute code before authentication. The remaining issues weaken credential protection: CVE-2026-94591 uses a fixed AES-128-CBC key and IV for stored database/message-broker credentials; CVE-2026-94592 initializes the database superuser with a vendor-defined fixed password; CVE-2026-94593 logs a backup/restore database command including the superuser password; and CVE-2026-94594 logs message-broker client credentials in plaintext. CISA confirms the product flaws and impact. Public reporting reviewed for this package does not confirm active exploitation of Armatura One. CVE-2023-46604 has been exploited broadly in other ActiveMQ deployments, but that does not establish exploitation of this product.
References
- CISA, “Armatura LLC Armatura One,” ICSA-26-274-01, published October 1, 2026: https://www.cisa.gov/news-events/ics-advisories/icsa-26-274-01
- Armatura PSIRT, VU#855730, published September 14, 2026: https://armatura.us/SecurityCenter.html
- CISA CSAF record for ICSA-26-274-01: https://raw.githubusercontent.com/cisagov/CSAF/develop/csaf_files/OT/white/2026/icsa-26-274-01.json
Impacted Systems
- Vendor/product: Armatura LLC Armatura One and Armatura One (USA), centralized web-based physical access-control management platform.
- Affected versions: Armatura One earlier than 4.7.2; Armatura One (USA) earlier than 4.6.1.
- Fixed versions: Armatura One 4.7.2; Armatura One (USA) 4.6.1_USA. Vendor support may be required to obtain/apply the upgrade.
- Platform/deployment: Self-managed server deployment; underlying operating-system versions are not specified in the cited advisory.
- Roles/components: Embedded Apache ActiveMQ/OpenWire broker, application database, backup/restore workflow, message-broker logs, and physical-access control management.
- Exposure/prerequisites: CVE-2023-46604 requires network reachability to the embedded OpenWire listener and no credentials. CVE-2026-94591 requires possession of the installer and separate access to the encrypted configuration. CVE-2026-94592 requires server access and an unchanged default database password. CVE-2026-94593 requires low-privileged local log access; CVE-2026-94594 requires read access to broker logs, backups, or support bundles.
- Explicitly unaffected: Armatura One 4.7.2 and later and Armatura One (USA) 4.6.1_USA and later are outside the cited affected ranges.
Why this matters
Compromise of a physical access-control platform can cross cyber and physical boundaries. The default network exposure of a known remote-code-execution flaw, combined with reusable or logged privileged credentials, creates multiple routes from network access to host, database, and badge/access-control manipulation. Although product-specific exploitation is not confirmed, the consequence and critical-sector deployment justify urgent inventory and exposure review plus targeted hunting where telemetry exists.
Exploitation Status
No confirmed Armatura One exploitation in the cited reporting. CVE-2023-46604 is a historically exploited Apache ActiveMQ vulnerability, but CISA’s Armatura advisory does not state that Armatura One has been exploited. The four 2026 CVEs are not listed as known exploited in the sources reviewed. Treat scans, OpenWire access, Java child processes, credential-reading commands, or unusual server egress as suspicious context requiring validation, not proof of exploitation.
What this hunt looks for
Untrusted or new access to the embedded OpenWire listener, connection bursts, Java/ActiveMQ child processes, persistence creation, rare server egress, command-line access to configuration or credential-bearing logs, and Windows/Syslog fallback activity.
Required logs
An authoritative Armatura server inventory; firewall or flow telemetry in CommonSecurityLog; endpoint process and network telemetry in DeviceProcessEvents and DeviceNetworkEvents; Windows Security Event 4688 or Linux Syslog; and Armatura/ActiveMQ application logs where available.
Hunt 1 — External access to Armatura One OpenWire service
let lookback = 90d;
let ArmaturaIPs = dynamic(["REPLACE_WITH_ARMATURA_SERVER_IP"]);
CommonSecurityLog
| where TimeGenerated >= ago(lookback)
| where DestinationIP in (ArmaturaIPs) and DestinationPort == 61616
| where not(ipv4_is_private(SourceIP))
| project TimeGenerated, SourceIP, SourcePort, DestinationIP, DestinationPort, Protocol, DeviceAction, ApplicationProtocol, Message
| order by TimeGenerated desc
Hunt 2 — New sources reaching OpenWire on an Armatura server
let baseline = 30d;
let recent = 2d;
let ArmaturaIPs = dynamic(["REPLACE_WITH_ARMATURA_SERVER_IP"]);
let known = CommonSecurityLog
| where TimeGenerated between (ago(baseline) .. ago(recent))
| where DestinationIP in (ArmaturaIPs) and DestinationPort == 61616
| summarize by SourceIP, DestinationIP;
CommonSecurityLog
| where TimeGenerated >= ago(recent)
| where DestinationIP in (ArmaturaIPs) and DestinationPort == 61616
| join kind=leftanti known on SourceIP, DestinationIP
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Connections=count(), Actions=make_set(DeviceAction, 20) by SourceIP, DestinationIP
| order by LastSeen desc
Hunt 3 — OpenWire connection bursts or scanning behavior
let lookback = 14d;
let ArmaturaIPs = dynamic(["REPLACE_WITH_ARMATURA_SERVER_IP"]);
CommonSecurityLog
| where TimeGenerated >= ago(lookback)
| where DestinationIP in (ArmaturaIPs) and DestinationPort == 61616
| summarize Connections=count(), SourcePorts=dcount(SourcePort), Actions=make_set(DeviceAction, 10) by SourceIP, DestinationIP, bin(TimeGenerated, 5m)
| where Connections >= 20
| order by TimeGenerated desc
Hunt 4 — Java or ActiveMQ spawning a shell or scripting engine
let lookback = 90d;
let ArmaturaHosts = dynamic(["REPLACE_WITH_ARMATURA_HOSTNAME"]);
DeviceProcessEvents
| where Timestamp >= ago(lookback)
| where DeviceName in~ (ArmaturaHosts)
| where InitiatingProcessFileName in~ ("java", "java.exe")
| where FileName in~ ("cmd.exe", "powershell.exe", "pwsh.exe", "sh", "bash", "dash", "python", "python3", "perl", "curl", "wget", "certutil.exe")
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessCommandLine, SHA256
| order by Timestamp desc
Hunt 5 — Persistence or service creation from the Java lineage
let lookback = 90d;
let ArmaturaHosts = dynamic(["REPLACE_WITH_ARMATURA_HOSTNAME"]);
DeviceProcessEvents
| where Timestamp >= ago(lookback)
| where DeviceName in~ (ArmaturaHosts)
| where InitiatingProcessFileName in~ ("java", "java.exe") or InitiatingProcessCommandLine has_any ("activemq", "Armatura")
| extend Cmd=tolower(ProcessCommandLine)
| where FileName in~ ("sc.exe", "schtasks.exe", "reg.exe", "systemctl", "service", "crontab") or Cmd has_any ("currentversion\\run", "authorized_keys", "/etc/cron", "systemctl enable", "create service")
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc
Hunt 6 — Rare outbound destinations from Armatura server processes
let baseline = 30d;
let recent = 2d;
let ArmaturaHosts = dynamic(["REPLACE_WITH_ARMATURA_HOSTNAME"]);
let known = DeviceNetworkEvents
| where Timestamp between (ago(baseline) .. ago(recent))
| where DeviceName in~ (ArmaturaHosts)
| summarize by DeviceId, RemoteIP, RemoteUrl, RemotePort;
DeviceNetworkEvents
| where Timestamp >= ago(recent)
| where DeviceName in~ (ArmaturaHosts)
| join kind=leftanti known on DeviceId, RemoteIP, RemoteUrl, RemotePort
| summarize FirstSeen=min(Timestamp), LastSeen=max(Timestamp), Connections=count(), Processes=make_set(InitiatingProcessFileName, 20), Commands=make_set(InitiatingProcessCommandLine, 10) by DeviceId, DeviceName, RemoteIP, RemoteUrl, RemotePort
| order by LastSeen desc
Hunt 7 — Commands reading Armatura configuration, backup, or broker logs
let lookback = 90d;
let ArmaturaHosts = dynamic(["REPLACE_WITH_ARMATURA_HOSTNAME"]);
DeviceProcessEvents
| where Timestamp >= ago(lookback)
| where DeviceName in~ (ArmaturaHosts)
| where FileName in~ ("cmd.exe", "powershell.exe", "pwsh.exe", "type.exe", "findstr.exe", "grep", "cat", "sed", "awk", "tar", "zip", "7z.exe")
| extend Cmd=tolower(ProcessCommandLine)
| where Cmd has_any ("armatura", "activemq", "connectionstring", "password", "credential", "backup", "restore", "broker") and Cmd has_any (".log", ".conf", ".config", ".properties", ".xml", ".bak", ".zip")
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc
Hunt 8 — Windows process-creation fallback for Java-to-shell execution
let lookback = 90d;
let ArmaturaHosts = dynamic(["REPLACE_WITH_ARMATURA_HOSTNAME"]);
SecurityEvent
| where TimeGenerated >= ago(lookback)
| where Computer in~ (ArmaturaHosts) and EventID == 4688
| extend NewProcess=tostring(NewProcessName), Parent=tostring(ParentProcessName), Cmd=tostring(CommandLine)
| where Parent endswith "\\java.exe"
| where NewProcess endswith "\\cmd.exe" or NewProcess endswith "\\powershell.exe" or NewProcess endswith "\\pwsh.exe" or NewProcess endswith "\\certutil.exe" or NewProcess endswith "\\curl.exe"
| project TimeGenerated, Computer, SubjectUserName, NewProcess, Parent, Cmd
| order by TimeGenerated desc
Hunt 9 — Syslog fallback for ActiveMQ errors and suspicious child activity
let lookback = 90d;
Syslog
| where TimeGenerated >= ago(lookback)
| where SyslogMessage has_any ("ActiveMQ", "OpenWire", "activemq")
| where SyslogMessage has_any ("unmarshal", "deserialize", "ClassPathXmlApplicationContext", "ExceptionResponse", "command", "shell", "wget", "curl", "failed", "error")
| project TimeGenerated, Computer, HostName, ProcessName, Facility, SeverityLevel, SyslogMessage
| order by TimeGenerated desc
Detection Notes
- Hunts 4 and 5 are highest signal when the Armatura server is correctly scoped: a Java/ActiveMQ lineage launching an interpreter or creating persistence is rarely expected. Validate legitimate backup, upgrade, and support activity.
- Hunts 1–3 require replacement of the placeholder with authoritative Armatura server IPs. TCP/61616 is the default ActiveMQ OpenWire port, but confirm the deployed listener and NAT path; alternate ports or internal-only exposure will not match.
- Network reachability or a burst to 61616 is not proof of CVE-2023-46604 exploitation. Conversely, successful deserialization can be very fast and may not create a distinctive network volume.
- Hunt 9 uses strings associated with ActiveMQ/deserialization investigation, not a product-specific exploit signature. Log level and forwarding configuration determine visibility.
- Credential flaws CVE-2026-94591 through CVE-2026-94594 are hard to detect directly because file-read telemetry is often unavailable. Hunt 7 observes command-line access only and will miss reads performed by custom tools, direct APIs, or unmonitored local access.
- Physical-access changes may reside only in Armatura audit logs. If those logs are not ingested, Sentinel cannot determine whether badge, door, or access-policy state changed.