performing-ot-network-security-assessment について
産業セキュリティ評価担当者向けのOT/ICSネットワーク評価スキルです。受動的な検出と安全境界を重視し、ゾーンルールや産業プロトコルの確認、IEC 62443およびNIST SP 800-82に沿った報告を扱います。
主な機能
- 01安全なスコープと境界の設定
- 02受動的なネットワーク検出と資産一覧
- 03Purdueゾーン間のファイアウォールルール分析
- 04産業プロトコルの安全性確認
- 05規格に沿った評価レポート作成
活用シーン
- 01OT/ICS環境のセキュリティ基準策定
- 02IT/OT統合後のネットワーク評価
- 03IEC 62443またはNERC CIP監査の準備
- 04OTネットワークの不審な接続経路の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill performing-ot-network-security-assessmentインストール後、Claude Code で/performing-ot-network-security-assessmentを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEassets/template.mdreferences/api-reference.mdstandards.mdworkflows.mdscripts/agent.pyprocess.py詳細
OT ネットワークセキュリティ評価の実施
使用するタイミング
- 新しいクライアントの OT/ICS 環境について、初期のセキュリティベースラインを確立するとき
- IT/OT 統合の取り組みの後に、施設のセキュリティ態勢を評価するとき
- IEC 62443 または NERC CIP のコンプライアンス監査に備えるとき
- 産業施設を含む合併・買収の後にリスクを評価するとき
- OT ネットワークが侵害されていないか、または企業 IT へ監視されていない経路がないかを調査するとき
使用しないでください: OT コンポーネントを含まない IT のみのネットワーク評価、IT Web アプリケーションのアプリケーション層の脆弱性スキャン(performing-web-app-penetration-test を参照)、適切な許可と安全管理策がない稼働中の OT システムへの能動的な攻撃。
前提条件
- すべての評価作業について、資産所有者および運用管理者からの書面による許可
- Purdue 参照モデルと IEC 62443 のゾーン/コンduit アーキテクチャの理解
- パッシブネットワーク監視ツール(Nozomi Guardian、Dragos Platform、または産業用プロトコルのディセクタを備えた Wireshark)
- ネットワーク図、ファイアウォールのルールセット、資産インベントリへのアクセス(またはパッシブな検出を実行する能力)
- 評価対象の OT システムが制御する物理プロセスに関する安全ブリーフィング
ワークフロー
ステップ 1: 評価の範囲と安全境界を定義する
Purdue 参照モデルのレベルに基づいて範囲を定義し、能動的にスキャンしてはならない安全重要システムを特定します。OT の評価は IT の評価と根本的に異なります。能動的なスキャンによって PLC がクラッシュしたり、安全計装システム(SIS)が妨害されたり、物理的な損害が生じたりする可能性があるためです。
# OT Assessment Scope Definition
assessment:
facility: "Chemical Processing Plant - Site Alpha"
purdue_levels_in_scope:
- level_0: "Physical process sensors and actuators (passive observation only)"
- level_1: "PLCs, RTUs, safety controllers (passive only, no active scanning)"
- level_2: "HMI stations, engineering workstations, historian (limited active with approval)"
- level_3: "Site operations - OPC servers, application servers (active scanning permitted)"
- level_3_5: "DMZ - data diodes, jump servers (active scanning permitted)"
- level_4: "Enterprise IT connecting to OT (active scanning permitted)"
safety_exclusions:
- "Safety Instrumented Systems (SIS) - Triconex controllers"
- "Emergency Shutdown (ESD) systems"
- "Fire and Gas detection systems"
- "Any Level 0/1 device during active production"
authorized_activities:
passive:
- "Network traffic capture and analysis via SPAN ports"
- "Industrial protocol deep packet inspection"
- "Wireless spectrum analysis"
- "Physical walkthrough and visual inspection"
active_with_approval:
- "Targeted Nmap scans of Level 2-4 systems during maintenance windows"
- "Authentication testing on HMI and engineering workstations"
- "Firewall rule verification between zones"
prohibited:
- "Active scanning of PLCs, RTUs, or SIS controllers"
- "Fuzzing industrial protocols on live systems"
- "Modifying PLC logic or firmware"
ステップ 2: パッシブなネットワーク検出と資産インベントリを実施する
運用を妨げる可能性のあるトラフィックを一切送信せずに、OT ネットワーク上のすべてのデバイス、通信フロー、プロトコルを把握するため、パッシブ監視を展開します。
#!/usr/bin/env python3
"""OT Network Passive Discovery and Asset Inventory Builder.
Uses pcap captures from SPAN ports to identify OT assets, protocols,
and communication patterns without active scanning.
"""
import json
import sys
from collections import defaultdict
from datetime import datetime
try:
from scapy.all import rdpcap, IP, TCP, UDP
from scapy.contrib.modbus import ModbusADURequest, ModbusADUResponse
except ImportError:
print("Install scapy: pip install scapy")
sys.exit(1)
# Industrial protocol port mappings
OT_PROTOCOL_PORTS = {
502: "Modbus/TCP",
102: "S7comm (Siemens)",
44818: "EtherNet/IP (CIP)",
2222: "EtherNet/IP (implicit)",
4840: "OPC UA",
20000: "DNP3",
47808: "BACnet",
1911: "Niagara Fox",
789: "Crimson v3 (Red Lion)",
2404: "IEC 60870-5-104",
18245: "GE SRTP",
5094: "HART-IP",
}
PURDUE_LEVEL_RANGES = {
"Level 0-1 (Field Devices)": ["10.10.0.0/16", "192.168.10.0/24"],
"Level 2 (Control Systems)": ["10.20.0.0/16", "192.168.20.0/24"],
"Level 3 (Site Operations)": ["10.30.0.0/16", "192.168.30.0/24"],
"Level 3.5 (DMZ)": ["172.16.0.0/16"],
"Level 4 (Enterprise)": ["10.0.0.0/16"],
}
def classify_purdue_level(ip_addr):
"""Classify an IP address to its Purdue Reference Model level."""
from ipaddress import ip_address, ip_network
addr = ip_address(ip_addr)
for level, subnets in PURDUE_LEVEL_RANGES.items():
for subnet in subnets:
if addr in ip_network(subnet):
return level
return "Unknown"
def analyze_ot_pcap(pcap_file):
"""Analyze pcap file to discover OT assets and communication patterns."""
packets = rdpcap(pcap_file)
assets = {}
connections = defaultdict(lambda: {"count": 0, "protocols": set(), "ports": set()})
protocol_stats = defaultdict(int)
cross_zone_flows = []
for pkt in packets:
if not pkt.haslayer(IP):
continue
src_ip = pkt[IP].src
dst_ip = pkt[IP].dst
# Track assets
for ip in (src_ip, dst_ip):
if ip not in assets:
assets[ip] = {
"ip": ip,
"purdue_level": classify_purdue_level(ip),
"protocols_observed": set(),
"roles": set(),
"first_seen": str(pkt.time),
"last_seen": str(pkt.time),
"mac": None,
}
assets[ip]["last_seen"] = str(pkt.time)
# Identify OT protocols by port
dst_port = None
if pkt.haslayer(TCP):
dst_port = pkt[TCP].dport
elif pkt.haslayer(UDP):
dst_port = pkt[UDP].dport
if dst_port and dst_port in OT_PROTOCOL_PORTS:
protocol_name = OT_PROTOCOL_PORTS[dst_port]
protocol_stats[protocol_name] += 1
assets[src_ip]["protocols_observed"].add(protocol_name)
assets[dst_ip]["protocols_observed"].add(protocol_name)
# Determine roles
assets[src_ip]["roles"].add("client/master")
assets[dst_ip]["roles"].add("server/slave")
# Track connections
conn_key = (src_ip, dst_ip)
connections[conn_key]["count"] += 1
if dst_port:
connections[conn_key]["ports"].add(dst_port)
if dst_port in OT_PROTOCOL_PORTS:
connections[conn_key]["protocols"].add(OT_PROTOCOL_PORTS[dst_port])
# Detect cross-zone communication
src_level = classify_purdue_level(src_ip)
dst_level = classify_purdue_level(dst_ip)
if src_level != dst_level and src_level != "Unknown" and dst_level != "Unknown":
cross_zone_flows.append({
"src": src_ip,
"src_level": src_level,
"dst": dst_ip,
"dst_level": dst_level,
"protocol": OT_PROTOCOL_PORTS.get(dst_port, f"port/{dst_port}"),
})
return {
"asset_count": len(assets),
"assets": {ip: {k: list(v) if isinstance(v, set) else v for k, v in info.items()} for ip, info in assets.items()},
"protocol_distribution": dict(protocol_stats),
"total_connections": len(connections),
"cross_zone_flows": cross_zone_flows[:50],
}
def generate_assessment_report(results):
"""Generate the OT network assessment findings report."""
report = []
report.append("=" * 70)
report.append("OT NETWORK PASSIVE DISCOVERY REPORT")
report.append(f"Generated: {datetime.now().isoformat()}")
report.append("=" * 70)
report.append(f"\nTotal Assets Discovered: {results['asset_count']}")
report.append(f"Total Unique Connections: {results['total_connections']}")
report.append("\n--- INDUSTRIAL PROTOCOL DISTRIBUTION ---")
for proto, count in sorted(results["protocol_distribution"].items(), key=lambda x: -x[1]):
report.append(f" {proto}: {count} packets")
report.append("\n--- CROSS-ZONE COMMUNICATION FLOWS ---")
if results["cross_zone_flows"]:
for flow in results["cross_zone_flows"][:20]:
report.append(
f" {flow['src']} ({flow['src_level']}) -> "
f"{flow['dst']} ({flow['dst_level']}) via {flow['protocol']}"
)
else:
report.append(" No cross-zone flows detected (check subnet classifications)")
report.append("\n--- FINDINGS ---")
# Check for Level 4 to Level 0-1 direct connections (critical finding)
for flow in results["cross_zone_flows"]:
if "Level 4" in flow["src_level"] and "Level 0-1" in flow["dst_level"]:
report.append(
f" [CRITICAL] Direct Enterprise-to-Field traffic: "
f"{flow['src']} -> {flow['dst']} via {flow['protocol']}"
)
elif "Level 4" in flow["src_level"] and "Level 2" in flow["dst_level"]:
report.append(
f" [HIGH] Enterprise-to-Control traffic bypassing DMZ: "
f"{flow['src']} -> {flow['dst']} via {flow['protocol']}"
)
return "\n".join(report)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python ot_network_discovery.py <pcap_file>")
sys.exit(1)
results = analyze_ot_pcap(sys.argv[1])
print(generate_assessment_report(results))
# Save detailed JSON
output_file = sys.argv[1].replace(".pcap", "_inventory.json")
with open(output_file, "w") as f:
json.dump(results, f, indent=2, default=str)
print(f"\nDetailed inventory saved to: {output_file}")
ステップ 3: Purdue ゾーン間のファイアウォールルールを評価する
OT ゾーン間のファイアウォール設定を分析し、過度に許容的なルール、デフォルト拒否の欠如、IEC 62443 のゾーンモデルに違反する未承認のコンduit を特定します。
#!/usr/bin/env python3
"""OT Zone Firewall Rule Analyzer.
Parses firewall rule exports (CSV format) and evaluates them against
IEC 62443 zone/conduit model requirements.
"""
import csv
import json
import sys
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class FirewallRule:
rule_id: str
source_zone: str
source_ip: str
dest_zone: str
dest_ip: str
service: str
port: str
action: str
enabled: bool
comment: str = ""
# IEC 62443 zone communication policy
# Defines which zone pairs are allowed to communicate and through what conduit
ALLOWED_CONDUITS = {
("Level 4", "Level 3.5"): {
"allowed_ports": [443, 3389, 22],
"description": "Enterprise to DMZ - web services, jump hosts",
"requires_inspection": True,
},
("Level 3.5", "Level 3"): {
"allowed_ports": [443, 1433, 5432, 8080],
"description": "DMZ to Site Ops - historian mirror, OPC relay",
"requires_inspection": True,
},
("Level 3", "Level 2"): {
"allowed_ports": [502, 44818, 4840, 102],
"description": "Site Ops to Control - OPC UA, Modbus, S7comm",
"requires_inspection": True,
},
("Level 2", "Level 1"): {
"allowed_ports": [502, 44818, 102, 2222],
"description": "Control to Field - direct industrial protocols",
"requires_inspection": False,
},
}
# Prohibited direct connections
PROHIBITED_CONDUITS = [
("Level 4", "Level 3"),
("Level 4", "Level 2"),
("Level 4", "Level 1"),
("Level 4", "Level 0"),
("Level 3", "Level 1"),
("Level 3", "Level 0"),
("Internet", "Level 3.5"),
("Internet", "Level 3"),
("Internet", "Level 2"),
("Internet", "Level 1"),
]
def parse_firewall_rules(csv_file):
"""Parse firewall rules from CSV export."""
rules = []
with open(csv_file, "r") as f:
reader = csv.DictReader(f)
for row in reader:
rules.append(FirewallRule(
rule_id=row.get("rule_id", ""),
source_zone=row.get("source_zone", ""),
source_ip=row.get("source_ip", ""),
dest_zone=row.get("dest_zone", ""),
dest_ip=row.get("dest_ip", ""),
service=row.get("service", ""),
port=row.get("port", ""),
action=row.get("action", ""),
enabled=row.get("enabled", "true").lower() == "true",
comment=row.get("comment", ""),
))
return rules
def analyze_rules(rules):
"""Analyze firewall rules against IEC 62443 zone model."""
findings = {"critical": [], "high": [], "medium": [], "low": [], "info": []}
for rule in rules:
if not rule.enabled or rule.action.lower() != "allow":
continue
zone_pair = (rule.source_zone, rule.dest_zone)
port = int(rule.port) if rule.port.isdigit() else 0
# Check for prohibited conduits
if zone_pair in PROHIBITED_CONDUITS:
findings["critical"].append({
"rule_id": rule.rule_id,
"finding": f"Prohibited direct connection: {rule.source_zone} -> {rule.dest_zone}",
"detail": f"Rule allows {rule.source_ip} to reach {rule.dest_ip}:{rule.port} ({rule.service})",
"remediation": "Remove rule. Route traffic through DMZ (Level 3.5) with application-layer inspection.",
})
# Check for overly broad rules (any/any)
elif rule.source_ip in ("any", "0.0.0.0/0") or rule.dest_ip in ("any", "0.0.0.0/0"):
findings["high"].append({
"rule_id": rule.rule_id,
"finding": f"Overly permissive rule with 'any' address",
"detail": f"{rule.source_ip} -> {rule.dest_ip}:{rule.port} in {zone_pair}",
"remediation": "Restrict to specific host IPs per IEC 62443 least-privilege conduit policy.",
})
# Check allowed conduits for port violations
elif zone_pair in ALLOWED_CONDUITS:
conduit = ALLOWED_CONDUITS[zone_pair]
if port and port not in conduit["allowed_ports"]:
findings["medium"].append({
"rule_id": rule.rule_id,
"finding": f"Unauthorized port in conduit {zone_pair}",
"detail": f"Port {port} ({rule.service}) not in allowed list {conduit['allowed_ports']}",
"remediation": f"Remove port {port} from conduit or justify in risk assessment.",
})
return findings
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python ot_firewall_analyzer.py <rules.csv>")
sys.exit(1)
rules = parse_firewall_rules(sys.argv[1])
findings = analyze_rules(rules)
print("=" * 70)
print("OT ZONE FIREWALL RULE ANALYSIS")
print("=" * 70)
for severity in ["critical", "high", "medium", "low"]:
if findings[severity]:
print(f"\n--- {severity.upper()} FINDINGS ({len(findings[severity])}) ---")
for f in findings[severity]:
print(f" [{f['rule_id']}] {f['finding']}")
print(f" Detail: {f['detail']}")
print(f" Fix: {f['remediation']}")
ステップ 4: 産業用プロトコルのセキュリティを評価する
使用中の産業用プロトコルのセキュリティ設定を評価し、認証、暗号化、アクセス制御を確認します。
# Capture Modbus/TCP traffic for analysis
tcpdump -i eth0 -w ot_capture.pcap 'port 502 or port 44818 or port 4840 or port 102 or port 20000' -c 100000
# Use Wireshark with OT protocol dissectors for deep inspection
tshark -r ot_capture.pcap -Y "modbus" -T fields \
-e ip.src -e ip.dst -e modbus.func_code -e modbus.reference_num \
-e modbus.word_cnt > modbus_analysis.csv
# Check for unauthenticated Modbus write operations (function codes 5,6,15,16)
tshark -r ot_capture.pcap -Y "modbus.func_code >= 5 && modbus.func_code <= 16" \
-T fields -e ip.src -e ip.dst -e modbus.func_code -e frame.time
# Scan for OPC UA servers and check security policies
# Only run against Level 3+ systems with explicit authorization
python3 -c "
from opcua import Client
server_url = 'opc.tcp://10.30.1.50:4840'
client = Client(server_url)
endpoints = client.connect_and_get_server_endpoints()
for ep in endpoints:
print(f'Endpoint: {ep.EndpointUrl}')
print(f' Security Mode: {ep.SecurityMode}')
print(f' Security Policy: {ep.SecurityPolicyUri}')
print(f' Auth Tokens: {[t.TokenType for t in ep.UserIdentityTokens]}')
"
ステップ 5: 評価レポートを作成する
検出事項を、IEC 62443 および NIST SP 800-82 Rev.3 に沿った構造化されたレポートにまとめます。
OT Network Security Assessment Report
=======================================
Facility: Chemical Processing Plant - Site Alpha
Assessment Date: 2026-02-23
Standard: IEC 62443-3-3 / NIST SP 800-82r3
Assessor: [Assessor Name]
EXECUTIVE SUMMARY:
The OT network assessment identified 47 assets across Purdue levels 0-4.
12 critical and 23 high-severity findings were identified, primarily
related to insufficient network segmentation, unauthenticated industrial
protocols, and unauthorized cross-zone communication paths.
ASSET INVENTORY SUMMARY:
Level 0-1 (Field): 18 devices (PLCs, RTUs, I/O modules)
Level 2 (Control): 9 devices (HMIs, engineering workstations)
Level 3 (Operations): 12 devices (historians, OPC servers, app servers)
Level 3.5 (DMZ): 3 devices (data diode, jump server, patch server)
Level 4 (Enterprise): 5 devices (domain controllers, file servers)
CRITICAL FINDINGS:
[OT-001] Direct Enterprise-to-PLC communication detected
Source: 10.0.5.22 (Level 4 - IT workstation)
Dest: 10.10.1.15 (Level 1 - Allen-Bradley PLC)
Protocol: EtherNet/IP (port 44818)
Impact: An attacker on the corporate network could directly modify PLC logic
Remediation: Block direct L4-L1 traffic; route through DMZ proxy
[OT-002] Modbus/TCP write commands without authentication
Affected: 8 PLCs accepting unauthenticated FC6 (Write Single Register)
Impact: Any device on the OT network can modify process setpoints
Remediation: Deploy Modbus-aware firewall; restrict write-capable sources
[OT-003] Flat network - no segmentation between Purdue levels
Detail: All OT devices share VLAN 100 (10.10.0.0/16)
Impact: Compromised HMI has direct access to all PLCs and SIS
Remediation: Implement zone-based segmentation per IEC 62443-3-2
RISK MATRIX:
Critical: 12 findings (immediate remediation required)
High: 23 findings (remediate within 30 days)
Medium: 15 findings (remediate within 90 days)
Low: 8 findings (remediate in next maintenance cycle)
主要な概念
| 用語 | 定義 |
|---|---|
| Purdue 参照モデル | 産業制御システムを整理するための階層型アーキテクチャモデル(レベル 0〜5)。物理プロセスからエンタープライズ IT までのセキュリティゾーンを定義する |
| IEC 62443 | 産業オートメーションおよび制御システム(IACS)のセキュリティに関する国際規格シリーズ。セキュリティレベル、ゾーン、コンduit、セキュリティ要件を定義する |
| ゾーン | 共通のセキュリティ要件を共有する論理的または物理的な資産のグループ。IEC 62443-3-2 で定義される |
| コンduit | 2 つ以上のゾーンを接続する通信チャネルの論理的なグループ。共通のセキュリティポリシーの対象となる |
| SCADA | Supervisory Control and Data Acquisition。産業プロセスの高レベルな監視管理のためのシステムアーキテクチャ |
| DCS | Distributed Control System。制御要素がシステム全体に分散配置される制御システムアーキテクチャ |
| エアギャップ | OT ネットワークを IT/インターネットから物理的に分離すること。ファイアウォールとデータダイオードを備えた管理されたコンduit に置き換えられつつある |
| 安全計装システム(SIS) | 危険な状態が検出された際に、プロセスを安全な状態に移行させるよう設計された独立したシステム |
ツールとシステム
- Nozomi Networks Guardian: 産業環境向けに、資産検出、脆弱性評価、異常検知を提供するパッシブ OT ネットワーク監視プラットフォーム
- Dragos Platform: 重要インフラ向けに設計された、資産の可視化、脅威検出、脆弱性管理を備えた OT サイバーセキュリティプラットフォーム
- Claroty xDome: OT、IoT、IIoT 全体にわたる包括的な資産インベントリとリスクスコアリングを提供するサイバーフィジカルシステム保護プラットフォーム
- Wireshark/tshark: Modbus、DNP3、S7comm、EtherNet/IP、OPC UA、BACnet 向けの産業用プロトコルディゼクターを備えたネットワークプロトコルアナライザー
- Nmap with OT scripts: OT プロトコルの列挙を行う NSE スクリプトを備えたネットワークスキャナー(レベル 2 以上で、承認を得た場合にのみ使用してください)
- Grassmarlin: SCADA/ICS ネットワークのトポロジーを特定するための、NSA が開発したパッシブ OT ネットワークマッピングツール
一般的なシナリオ
シナリオ: セグメンテーションのないフラットな OT ネットワーク
背景: ある水道事業者では、すべての OT 機器が単一の VLAN に接続されています。パッシブネットワーク監視により、HMI、PLC、ヒストリアン、ドメインコントローラーがすべて同じレイヤー 2 ブロードキャストドメインを共有していることが判明しました。企業ネットワークと OT 環境の間には DMZ が存在しません。
アプローチ:
- SPAN ポートにパッシブ監視を導入し、2〜4 週間かけて完全な通信ベースラインを取得します
- すべての機器間の通信フローを、プロトコルとデータ量とともにマッピングします
- 機能と通信パターンに基づいて、資産を Purdue レベルに分類します
- IEC 62443-3-2 に従い、VLAN とゾーン間ファイアウォールによるゾーンアーキテクチャを設計します
- 影響が最も大きいセグメンテーションとして、レベル 3 とレベル 4 の間の DMZ 作成を優先します
- 本番環境への影響を避ける移行フェーズを含め、セグメンテーション計画を提示します
注意点: 本番稼働中に PLC へアクティブスキャンを実行すると、通信タイムアウトや工程の中断を引き起こす可能性があります。完全なトラフィックベースラインがないままセグメンテーションを実施すると、正当な制御システムの通信が遮断されてしまいます。ネットワーク層のファイアウォールだけに頼り、産業用プロトコルの検査を行わない場合、Modbus/TCP や EtherNet/IP の書き込みコマンドが検査されないまま残ります。
出力形式
OT Network Security Assessment Report
=======================================
Facility: [Facility Name]
Assessment Date: YYYY-MM-DD
Standard: IEC 62443-3-3 / NIST SP 800-82r3
EXECUTIVE SUMMARY:
[2-3 sentence overview of findings and risk level]
ASSET INVENTORY:
Level 0-1: [count] field devices
Level 2: [count] control systems
Level 3: [count] operations systems
Level 3.5: [count] DMZ systems
Level 4: [count] enterprise systems
FINDINGS BY SEVERITY:
Critical: [count] (immediate action required)
High: [count] (30-day remediation)
Medium: [count] (90-day remediation)
Low: [count] (next maintenance window)
DETAILED FINDINGS:
[OT-NNN] Finding Title
Severity: Critical|High|Medium|Low
Affected Assets: [list]
IEC 62443 Reference: [section]
NIST 800-82r3 Reference: [section]
Description: [technical detail]
Impact: [operational and safety impact]
Remediation: [specific technical remediation steps]