CISA has published ICS Advisory ICSA-26-281-02, disclosing a cluster of critical vulnerabilities in Grid Protection Alliance's openPDC (open Phasor Data Concentrator) and openHistorian — two of the most widely deployed open-source platforms for collecting, concentrating, and archiving synchrophasor data in electric utility environments. The advisory carries a maximum CVSS v3 score of 9.8 (Critical).
This is not a theoretical exposure. openPDC and openHistorian sit at the heart of the operational technology (OT) data pipeline: they ingest real-time phasor measurement unit (PMU) data from substations, feed it to energy management systems, and archive it for grid stability analysis. A compromise here means an attacker gains a foothold on a system that is typically reachable from both the control network and enterprise IT — a classic pivot point. The vulnerability classes disclosed — deserialization of untrusted data, missing authentication for critical functions, SSRF, hard-coded credentials, and unsafe reflection — are a chain-ready toolkit for unauthenticated remote code execution followed by lateral movement deeper into the OT environment.
If you operate synchrophasor infrastructure in generation, transmission, or distribution — or you are an integrator/MSSP supporting utilities — this advisory requires immediate action. Synchrophasor systems are also squarely in scope for NERC CIP environments, which raises the compliance stakes on top of the operational risk.
Technical Analysis
Affected Products and Versions
Per CISA ICSA-26-281-02:
| Product | Affected Versions | CVEs |
|---|---|---|
| openPDC | Prior to 2.9.477 / 2.9.482 | CVE-2026-104629, CVE-2026-100730, CVE-2026-105281, CVE-2026-85479, CVE-2026-101022 |
| openPDC (Docker image) | Prior to 2.9.477 / 2.9.482 | All of the above, plus CVE-2026-105278 |
| openHistorian | Prior to 2.8.580 / 2.8.585 | CVE-2026-104629, CVE-2026-100730, CVE-2026-105281, CVE-2026-85479, CVE-2026-101022 |
The dual fixed-version notation reflects that different CVEs are addressed in different builds — defenders should target the highest available build (2.9.482+ for openPDC, 2.8.585+ for openHistorian) to close the full set. Note that the Docker image carries an additional, sixth CVE (CVE-2026-105278), which likely reflects a container-specific weakness — containerized deployments are not a shortcut to safety here.
Vulnerability Classes and Defender's Attack-Chain View
The disclosed CWE categories map to a coherent exploitation chain:
-
Missing Authentication for Critical Function — management interfaces, web consoles, or data/configuration endpoints reachable without credentials. openPDC/openHistorian expose management web UIs and historian service listeners; if these are network-reachable (and in too many deployments they are bound to
0.0.0.0), an unauthenticated attacker can reach the vulnerable code paths directly. -
Deserialization of Untrusted Data (CVSS 9.8) — the headline risk. openPDC and openHistorian are .NET applications, and .NET deserialization flaws against attacker-controlled input routinely yield remote code execution under the service account context (typically
SYSTEMon Windows hosts, or root inside the container). This is the CVE almost certainly driving the 9.8 score. -
Use of Externally-Controlled Input to Select Classes or Code ('Unsafe Reflection') — a close cousin of deserialization; attacker-controlled type names or assembly references allow arbitrary code loading. Combined with (1), this is unauthenticated RCE by another route.
-
Hard-coded Credentials — embedded service or database credentials in shipped configurations. Even post-patch, any credential shipped with the product must be rotated; a patched binary with unchanged credentials is still compromised.
-
Server-Side Request Forgery (SSRF) — allows the attacker to coerce the openPDC/openHistorian host into making arbitrary outbound requests. In an OT context this is the lateral-movement primitive: reaching internal historian APIs, substation gateways, or cloud metadata endpoints (e.g.,
169.254.169.254) from a trusted network position.
Exploitation Status
At the time of publication, CISA reports no known public exploitation of these specific CVEs, and none of the six identifiers appear in the CISA Known Exploited Vulnerabilities (KEV) catalog as of this writing. However, ICS advisories at 9.8 with unauthenticated RCE classes historically attract rapid researcher attention — and synchrophasor infrastructure is a documented target of state-sponsored actors. Treat "not yet exploited" as a window, not a comfort.
Detection & Response
The most reliable post-exploitation signal for a deserialization/RCE flaw in a .NET Windows service or container is the service process doing things a data concentrator never does: spawning shells, making unexpected outbound connections, or writing to unusual paths. The detections below are tuned to that logic.
Sigma Rules
---
title: openPDC or openHistorian Process Spawning Shell or Script Interpreter
id: 3f8a2c91-7b4e-4d6a-9c21-8e5f0a1b2c3d
status: experimental
description: Detects openPDC/openHistorian service processes spawning command shells or script interpreters, consistent with post-exploitation following deserialization or unsafe reflection RCE (ICSA-26-281-02).
references:
- https://www.cisa.gov/news-events/ics-advisories/icsa-26-281-02
- https://attack.mitre.org/techniques/T1059/
author: Security Arsenal
date: 2026/10/08
tags:
- attack.execution
- attack.t1059
logsource:
category: process_creation
product: windows
detection:
selection_parent:
ParentImage|endswith:
- '\openPDC.exe'
- '\openPDCConsole.exe'
- '\openHistorian.exe'
- '\openHistorianConsole.exe'
- '\HistorianAdapters.exe'
selection_child:
Image|endswith:
- '\cmd.exe'
- '\powershell.exe'
- '\pwsh.exe'
- '\wscript.exe'
- '\cscript.exe'
- '\mshta.exe'
- '\rundll32.exe'
- '\regsvr32.exe'
- '\certutil.exe'
- '\bitsadmin.exe'
- '\wmic.exe'
condition: selection_parent and selection_child
falsepositives:
- Rare legitimate vendor diagnostic scripts invoked by administrators; verify with change records
level: critical
---
title: openPDC or openHistorian SSRF to Cloud Metadata or Internal Infrastructure
id: 6d1e4a72-9c83-4f5b-b7e2-2a9d0c4e5f6a
status: experimental
description: Detects outbound connections from openPDC/openHistorian processes to cloud instance metadata endpoints or link-local ranges, indicative of SSRF abuse per ICSA-26-281-02.
references:
- https://www.cisa.gov/news-events/ics-advisories/icsa-26-281-02
- https://attack.mitre.org/techniques/T1190/
author: Security Arsenal
date: 2026/10/08
tags:
- attack.initial_access
- attack.t1190
logsource:
category: network_connection
product: windows
detection:
selection_image:
Image|endswith:
- '\openPDC.exe'
- '\openPDCConsole.exe'
- '\openHistorian.exe'
- '\openHistorianConsole.exe'
selection_metadata:
DestinationIp:
- '169.254.169.254'
- '169.254.170.2'
- '100.100.100.200'
- '192.0.0.192'
condition: selection_image and selection_metadata
falsepositives:
- None expected; synchrophasor services have no legitimate reason to query cloud metadata endpoints
level: critical
---
title: Unauthenticated-Style Access to openPDC or openHistorian Management Interfaces from Unexpected Hosts
id: 9b2c5d83-1e6f-4a7c-c8d3-5f1e2a3b4c5d
status: experimental
description: Detects inbound network connections to openPDC/openHistorian management web interfaces originating from hosts outside expected OT/EMS segments, consistent with exploitation of missing authentication for critical functions (ICSA-26-281-02). Tune AllowedSources to your environment.
references:
- https://www.cisa.gov/news-events/ics-advisories/icsa-26-281-02
- https://attack.mitre.org/techniques/T1190/
author: Security Arsenal
date: 2026/10/08
tags:
- attack.initial_access
- attack.t1190
- attack.t0883
logsource:
category: firewall
product: windows
detection:
selection:
dst_port:
- 8180
- 8280
- 8888
- 6165
filter_allowed:
src_ip|cidr:
- '10.0.0.0/8'
- '192.168.0.0/16'
condition: selection and not filter_allowed
falsepositives:
- Internet-routable deployments (strongly discouraged); scanner infrastructure; tune source CIDRs to approved EMS/SCADA management segments
level: high
KQL — Microsoft Sentinel / Defender
Hunt both native Windows telemetry (Defender) and container/Syslog telemetry (Docker deployments) in one pass. The first query targets child-process and network behavior; the second targets the management-plane exposure for containerized openPDC.
// Hunt 1: openPDC/openHistorian service spawning shells or reaching suspicious destinations
let ServiceProcs = dynamic(["openPDC.exe","openPDCConsole.exe","openHistorian.exe","openHistorianConsole.exe","HistorianAdapters.exe"]);
let SuspiciousChildren = dynamic(["cmd.exe","powershell.exe","pwsh.exe","wscript.exe","cscript.exe","mshta.exe","rundll32.exe","regsvr32.exe","certutil.exe","bitsadmin.exe","wmic.exe"]);
let ProcHits = DeviceProcessEvents
| where TimeGenerated > ago(14d)
| where InitiatingProcessFileName in~ (ServiceProcs)
| where FileName in~ (SuspiciousChildren)
| project TimeGenerated, DeviceName, InitiatingProcessFileName, FileName, ProcessCommandLine, AccountName, ReportId;
let NetHits = DeviceNetworkEvents
| where TimeGenerated > ago(14d)
| where InitiatingProcessFileName in~ (ServiceProcs)
| where RemoteIP in ("169.254.169.254","169.254.170.2","100.100.100.200")
or (RemoteUrl has_any ("metadata.google.internal"))
| project TimeGenerated, DeviceName, InitiatingProcessFileName, RemoteIP, RemotePort, RemoteUrl, ReportId;
union ProcHits, NetHits
| sort by TimeGenerated desc;
// Hunt 2: External/unexpected sources hitting openPDC/openHistorian management listeners (CEF/Syslog firewall ingestion)
CommonSecurityLog
| where TimeGenerated > ago(14d)
| where DestinationPort in (8180, 8280, 8888, 6165)
| where DeviceAction in ("allow","allowed","accept","Accept") or isempty(DeviceAction)
| where not(ipv4_is_private(SourceIP))
| summarize ConnectionCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated)
by SourceIP, DestinationIP, DestinationPort, DeviceVendor
| sort by ConnectionCount desc;
// Hunt 3: Docker-host syslog for openPDC container shell execution (possible container breakout or post-exploit triage)
Syslog
| where TimeGenerated > ago(14d)
| where ProcessName has_any ("dockerd","containerd")
| where SyslogMessage has_any ("openpdc","openhistorian")
| where SyslogMessage has_any ("exec","/bin/sh","/bin/bash","sh -c")
| project TimeGenerated, Computer, ProcessName, SyslogMessage
| sort by TimeGenerated desc;
Velociraptor VQL
Use this artifact across Windows OT DMZ jump hosts and historians to enumerate running openPDC/openHistorian processes, their loaded state, and any non-standard listening or established sockets attributable to them — rapid scoping before and after patching.
-- Scope openPDC/openHistorian exposure: processes, versions on disk, and network sockets
-- ICSA-26-281-02 (CVE-2026-104629 et al.)
LET procs = SELECT Pid, Name, Exe, CommandLine, Username, CreateTime
FROM pslist()
WHERE Name =~ '(?i)openpdc|openhistorian|historianadapters'
LET sockets = SELECT Pid, Name, Family, Type, Laddr, Lport, Raddr, Rport, Status
FROM netstat()
WHERE Name =~ '(?i)openpdc|openhistorian'
LET binaries = SELECT FullPath, Size, Mtime
FROM glob(globs=['C:/Program Files/openPDC/**/*.exe','C:/Program Files/openHistorian/**/*.exe','C:/Program Files (x86)/openPDC/**/*.exe'])
WHERE FullPath =~ '(?i)openpdc|openhistorian'
SELECT * FROM procs
UNION ALL
SELECT * FROM sockets
For Docker hosts, pull the running image tag directly — this is your patch-verification primitive:
-- Verify openPDC Docker image version on Linux/container hosts
SELECT Pid, Name, CommandLine, Exe
FROM pslist()
WHERE CommandLine =~ '(?i)openpdc'
OR CommandLine =~ '(?i)openhistorian'
Remediation
1. Patch Immediately — This Is the Only Complete Fix
Upgrade to the fixed builds per Grid Protection Alliance's releases:
- openPDC: 2.9.482 or later (2.9.477 addresses a subset; 2.9.482 completes the set — do not stop at the earlier build)
- openPDC Docker image: pull and redeploy 2.9.482 or later — note the Docker image carries an additional CVE (CVE-2026-105278), so container deployments are more exposed, not less
- openHistorian: 2.8.585 or later
Verify the deployment post-upgrade:
# openPDC / openHistorian patch verification and exposure audit (Windows hosts)
# Run elevated on each host running openPDC or openHistorian services
# 1. Enumerate installed openPDC/openHistorian services and binary versions
Get-CimInstance Win32_Service |
Where-Object { $_.PathName -match 'openPDC|openHistorian|HistorianAdapters' } |
ForEach-Object {
$exe = ($_.PathName -replace '"','') -split ' ' | Select-Object -First 1
$ver = if (Test-Path $exe) { (Get-Item $exe).VersionInfo.ProductVersion } else { 'NOT FOUND' }
[PSCustomObject]@{
Service = $_.Name
State = $_.State
StartName = $_.StartName
Binary = $exe
Version = $ver
Compliant = ($ver -ge '2.9.482' -and $_.Name -match 'PDC') -or ($ver -ge '2.8.585' -and $_.Name -match 'Historian')
}
} | Format-Table -AutoSize
# 2. Identify listeners bound to all interfaces (0.0.0.0) on management/historian ports
Get-NetTCPConnection -State Listen |
Where-Object { $_.LocalPort -in 8180,8280,8888,6165 -and $_.LocalAddress -in '0.0.0.0','::' } |
Select-Object LocalAddress, LocalPort, OwningProcess,
@{N='Process';E={(Get-Process -Id $_.OwningProcess).ProcessName}} |
Format-Table -AutoSize
# 3. Audit child processes of openPDC/openHistorian for signs of prior exploitation
Get-CimInstance Win32_Process |
Where-Object { $_.Name -match 'cmd|powershell|pwsh|mshta|rundll32' } |
ForEach-Object {
$parent = Get-CimInstance Win32_Process -Filter "ProcessId=$($_.ParentProcessId)" -ErrorAction SilentlyContinue
if ($parent.Name -match 'openPDC|openHistorian') {
[PSCustomObject]@{ Time=$_.CreationDate; Child=$_.Name; CmdLine=$_.CommandLine; Parent=$parent.Name }
}
} | Format-List
# 4. Check for cloud metadata access in Windows Firewall / DNS cache (SSRF indicator)
Get-NetConnectionProfile | Out-Null
Resolve-DnsName -Name 'metadata.google.internal' -ErrorAction SilentlyContinue
#!/usr/bin/env bash
# openPDC Docker deployment: version verification and hardening (Linux hosts)
# ICSA-26-281-02 — upgrade to gridprotectionalliance/openpdc:2.9.482 or later
set -euo pipefail
# 1. Report currently running openPDC container image tags
echo "=== Running openPDC containers ==="
docker ps --filter "ancestor=gridprotectionalliance/openpdc" --format '{{.Names}} {{.Image}} {{.Status}}'
docker ps -a --format '{{.Names}} {{.Image}}' | grep -iE 'openpdc|openhistorian' || echo "No matching containers found"
# 2. Pull the patched image and verify tag
echo "=== Pulling patched image ==="
docker pull gridprotectionalliance/openpdc:latest
docker images gridprotectionalliance/openpdc --format '{{.Tag}} {{.CreatedAt}}'
# 3. Audit published ports — management interfaces must NOT be bound to 0.0.0.0
echo "=== Published port audit (flag any 0.0.0.0 bindings on mgmt ports) ==="
docker ps --format '{{.Names}} {{.Ports}}' | grep -iE 'openpdc|openhistorian'
# 4. Check for shell execution inside containers (post-exploitation indicator)
echo "=== Container exec audit ==="
for c in $(docker ps --format '{{.Names}}' | grep -iE 'openpdc|openhistorian'); do
echo "--- $c ---"
docker logs "$c" 2>&1 | grep -iE 'exec|/bin/sh|/bin/bash' | tail -20 || true
done
# 5. Verify no outbound connections to metadata endpoints from containers
echo "=== SSRF / metadata egress check (conntrack) ==="
conntrack -L 2>/dev/null | grep -E '169.254.169.254|169.254.170.2' || echo "No metadata connections observed"
2. Rotate All Credentials — Patch Is Not Enough
Because the advisory includes hard-coded credentials, treat every credential shipped with or created by the product as compromised:
- Rotate the openPDC/openHistorian database credentials (SQL Server / SQLite connection strings in
openPDC.exe.config,openHistorian.exe.config, or environment variables for containers) - Rotate any service account under which the services run; move to a gMSA or least-privilege local service account
- Rotate web console admin passwords and any API keys stored in configuration
3. Compensating Controls Where Patching Must Wait (Maintenance Windows)
OT patch cycles are constrained — if you cannot patch within days, apply these immediately:
- Bind management interfaces to localhost or a dedicated management NIC. Edit the service configuration so the web console and configuration endpoints listen on
127.0.0.1or a hardened management subnet only — this directly mitigates the missing-authentication exposure. - Firewall segmentation. Restrict access to openPDC/openHistorian listeners (management web ports and historian data ports) to an explicit allowlist of EMS/SCADA management hosts. Deny all inbound from IT networks and the internet. In NERC CIP terms: these hosts belong behind your Electronic Security Perimeter with Electronic Access Points enforced.
- Egress filtering. Block outbound traffic from openPDC/openHistorian hosts to anything not operationally required — specifically deny
169.254.169.254/32, link-local ranges, and the broader internet. This neuters the SSRF primitive. - Container hardening. Run the Docker image as non-root, drop capabilities (
--cap-drop ALL), mount the filesystem read-only where possible, and never publish management ports on0.0.0.0— bind to a specific internal interface. - Enhanced monitoring. Deploy the Sigma rules and KQL hunts above to your SIEM now; alert on any child process of openPDC/openHistorian services at CRITICAL severity.
4. Threat-Hunt Before You Assume Clean
Because these are unauthenticated, internet-adjacent-exploitable classes, run retroactive hunts (14–30 days lookback) using the queries above before declaring the patch complete. Any hit on child-process spawning or metadata-endpoint connections warrants full IR scoping — memory capture of the service process, config file review for injected adapter definitions, and credential rotation as a containment measure.
References
- CISA ICS Advisory: https://www.cisa.gov/news-events/ics-advisories/icsa-26-281-02
- Grid Protection Alliance (openPDC / openHistorian releases): https://github.com/GridProtectionAlliance
- CISA ICS-CERT recommended practices: https://www.cisa.gov/resources-tools/resources/ics-recommended-practices
Bottom line: five-to-six critical CVEs, unauthenticated RCE classes, sitting on the IT/OT boundary of electric grid operations. Patch to openPDC 2.9.482+ / openHistorian 2.8.585+, rotate every credential the product ever touched, segment the management plane, and hunt backward for the child-process and SSRF signatures. The exploitation window for advisories like this closes fast — on the attacker's schedule, not yours.
Related Resources
Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub
Is your security operations ready?
Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.