Back to Intelligence

ICSA-26-281-02: Critical openPDC & openHistorian Flaws (CVSS 9.8) — Detection and Remediation Guide for Energy-Sector Defenders

SA
Security Arsenal Team
October 8, 2026
12 min read

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:

ProductAffected VersionsCVEs
openPDCPrior to 2.9.477 / 2.9.482CVE-2026-104629, CVE-2026-100730, CVE-2026-105281, CVE-2026-85479, CVE-2026-101022
openPDC (Docker image)Prior to 2.9.477 / 2.9.482All of the above, plus CVE-2026-105278
openHistorianPrior to 2.8.580 / 2.8.585CVE-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:

  1. 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.

  2. 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 SYSTEM on Windows hosts, or root inside the container). This is the CVE almost certainly driving the 9.8 score.

  3. 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.

  4. 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.

  5. 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

YAML
---
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.

KQL — Microsoft Sentinel / Defender
// 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.

VQL — Velociraptor
-- 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:

VQL — Velociraptor
-- 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:

PowerShell
# 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
Bash / Shell
#!/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.1 or 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 on 0.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

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.