mcpField
D
detecting-attacks-on-historian-servers

detecting-attacks-on-historian-servers

github.com/mukul975セキュリティ35.2k63 回インストール8 週間前に更新
リポジトリ

detecting-attacks-on-historian-servers について

OSIsoft PI や Ignition などの運用技術ヒストリアンを監視するチーム向けのセキュリティスキルです。不正アクセス、データ改ざん、横移動、ヒストリアン固有の脆弱性悪用を調べる手順を示します。

主な機能

  1. 01ヒストリアンの攻撃兆候監視
  2. 02不正なクライアントとクエリの確認
  3. 03データ改ざんの調査
  4. 04横移動の評価
  5. 05ヒストリアンデータの完全性確認

活用シーン

  1. 01不審なヒストリアン接続の調査
  2. 02OT インシデント後のデータ完全性確認
  3. 03ヒストリアン固有の脆弱性悪用の調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mukul975/anthropic-cybersecurity-skills --skill detecting-attacks-on-historian-servers

インストール後、Claude Code で/detecting-attacks-on-historian-serversを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

LICENSE
references/
api-reference.md
scripts/
agent.py

詳細

ヒストリアンサーバーへの攻撃の検出

使用する場面

  • ITネットワークとOTネットワークを接続するヒストリアンサーバーを監視し、侵害の兆候を検出するとき
  • プロセスヒストリアンのデータベースに対する不正なクエリやデータ改ざんを検出するとき
  • ITゾーンとOTゾーンの間で、ヒストリアンサーバーを経由するラテラルムーブメントを調査するとき
  • ヒストリアン固有の脆弱性(CVE-2025-0921)の悪用に関するアラートに対応するとき
  • OTセキュリティインシデントが疑われる場合に、ヒストリアンのデータ整合性を検証するとき

使用しないでください。一般的なデータベースセキュリティの監視(データベースセキュリティのスキルを参照)、ヒストリアンのデプロイと設定、またはIT専用データウェアハウスのセキュリティには使用しないでください。

前提条件

  • ヒストリアンサーバーのインベントリ(OSIsoft PI、Ignition、GE Proficy、Wonderware InSQL)
  • ヒストリアンのネットワークセグメント(IT側とOT側の両方のインターフェース)のネットワーク監視
  • データ整合性検証のためのヒストリアンAPIアクセス
  • 通常のヒストリアンクエリパターンのベースライン(どのアプリケーションがどのタグにクエリを実行するか)
  • ヒストリアンのアーキテクチャ(データソース、インターフェース、クライアント接続)に関する理解

ワークフロー

ステップ 1:ヒストリアンを監視して攻撃の兆候を検出する
#!/usr/bin/env python3
"""OT Historian Attack Detector.

Monitors historian servers for unauthorized access, data manipulation,
lateral movement indicators, and exploitation of historian-specific
vulnerabilities. Supports OSIsoft PI and Ignition platforms.
"""

import json
import sys
from collections import defaultdict
from datetime import datetime, timedelta
from typing import Dict, List, Optional

try:
    import requests
except ImportError:
    print("Install requests: pip install requests")
    sys.exit(1)


class HistorianAttackDetector:
    """Detects attacks targeting OT historian servers."""

    def __init__(self, historian_type: str, historian_url: str,
                 api_credentials: dict, verify_ssl: bool = False):
        self.historian_type = historian_type
        self.historian_url = historian_url.rstrip("/")
        self.credentials = api_credentials
        self.verify_ssl = verify_ssl
        self.alerts = []
        self.authorized_clients = set()
        self.authorized_queries = {}

    def set_baseline(self, authorized_clients: List[str],
                     authorized_query_patterns: Dict[str, List[str]]):
        """Set baseline of authorized historian clients and query patterns."""
        self.authorized_clients = set(authorized_clients)
        self.authorized_queries = authorized_query_patterns

    def check_active_connections(self) -> List[dict]:
        """Check for unauthorized connections to historian."""
        connections = []

        if self.historian_type == "osisoft_pi":
            try:
                resp = requests.get(
                    f"{self.historian_url}/piwebapi/system/status",
                    auth=(self.credentials.get("username"), self.credentials.get("password")),
                    verify=self.verify_ssl,
                    timeout=10,
                )
                if resp.status_code == 200:
                    data = resp.json()
                    connections = data.get("ConnectedClients", [])
            except requests.RequestException as e:
                print(f"[!] PI Web API error: {e}")

        elif self.historian_type == "ignition":
            try:
                resp = requests.get(
                    f"{self.historian_url}/data/status/connections",
                    headers={"Authorization": f"Bearer {self.credentials.get('token')}"},
                    verify=self.verify_ssl,
                    timeout=10,
                )
                if resp.status_code == 200:
                    connections = resp.json().get("connections", [])
            except requests.RequestException as e:
                print(f"[!] Ignition API error: {e}")

        # Check for unauthorized clients
        for conn in connections:
            client_ip = conn.get("client_ip", conn.get("address", ""))
            if self.authorized_clients and client_ip not in self.authorized_clients:
                self.alerts.append({
                    "severity": "HIGH",
                    "type": "UNAUTHORIZED_HISTORIAN_CLIENT",
                    "timestamp": datetime.now().isoformat(),
                    "source_ip": client_ip,
                    "details": f"Unauthorized client {client_ip} connected to {self.historian_type} historian",
                    "mitre": "T0802 - Automated Collection",
                })

        return connections

    def check_data_integrity(self, tags: List[str], hours_back: int = 24):
        """Check historian data for manipulation indicators."""
        print(f"[*] Checking data integrity for {len(tags)} tags over last {hours_back}h")

        integrity_issues = []
        for tag in tags:
            try:
                if self.historian_type == "osisoft_pi":
                    resp = requests.get(
                        f"{self.historian_url}/piwebapi/streams/{tag}/recorded",
                        params={"startTime": f"*-{hours_back}h", "endTime": "*"},
                        auth=(self.credentials.get("username"), self.credentials.get("password")),
                        verify=self.verify_ssl,
                        timeout=15,
                    )
                    if resp.status_code == 200:
                        items = resp.json().get("Items", [])
                        # Check for suspicious patterns
                        if len(items) == 0:
                            integrity_issues.append({
                                "tag": tag, "issue": "NO_DATA",
                                "detail": "No data points in expected timeframe - possible deletion",
                            })
                        else:
                            values = [i.get("Value", 0) for i in items if isinstance(i.get("Value"), (int, float))]
                            if values and len(set(values)) == 1 and len(values) > 100:
                                integrity_issues.append({
                                    "tag": tag, "issue": "FLATLINE",
                                    "detail": f"Constant value {values[0]} for {len(values)} points - possible replay/spoofing",
                                })
            except requests.RequestException:
                pass

        for issue in integrity_issues:
            self.alerts.append({
                "severity": "HIGH",
                "type": f"DATA_INTEGRITY_{issue['issue']}",
                "timestamp": datetime.now().isoformat(),
                "tag": issue["tag"],
                "details": issue["detail"],
                "mitre": "T0809 - Data Destruction" if issue["issue"] == "NO_DATA" else "T0832 - Manipulation of View",
            })

        return integrity_issues

    def check_lateral_movement_indicators(self):
        """Check for indicators of historian being used as pivot point."""
        indicators = []

        # Check 1: Historian making outbound connections to Level 1 devices
        # (Historian should receive data, not initiate connections to PLCs)
        indicators.append({
            "check": "Outbound connections to PLC subnets",
            "description": "Historian initiating connections to Level 1 devices may indicate compromise",
            "detection": "Monitor firewall logs for historian IP connecting to PLC ports (502, 102, 44818)",
        })

        # Check 2: New processes or services on historian
        indicators.append({
            "check": "Unauthorized processes on historian server",
            "description": "Attackers may install tools on historian for lateral movement",
            "detection": "Monitor process creation events (Sysmon EventID 1) on historian",
        })

        # Check 3: Unusual authentication to historian
        indicators.append({
            "check": "Authentication from unexpected sources",
            "description": "Compromised IT systems authenticating to historian for pivoting",
            "detection": "Monitor Windows Security Event 4624 for logons from non-baseline sources",
        })

        return indicators

    def generate_report(self):
        """Generate historian attack detection report."""
        print(f"\n{'='*70}")
        print("HISTORIAN ATTACK DETECTION REPORT")
        print(f"{'='*70}")
        print(f"Historian Type: {self.historian_type}")
        print(f"Historian URL: {self.historian_url}")
        print(f"Report Time: {datetime.now().isoformat()}")
        print(f"Total Alerts: {len(self.alerts)}")

        if self.alerts:
            print(f"\n--- ALERTS ---")
            for alert in self.alerts:
                print(f"\n  [{alert['severity']}] {alert['type']}")
                print(f"    Time: {alert['timestamp']}")
                print(f"    Detail: {alert['details']}")
                print(f"    MITRE ICS: {alert.get('mitre', 'N/A')}")

        print(f"\n--- LATERAL MOVEMENT CHECKS ---")
        for indicator in self.check_lateral_movement_indicators():
            print(f"\n  Check: {indicator['check']}")
            print(f"    Risk: {indicator['description']}")
            print(f"    Detection: {indicator['detection']}")


if __name__ == "__main__":
    detector = HistorianAttackDetector(
        historian_type="osisoft_pi",
        historian_url="https://pi-server.plant.local",
        api_credentials={"username": "pi_reader", "password": "api_key_here"},
    )

    detector.set_baseline(
        authorized_clients=["10.10.2.10", "10.10.2.20", "10.10.3.50", "10.10.150.10"],
        authorized_query_patterns={},
    )

    detector.check_active_connections()
    detector.check_data_integrity(tags=["REACTOR_01.TEMP", "PUMP_03.FLOW"], hours_back=24)
    detector.generate_report()

主な概念

用語定義
OT ヒストリアンSCADA/DCSシステムの時系列プロセスデータを保存するデータベースサーバー(OSIsoft PI、Ignition、Wonderware)
ピボットポイントITネットワークとOTネットワークの間に位置するヒストリアンは、攻撃者がゾーン間を移動する際の格好の標的となります
データリプレイ攻撃HMIに過去のデータを送信し、リアルタイムのプロセス改ざんを隠蔽する手法(Stuxnetの手法)
OSIsoft PI最も広く導入されているOTヒストリアンで、Global 500のプロセス産業企業の65%が使用しています
IgnitionHistorianモジュールを備えたInductive AutomationのSCADAプラットフォームで、Pythonスクリプト機能により標的にされることが増えています
CVE-2025-0921悪意のあるプロジェクトファイルを介した権限昇格を可能にする、Ignition SCADAの特権ファイルシステム脆弱性

出力形式

HISTORIAN ATTACK DETECTION REPORT
====================================
Historian: [type and hostname]
Date: YYYY-MM-DD

CONNECTION ANALYSIS:
  Authorized Clients: [count]
  Unauthorized Clients Detected: [count with IPs]

DATA INTEGRITY:
  Tags Checked: [count]
  Integrity Issues: [count]
  Flatline Detections: [count]
  Data Gaps: [count]

LATERAL MOVEMENT INDICATORS:
  Outbound PLC Connections: [found/not found]
  Unauthorized Processes: [found/not found]
  Anomalous Authentication: [found/not found]

コメント