はじめに:なぜ、いま「DNP3」とインフラセキュリティなのか
おい、最近のWeb系やクラウドのトレンドばかり追っている若いエンジニアたち、ちょっと耳を貸してくれ。君たちが普段何気なく叩いている fetch() や REST API の向こう側、あるいはもっと深い物理世界——そう、私たちが暮らす都市の電力網や水道プラントを支える「OT(制御技術)ネットワーク」が、今この瞬間もサイバー犯罪グループの標的にされている。
オフィスやクラウドの世界では「ゼロトラスト」や「JWT認証」が当たり前になりつつあるが、重要インフラの現場に目を向けると、そこには昭和から平成初期の空気を色濃く残す、あまりにも無防備なプロトコルが息を潜めている。その代表格が、今回取り上げる DNP3(Distributed Network Protocol 3) だ。
ポート番号 20000。この数字を聞いて冷や汗が流れるようになれば、君も一人前のインフラ・セキュリティエンジニアだ。今回は、このDNP3が抱える構造的な脆弱性と、それがどのようにランサムウェアや国家を背景に持つサイバー兵器の侵入経路になり得るのか、現場のリアルな泥臭い知見を交えて徹底的に解説しよう。
—
1. DNP3プロトコルとは何か? なぜOTインフラで使われ続けるのか
DNP3は、電力会社や水道局などのSCADA(Supervisory Control and Data Acquisition)システムやRTU(Remote Terminal Unit)、IED(Intelligent Electronic Device)の間でデータをやり取りするために開発された、北米発祥の通信プロトコルだ。IEEE 1815としても標準化されている。
標準仕様と通信の特徴
- レイヤー構造: OSI参照モデルの「拡張EP団(Enhanced Performance Architecture)」として設計されており、大雑把に言えば、データリンク層(Layer 2)、トランスポート層(Layer 3相当の機能)、そしてアプリケーション層(Layer 7)がコンパクトに統合されている。
- TCP/IPへの対応: 本来はシリアル通信(RS-232/RS-485)を主眼に置いていたが、現代ではTCPのポート
20000を用いてイーサネット上でカプセル化されて動作することが多い。
なぜこれが狙われるのか?(致命的な設計思想)
Web APIやモダンなマイクロサービス設計に慣れた感覚からすると、DNP3の仕様は悪夢のように映るはずだ。
1. 暗号化の欠如: 標準のDNP3には、機密性を保つための暗号化機構(TLSなど)がデフォルトで含まれていない。パケットはすべて平文(クリアテキスト)で流れる。
2. 認証の欠如: 「誰がコマンドを送ってきたか」を検証する仕組みが長年存在しなかった(のちに追加された「Secure Authentication (SA)」も、古いファームウェアのまま放置されている現場が山ほどある)。
つまり、一度ネットワークの境界(ペリメータ)が破られ、内部のOTセグメントに足場を築かれてしまえば、攻撃者は「送電網の遮断機を開け」「水処理施設の薬品注入バルブを全開にしろ」という命令を、誰の目にも止まらず、誰の認証も経ずに自由に流し込むことができるのだ。
—
2. DNP3通信の内部構造とパケットの泥臭い現実
百聞は一見に如かず。実際にDNP3のアプリケーションレイヤーがどのような構造を持ち、攻撃者がどこを突くのかをコードレベルで見ていこう。
DNP3ヘッダーの基本構造(Pythonによる解析イメージ)
DNP3のフレームは、マジックバイトから始まる。Pythonの struct モジュールを使って、そのパケットの断片を読み解くスクリプトを書いてみた。実務でパケットキャプチャ(PCAP)を解析する際のイメージを持ってほしい。
import struct
def parse_dnp3_header(packet_bytes):
"""
DNP3のデータリンク層ヘッダー(10バイト)をパースするサンプル
"""
if len(packet_bytes) < 10:
raise ValueError("パケット長が短すぎます")
# マジックバイト(固定で 0x05, 0x64)の検証
start_bytes = struct.unpack("!H", packet_bytes[0:2])[0]
if start_bytes != 0x0564:
raise ValueError("有効なDNP3パケットではありません(マジックバイト不一致)")
length = packet_bytes[2] # ヘッダーを除いた後続の長さ
control = packet_bytes[3] # 制御オクテット(DIR, PRM, FCB, FCV, 伝送制御など)
dest_addr = struct.unpack("!H", packet_bytes[4:6])[0] # 宛先アドレス (Destination)
src_addr = struct.unpack("!H", packet_bytes[6:8])[0] # 送信元アドレス (Source)
crc = struct.unpack("!H", packet_bytes[8:10])[0] # ヘッダーCRC
print(f"[*] DNP3パケット検知:")
print(f" - 宛先アドレス: {dest_addr}")
print(f" - 送信元アドレス: {src_addr}")
print(f" - 制御バイト: 0x{control:02X}")
print(f" - ヘッダーCRC: 0x{crc:04X}")
# ダミーのDNP3データリンク層ヘッダー(例)
# 0x0564 (マジック), 0x05 (長さ), 0xC4 (制御), 0x0001 (宛先), 0x0002 (送信元), 0x3601 (CRC)
sample_packet = b'\x05\x64\x05\xC4\x00\x01\x00\x02\x36\x01'
parse_dnp3_header(sample_packet)
このコードが示す通り、アドレスや制御フラグはすべて剥き出しだ。ここに「偽装制御メッセージ」を挿入するセッションハイジャックや、ARPスプーフィング等を用いた中間者攻撃(MitM)を仕掛けるのは、熟練した攻撃者にとって朝飯前なのだ。
—
3. ランサムウェアがDNP3を悪用するシナリオ
昨今のランサムウェアは、単に企業のファイルサーバーを暗号化して「身代金を払え」と脅すだけではない。インフラ企業を標的にした場合、彼らは「物理的な破壊」をチラつかせて心理的圧力をかける(Double/Triple Extortion)。
感染から破壊までのキルチェーン(Kill Chain)
1. 初期侵入 (Initial Access):
情シス部門の甘いリモートデスクトップ(RDP)設定や、社員が踏んだフィッシングメールを起点に、ITネットワーク側へマルウェア(例: *BlackEnergy* や *Industroyer* の系譜)が侵入する。
2. ラテラルムーブメント (Lateral Movement):
ITとOTを繋ぐ「ピボットサーバー(踏み台)」や、メンテナンス用の不安全なファイアウォール設定の隙を突き、OTセグメントへと潜り込む。
3. DNP3スキャンとデバイス列挙:
攻撃者は内部から 20000/TCP に対してポートスキャンを行い、応答するRTUやIEDのIPアドレスとユニットアドレスをマッピングする。
4. セッションハイジャックと偽装制御:
正当なマスター局(SCADA)になりすますか、あるいは既存の通信に割り込んで、不正なDNP3アプリケーション層コマンド(Function Code)を送信する。
ここで使われる代表的なファンクションコード(Function Code: FC)の悪用例を挙げておこう。
FC 13 (COLD_START): RTUを強制的に再起動させ、システムをブラックアウトさせる。FC 3 (READ)/FC 4 (READ_STATISTICS): プラントの状態を不正に窃取し、攻撃のタイミングを計る。FC 45 (DIRECT_OPERATE)やFC 46 (SELECT_AND_OPERATE): アクチュエータの動作、遮断器の開放といった物理的な破壊コマンドを直接流し込む。
—
4. 実務で講じるべきネットワークレベルの防御策
「じゃあ、古いプロトコルだから諦めてシステムを全部買い換えろと言うのか?」——そんな予算が即座につく現場なんて、日本中探してもほとんどないはずだ。
限られた予算とレガシーな制約の中で、ネットワークエンジニアが今すぐ実装すべき実戦的な防御策を提示しよう。
① DNP3 Secure Authentication (DNP3-SA / IEC 62351-5) の強制
前述の通り、標準DNP3の最大の弱点は「認証の欠如」だ。これを補うために、DNP3 Secure Authenticationを有効化する。
これは、マスター局とアウトステーション(RTU)の間で「チャレンジ・レスポンス方式」の暗号学的認証を行い、正規のセッションであることを証明する仕組みだ。もし古いファームウェアでこれがサポートされていない場合は、直ちにベンダーにパッチの適用を要求するか、次項のネットワーク分離を急ぐべきだ。
② 次世代ファイアウォール(NGFW)とIDPSによるDNP3ディープパケットインスペクション(DPI)
単なるポート番号(20000/TCP)の許可・不許可では不十分だ。パケットの中身を解釈できるDPI(Deep Packet Inspection)機能を備えたファイアウォールを配置し、以下のようなポリシーを厳格に適用する。
- ファンクションコードのホワイトリスト制御:
例えば、「このRTUに対しては、状態読み取り(FC 1やFC 2)のトラフィックは許可するが、直接操作(FC 45/46)は特定の保守用踏み台からのみ許可し、それ以外は即座にドロップする」といったきめ細かい制御を行う。
Snort等のIDPSルール設定例(参考)
不審なDNP3の直接操作コマンド(FC 45)を検知するためのシグネチャ例だ。現場のIDPSに組み込んでおくと夜もぐっすり眠れるようになる。
# 不正なDNP3 Direct Operate (Function Code 45) の外部からの流入を検知するルール
alert tcp any any -> any 20000 ( \
msg:"ET OT/SCADA DNP3 Direct Operate Command Detected from Unauthorized Source"; \
flow:established,to_server; \
content:"|05 64|"; depth:2; \
byte_test:1,شون,=,45,10; \ # 10バイト目付近のファンクションコードが45であることを検証(概念的記述)
classtype:bad-unknown; \
sid:9000001; rev:1; \
)
③ 厳格なゾーン分割(Purdueモデルの再徹底)
ANSI/ISA-95(Purdue Enterprise Reference Architecture)の原則に立ち返ろう。
- Level 3 (オペレーション・管理層) と Level 2 (コントロール層 / OTセグメント) の間には、安易なルーティングを絶対に許さない。
- メンテナンス端末を持ち込む際は、必ず踏み台サーバー(Bastian Host)を経由させ、多要素認証(MFA)とセッションの全記録(ロギング)を義務付ける。ITのネットワークとOTのネットワークを物理的、あるいは強力なステートフル・ファイアウォールで完全に隔離(エアギャップの精神)するのだ。
—
おわりに:レガシーと向き合うエンジニアの矜持
インフラの現場で働くエンジニアにとって、セキュリティ対策は「新しい技術を導入して終わり」ではない。動いているシステムを止めずに、泥臭く、しかし確実にリスクを削り取っていく地道な作業の連続だ。
DNP3(ポート20000)という、セキュリティの「セ」の字もなかった時代に作られたプロトコルが今も社会インフラを動かしているという事実。それをリスクとして正しく恐れ、適切なネットワークの境界防御と監視の目を光らせることこそが、私たちシニアエンジニア、そして次代を担う君たちの使命なのだ。
さあ、ログを開いて、自社のOTセグメントに流れるトラフィックを今一度確認してみようじゃないか。
コメント