十一、安全加固方案
11.1 安全威胁分析
| 威胁类型 | RTU | TCP | 风险等级 |
|---|---|---|---|
| 物理嗅探 | 需接触线缆 | 任意网络可达位置 | 高(TCP) |
| 伪造请求 | 较难(需串口接入) | 容易(网络发送) | 高(TCP) |
| 重放攻击 | 可能 | 可能 | 中 |
| 拒绝服务 | RS485总线占满 | TCP SYN洪水 | 中 |
| 中间人攻击 | 难 | 可能 | 高(TCP) |
| 未授权访问 | 物理隔离 | 需防火墙 | 中 |
11.2 RTU安全措施
-
物理隔离:限制对串口线缆的物理接触
-
专用网络:RS485不与外网连接
-
地址旋转:定期修改从站地址
-
校验强化:CRC足够,无需额外措施
11.3 TCP安全措施(多层级)
11.3.1 网络层隔离
bash
# 防火墙规则(仅允许特定IP)
sudo iptables -A INPUT -p tcp --dport 502 -s 192.168.1.0/24 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 502 -j DROP
# 使用VLAN隔离
# 创建工业控制VLAN ID 100
vconfig add eth0 100
ifconfig eth0.100 192.168.100.1 netmask 255.255.255.0 up
11.3.2 Modbus Secure (TLS)
标准:IEC 62443,使用端口 802/tcp
实现示例:
python
import ssl
import socket
# 生成自签名证书
# openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(certfile="server.crt", keyfile="server.key")
context.load_verify_locations(cafile="ca.crt")
# 服务器端
listen_sock = socket.socket()
listen_sock.bind(('0.0.0.0', 802))
listen_sock.listen(5)
ssl_sock = context.wrap_socket(listen_sock, server_side=True)
# 客户端
sock = socket.socket()
ssl_sock = context.wrap_socket(sock, server_hostname="modbus-server")
ssl_sock.connect(('192.168.1.100', 802))
11.3.3 VPN隧道
bash
# 使用WireGuard建立VPN
# 服务器端
wg genkey | tee server.key | wg pubkey > server.pub
# 配置/etc/wireguard/wg0.conf
# 客户端通过VPN访问Modbus
wg-quick up wg0
# Modbus请求通过VPN隧道发送
11.3.4 反向代理 + 认证
bash
# nginx作为Modbus TCP反向代理
stream {
upstream modbus_backend {
server 192.168.1.100:502;
}
server {
listen 502;
proxy_pass modbus_backend;
proxy_connect_timeout 1s;
# 简单IP白名单
allow 192.168.1.0/24;
deny all;
}
}
11.4 监控与入侵检测
python
# 简单IDS规则示例
def packet_inspection(pdu):
# 检测异常写入频率
if pdu[0] == 0x06: # 写单寄存器
write_count[pdu[1:3]] += 1
if write_count > 100: # 每秒超过100次
alert("Possible DoS attack")
# 检测非法功能码
if pdu[0] not in [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x0F, 0x10]:
alert(f"Suspicious function code: {hex(pdu[0])}")
# 检测越界访问
address = (pdu[1] << 8) | pdu[2]
if address > 0x1000: # 超出已知寄存器范围
alert(f"Out-of-bounds access: {hex(address)}")