1. 工业IoT设备资产发现与指纹识别

工业IoT设备资产发现与指纹识别 学习卡片

为什么资产发现要“被动+主动”两条腿走路

工厂网络里大量设备是“哑设备”:PLC、变频器、远程IO、HMI、网关,它们不会主动上报自己是谁。被动流量分析能回答“谁在跟谁说话、用什么协议、暴露了哪些端口”,但拿不到厂商和型号;主动探测能补上指纹细节,却可能打扰生产设备。因此本例采用先被动摸清拓扑与协议分布,再对关键设备做低强度主动指纹的组合策略,最终输出可核对的资产清单。

被动侧:从镜像流量里提取“设备—协议—端口”三元组

在交换机镜像口或TAP上抓包,过滤工业协议特征端口与报文特征。下面脚本用 Scapy 读取 pcap,按源IP聚合出协议、端口和报文计数,作为资产发现的被动输入。

from scapy.all import rdpcap, IP, TCP, UDP
from collections import defaultdict

# 工业协议特征端口
PORT_PROTO = {
    502: "Modbus/TCP",
    4840: "OPC UA",
    1883: "MQTT",
    8883: "MQTT/TLS",
    102: "S7comm",
    44818: "EtherNet/IP",
    20000: "DNP3",
}

def passive_discover(pcap_path):
    assets = defaultdict(lambda: {"proto": set(), "ports": set(), "pkts": 0})
    for pkt in rdpcap(pcap_path):
        if IP not in pkt:
            continue
        src = pkt[IP].src
        if TCP in pkt:
            dport, sport = pkt[TCP].dport, pkt[TCP].sport
        elif UDP in pkt:
            dport, sport = pkt[UDP].dport, pkt[UDP].sport
        else:
            continue
        for port in (dport, sport):
            if port in PORT_PROTO:
                assets[src]["proto"].add(PORT_PROTO[port])
                assets[src]["ports"].add(port)
        assets[src]["pkts"] += 1
    return assets

for ip, info in passive_discover("factory.pcap").items():
    print(ip, sorted(info["proto"]), sorted(info["ports"]), info["pkts"])

运行后得到类似 192.168.10.21 ['Modbus/TCP'] [502] 1842 的记录,说明该IP在502端口上持续通信,是Modbus从站候选。被动结果只给出“协议+端口+活跃度”,厂商和型号仍未知。

主动侧:低强度指纹探测补全厂商与型号

对被动筛出的候选设备,按协议做最小化探测:Modbus 读设备标识对象(0x2B/0x0E),OPC UA 读 ServerStatus 与 BuildInfo,MQTT 观察 CONNACK 返回码与 broker 版本。下面用 pymodbus 读取 Modbus 设备标识,这是最安全的只读操作。

from pymodbus.client import ModbusTcpClient

def modbus_fingerprint(ip, port=502, unit=1):
    client = ModbusTcpClient(ip, port=port, timeout=2)
    if not client.connect():
        return {"ip": ip, "error": "connect_failed"}
    try:
        # 0x2B/0x0E 读设备标识,只读,不改变设备状态
        rr = client.read_device_information(unit=unit)
        if rr.isError():
            return {"ip": ip, "error": str(rr)}
        return {
            "ip": ip,
            "vendor": rr.information.get(0x00, b"").decode(errors="ignore"),
            "product": rr.information.get(0x01, b"").decode(errors="ignore"),
            "version": rr.information.get(0x02, b"").decode(errors="ignore"),
        }
    finally:
        client.close()

print(modbus_fingerprint("192.168.10.21"))

若设备支持标识对象,会返回厂商名、产品代码和版本;若不支持,则回退到 TCP 栈指纹(TTL、窗口大小、TCP选项顺序)和 HTTP/SNMP 横幅做辅助判断。主动探测必须限制并发与频率,避免对PLC造成连接风暴。

资产清单合并与去重

被动结果和主动结果按IP合并,冲突时以主动指纹为准,被动数据作为佐证。下表是合并后的资产清单示例(教学示例数据):

IP端口协议厂商型号/版本发现方式置信度
192.168.10.21502Modbus/TCPSiemensS7-1200 FW4.2被动+主动高
192.168.10.354840OPC UAKepwareKEPServerEX 6.5被动+主动高
192.168.10.401883MQTT未知Mosquitto 2.x被动+横幅中
192.168.10.55502Modbus/TCP未知未知仅被动低
置信度规则:被动+主动且标识对象返回完整=高;仅被动或仅横幅=中;只有端口命中无任何指纹=低。低置信度设备应列入下一轮重点探测名单。

探测流程与结果判读

工业IoT资产发现:被动流量 + 主动指纹 数据流 被动流量分析 交换机镜像口 / TAP 过滤 502/4840/1883/102 按源IP聚合协议+端口 输出:IP / 协议 / 端口 / 活跃度 不打扰设备,覆盖全量通信 主动指纹探测 Modbus 0x2B/0x0E 读标识 OPC UA 读 BuildInfo MQTT CONNACK / 横幅 输出:厂商 / 型号 / 版本 只读操作,限速限并发 候选设备列表 IP + 协议 + 端口 按活跃度排序,优先探测 资产清单合并与去重 按IP合并,主动指纹优先 被动数据作为佐证与置信度依据 输出资产清单 IP / 端口 / 协议 / 厂商 / 型号 / 置信度 置信度判读 高=被动+主动完整指纹 低=仅端口命中,需复测

结果验证与常见问题

  • 验证方法:把清单里的IP与交换机ARP表、DHCP租约、DNS记录交叉比对,确认没有遗漏网段;对高置信度设备抽查其Web管理页或铭牌,核对厂商型号是否一致。
  • 主动探测无响应:设备可能禁用了标识对象或只允许特定主站访问。此时不要反复重试,改用被动流量中的功能码分布和TCP栈指纹推断。
  • MQTT设备识别难:broker可能不返回版本。可观察CONNACK返回码、主题命名风格(如 factory/line1/plc21)和客户端ID格式辅助判断。
  • 安全边界:主动探测只做只读操作,禁止写线圈、改寄存器、重启设备;探测频率建议单设备不超过1次/秒,并发不超过5。
资产清单是后续所有安全工作的基线。清单不完整,后面的漏洞验证、访问控制检查都会漏掉设备。建议每季度重新跑一次被动发现,与上一版清单做差异对比。