工控 Modbus 协议安全加固实战:OT 网络防护配置

工控网络里的 Modbus 安全加固之所以棘手,根源在于协议本身没有设计认证与加密机制。Modbus TCP 只要能建立到 502 端口的连接,任何主机都可以读取甚至改写 PLC 的寄存器,协议层不会区分这是来自 HMI 的正常操作还是来自办公网的攻击者。因此防护思路必须从「给协议加密」转向「把协议关进受控的通信矩阵」,用网络分区、功能码白名单和流量检测三层手段把可利用面压到最小。

适用场景

  • 工业现场使用 Modbus TCP 协议,设备包含 PLC、HMI、SCADA 服务器与数据采集网关。
  • OT 网络与 IT 办公网之间存在直连或弱隔离,办公终端能直接访问 502 端口。
  • 设备型号老旧,固件不支持加密或访问控制,短期内无法更换。
  • 需要在不影响生产实时性的前提下,建立可审计的异常行为检测能力。

前置条件

  • 具备在交换机上配置镜像口(SPAN)或部署网络分光器(TAP)的权限,用于旁路流量采集。
  • 一台部署 Suricata 或 Wireshark 的检测主机,接在镜像口上,不串入生产链路。
  • 边界防火墙或工业防火墙支持按端口、源/目的地址做双向策略,若支持 Modbus 深度解析更佳。
  • 完整的资产清单与工艺通信关系,明确「哪台 HMI 允许写哪台 PLC」,这是编制通信矩阵的前提。

原理说明

理解 Modbus TCP 的报文结构是配置检测规则的基础。一次 Modbus TCP 请求由两部分组成:先是 7 字节的 MBAP 头,再是协议数据单元(PDU)。

  • 字节 0-1:事务标识符(Transaction ID),用于匹配请求与响应。
  • 字节 2-3:协议标识符,Modbus 固定为 0x0000。这个特征可用于在混杂流量中快速定位 Modbus 报文。
  • 字节 4-5:后续字节长度。
  • 字节 6:单元标识符(Unit ID),用于区分串行链路下的从站设备。
  • 字节 7:功能码(Function Code)。

功能码决定了操作性质。0x01/0x02/0x03/0x04 属于读取类,风险相对可控;0x05(写单个线圈)、0x06(写单个寄存器)、0x0F(写多个线圈)、0x10(写多个寄存器)属于写入类,直接改变设备运行状态;0x08(诊断)与 0x2B(读取设备标识)常用于侦察,攻击者用它们枚举设备型号与固件版本。检测规则的关键就是盯住功能码位于字节 7 这一固定偏移。

防护体系可以按 ISA-95 / Purdue 参考模型分层。现场层(Level 0-1)的 PLC 只与监控层(Level 2)的 HMI/SCADA 通信,办公网(Level 3-4)不得直连现场设备。工程实施上,防护落地在三处:网络分区切断不必要路径,通信矩阵把允许的流量收敛到白名单,旁路检测负责发现越界行为并告警。

操作步骤

1. 梳理资产与通信关系,生成通信矩阵

先在镜像口抓一段完整工艺周期的流量,再按源、目的与功能码聚合,得到事实上的通信关系表。

# 在镜像口抓取 Modbus 流量(建议覆盖至少一个完整生产班次)
sudo tcpdump -i eth1 -n 'tcp port 502' -c 200000 -w /tmp/modbus.pcap

# 按源地址-目的地址统计请求数量
tshark -r /tmp/modbus.pcap -Y 'modbus' -T fields \
  -e ip.src -e ip.dst | sort | uniq -c | sort -rn | head -30

# 统计每条通路上出现过的功能码,重点看是否有写入类操作
tshark -r /tmp/modbus.pcap -Y 'modbus.func_code' -T fields \
  -e ip.src -e ip.dst -e modbus.func_code | sort | uniq -c | sort -rn

# 提取读设备标识的侦察行为(功能码 0x2B)
tshark -r /tmp/modbus.pcap -Y 'modbus.func_code == 43' \
  -T fields -e ip.src -e ip.dst -e modbus.unit_id

上面第三条命令的输出就是通信矩阵的原始素材。把结果整理成「源设备 → 目的设备 → 允许的功能码集合」三列表格,作为后续防火墙与检测规则的唯一依据。任何不在矩阵内的通路都应视为异常。

2. 网络层:用 nftables 收敛到通信矩阵白名单

# 定义地址集合:HMI、SCADA、工程站、PLC
sudo nft add table inet otfilter
sudo nft add set inet otfilter hmi_hosts   { type ipv4_addr\; elements = { 10.10.20.11, 10.10.20.12 } }
sudo nft add set inet otfilter plc_hosts   { type ipv4_addr\; elements = { 10.10.30.10, 10.10.30.11 } }
sudo nft add set inet otfilter scada_hosts { type ipv4_addr\; elements = { 10.10.20.5 } }

sudo nft add chain inet otfilter forward { type filter hook forward priority 0 \; policy drop \; }

# 1) 只允许 HMI 与 SCADA 访问 PLC 的 502
sudo nft add rule inet otfilter forward ip saddr @hmi_hosts   ip daddr @plc_hosts tcp dport 502 ct state new,established accept
sudo nft add rule inet otfilter forward ip saddr @scada_hosts ip daddr @plc_hosts tcp dport 502 ct state new,established accept

# 2) PLC 的响应回程放行
sudo nft add rule inet otfilter forward ip saddr @plc_hosts ip daddr @hmi_hosts   ct state established accept
sudo nft add rule inet otfilter forward ip saddr @plc_hosts ip daddr @scada_hosts ct state established accept

# 3) 记录并丢弃其余到 502 的连接(含办公网直连尝试)
sudo nft add rule inet otfilter forward tcp dport 502 log prefix \"OT-DENY502 \" limit rate 10/minute drop

注意最后一条的 limit rate 10/minute:OT 设备一旦被扫描,日志会被瞬间写满,限速是保持日志可读的必要措施。

3. 应用层:Suricata 检测越界写入与侦察行为

Suricata 以旁路 IDS 模式部署,只做检测不做阻断,不会给生产链路引入延迟。以下规则基于功能码位于字节 7 这一固定偏移编写。

# /etc/suricata/rules/modbus-ot.rules
# 变量定义写在 suricata.yaml 中:OT_NET 与 IT_NET

# 1) 来自 IT 网段的写多寄存器操作(0x10 = 16)
alert tcp $IT_NET any -> $OT_NET 502 (
  msg:"OT-Modbus 办公网越界写入多个寄存器";
  flow:established,to_server;
  content:"|00 00|"; offset:2; depth:2;
  byte_test:1,=,16,7;
  classtype:attempted-admin; sid:9000010; rev:1;)

# 2) 来自 IT 网段的写单个线圈(0x05 = 5)
alert tcp $IT_NET any -> $OT_NET 502 (
  msg:"OT-Modbus 办公网越界写线圈";
  flow:established,to_server;
  content:"|00 00|"; offset:2; depth:2;
  byte_test:1,=,5,7;
  classtype:attempted-admin; sid:9000011; rev:1;)

# 3) 设备标识扫描(0x2B = 43),攻击者常用它做资产测绘
alert tcp $IT_NET any -> $OT_NET 502 (
  msg:"OT-Modbus 设备标识读取(资产测绘嫌疑)";
  flow:established,to_server;
  content:"|00 00|"; offset:2; depth:2;
  byte_test:1,=,43,7;
  classtype:network-scan; sid:9000012; rev:1;)

# 4) 诊断功能码滥用(0x08 = 8),可用于强制设备进入特定状态
alert tcp any any -> $OT_NET 502 (
  msg:"OT-Modbus 诊断功能码调用";
  flow:established,to_server;
  content:"|00 00|"; offset:2; depth:2;
  byte_test:1,=,8,7;
  classtype:policy-violation; sid:9000013; rev:1;)

byte_test:1,=,16,7 的含义是「取 1 字节、判断等于 16(十进制,即 0x10)、位于偏离包首偏移 7 的位置」,正好对应功能码字段。相比用 content 匹配,byte_test 不受事务标识符随机值影响,规则更稳定。

4. 设备层:开启 PLC 自带的写保护能力

主流品牌都提供访问级别或写保护开关,在条件允许时应一并启用,形成纵深。

  • 西门子 S7-1200/1500:在 TIA Portal 的「保护」设置中启用访问级别密码,并勾选「禁止通过 PUT/GET 通信进行访问」,阻断基于 S7comm 的无认证读写。
  • 施耐德 M 系列:在 Unity Pro 中配置应用密码并锁定启用了内存保护的代码块。
  • 通用做法:把 PLC 的 RUN/STOP 切换开关置于 RUN 并在物理上封闭,防止现场人员误切换造成停机。

5. 应用层:OPC UA 服务端加固

如果现场使用 OPC UA 网关做协议转换,需单独加固 OPC UA 服务端,因为 OPC UA 本身支持安全策略,配置不当等于白开。

# 以 open62541 或商用 OPC UA 服务端为例,安全策略配置要点:
# 1) 禁用 SecurityPolicy None,只保留 Basic256Sha256
# 2) 消息安全模式设为 SignAndEncrypt(仅签名不足以防篡改)
# 3) 关闭匿名登录(Anonymous),启用用户名口令或 X.509 证书认证
# 4) 启用信任列表(Trust List),未签入的客户端证书一律拒绝连接
# 5) 关闭端点发现服务,避免未认证客户端枚举全部端点

# 验证:用开源客户端尝试匿名连接,应被拒绝
# opcua-client --url opc.tcp://10.10.30.20:4840 --security None

配置验证

# 1. 从办公网段探测 502 端口,应超时或被拒绝
nmap -Pn -p 502 10.10.30.10

# 2. 从 HMI 侧探测,应显示 open
nmap -Pn -p 502 10.10.30.10

# 3. 用 Modbus 专用脚本识别设备信息(从允许网段执行)
nmap -Pn -p 502 --script modbus-discover 10.10.30.10

# 4. 确认 nftables 规则已生效且命中计数在增长
sudo nft list chain inet otfilter forward
sudo nft list ruleset | grep -A2 OT-DENY502

# 5. 确认 Suricata 规则加载成功并可在离线回放中命中
sudo suricata -T -c /etc/suricata/suricata.yaml -S /etc/suricata/rules/modbus-ot.rules
sudo suricata -r /tmp/modbus.pcap -S /etc/suricata/rules/modbus-ot.rules -l /tmp/ot-logs/
jq '.alert.signature' /tmp/ot-logs/eve.json | sort | uniq -c

# 6. 确认检测主机不占用生产带宽(镜像口统计)
sudo tcpdump -i eth1 -n 'tcp port 502' -c 50

第 1 条与第 2 条应当返回相反结果,这是分区策略是否真正落地的判据;第 5 条的回放验证尤为关键,先用历史流量确认规则能命中,再上线运行,可以避免规则写完却从未触发的情况。

常见问题

FAQ 1:现场设备太老,固件不支持加密和认证,是不是就没法防护了?

可以防护,但要把安全边界从设备层上移到网络层。老设备的现实是「协议无认证」这个既成事实无法改变,能做的是让不该到设备的流量根本到不了设备。具体三层:一是用工业防火墙或 nftables 把通信收敛到通信矩阵白名单,只放行必要的源目的对;二是对必须跨网段的数据采集场景,使用只允许单向传输的网关,从物理上杜绝反向写入;三是把检测部署在旁路,用功能码规则发现越界写入。这套方案的防护强度取决于白名单的严格程度,而不取决于设备是否支持加密。

FAQ 2:把 Modbus 换个端口,或者加个隧道,能不能提升安全性?

改端口只能挡住基于固定端口的批量扫描,对已经掌握目标 IP 的攻击者毫无作用,还会让资产管理变得混乱,不建议作为安全措施。加隧道要分情况:如果是用 VPN 或网关把 Modbus 流量封装后再传输,确实能提升跨网段通信的机密性与完整性,但要注意隧道端点本身成为新的单点,必须同时加固隧道设备的认证与访问控制。更重要的判断是,隧道解决的是「传输被窃听篡改」的问题,解决不了「谁能向 PLC 发写指令」的授权问题,后者只能靠通信矩阵白名单来解决。

FAQ 3:在生产网里部署 Suricata 会不会影响实时性,甚至引起停机?

关键在于部署模式。必须使用旁路 IDS 模式,即通过交换机镜像口或分光器复制流量,检测主机只收不发,不串接在转发路径上。这样即使检测主机宕机或规则异常,生产链路也不受任何影响,这是 OT 环境部署检测设备的第一原则。只有在完全确定设备与工艺能承受中断风险的场景下,才考虑 IPS 串联模式,并且必须先做离线回放与灰度验证。实测在千兆镜像口下,Suricata 处理 Modbus 流量的资源占用远低于处理通用 Web 流量,规则数量控制在百条以内时,一台 4 核 8 GB 的检测主机即可稳定支撑。

总结

Modbus 安全加固的本质是把「任何主机都能读写 PLC」收敛为「只有白名单内的设备能执行白名单内的操作」。落地优先级是:先做资产与通信关系梳理,没有通信矩阵,后面的规则都缺乏依据;再用网络分区把办公网与现场层彻底隔开,这一步能消除绝大多数风险;然后部署旁路功能码检测,把越界写入与资产测绘行为变成可见告警;最后在设备与网关侧开启写保护和 OPC UA 安全策略做纵深。整个过程必须遵守旁路原则与灰度验证原则,任何直接影响生产转发的改动都要先在非生产环境回放确认,这是 OT 安全与 IT 安全最大的区别。