ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

等保2.0下嵌入式工控设备安全整改:功能码白名单与自查脚本实践

等保2.0下嵌入式工控设备安全整改:功能码白名单与自查脚本实践 前两年做水务工控安全整改的时候我拿到测评机构的整改建议书扫了一遍后发现一个很有意思的规律被点名整改最多的并不是机房里嗡嗡作响的服务器也不是核心交换机而是一批平时根本没人搭理的嵌入式终端——远程测控终端RTU、智能电表采集器、还有几台老款PLC的通信模块。等保2.0的工控系统安全扩展要求里控制设备安全这一组控制点列得非常细而嵌入式产品往往正是整改资源最少、历史债务最重的部分所以一到测评就集体躺枪。这篇文章把我实际项目里沉淀下来的做法整理一遍先把等保2.0工控扩展要求里跟嵌入式设备相关的控制点捋清楚再讲怎么按管理层、监控层、现场设备层做分层适配接着是功能码深度防护在C语言层面的实现思路最后给一套可以拿去现场跑的合规自查脚本。第18篇留的五道课后思考题也在这里逐一给出完整解析。如果你手里正好有嵌入式或工控设备的整改任务这篇可以直接当作业查手册来用。1. 为什么嵌入式设备在等保2.0测评里最容易躺枪1.1 等保2.0的框架里工控扩展要求到底管什么等保2.0依据GB/T 22239-2019《信息安全技术 网络安全等级保护基本要求》运行除了所有系统都要满足的通用安全要求外还针对云计算、移动互联、物联网、工控系统等场景增加了扩展要求。嵌入式工控设备RTU、DTU、PLC通信模块、智能终端等在工控网络里通常被视为控制设备主要对应工控系统安全扩展要求如果是一个独立的物联网感知设备可能还要同时对照物联网扩展要求。很多整改项目在前期扯皮都是因为没先把设备在这两本账里属于哪一本这个问题搞清楚。工控扩展要求里和嵌入式设备密切相关的核心控制组大致集中在这几块安全物理环境室外控制设备的物理防护比如机柜锁、防雷接地、室外设备的防盗防破坏。安全通信网络控制网络与管理网络分离通信传输需要保证完整性和保密性。安全区域边界访问控制、入侵防范尤其强调对工控协议做深度检测。安全计算环境控制设备安全、身份鉴别、访问控制、安全审计、入侵防范、可信验证等。我见过最多的情况是测评机构拿着控制设备安全这个控制点去查PLC和RTU结果连设备应关闭不必要的端口设备应禁止未授权的远程访问这种基础项都过不了。这两项在传统IT网络里就是防火墙的活到了嵌入式设备侧经常因为研发阶段习惯开着telnet调试、守着502端口裸奔测评一来直接挂掉。1.2 嵌入式设备最常见的几个翻车点和真实案例先列一下我在整改现场反复遇到的典型问题测评专家查的就是这些点翻车点测评会怎么查现场常见表现整改方向远程调试口全开扫描开放端口检查telnet/SSH23、22端口全网可达无来源IP限制关闭telnet限制SSH来源或改用运维审计通道Modbus/TCP 502端口全网可达区域边界访问控制检查任意主机都能访问RTU的502端口加IP白名单配合功能码白名单做指令级限制SNMP默认读团体字public检查SNMP配置工控交换机、采集器用public可读全部MIB修改团体字禁用SNMPv1/v2c或迁移到v3Web管理后台弱口令尝试登录管理页面admin/admin或无登录鉴权强口令策略登录失败锁定管理口不暴露到业务网固件升级无签名校验检查运维文档和设备抓包固件包明文存放在TFTP目录无校验升级镜像加签名设备侧验证后才可写入审计日志未落盘、未外送查看设备日志存储与转发配置本地无日志或掉电即丢本地循环存储同时通过syslog外送到集中平台控制指令无功能码白名单抓包分析控制流量任一主站可写保持寄存器实现功能码白名单按方向和地址范围过滤举一个实际案例。去年做某燃气站控系统的配合整改一期整改单里有一条是无线模块远程维护端口全部映射到管理网。开发同学很麻利直接把端口映射改了以为整改完成。结果下一个测评周期测评专家继续查设备本身发现RTU本地没有任何审计日志又追加了一条控制设备安全审计缺失。这个例子很典型边界上的问题可以用管理手段补偿但设备本身的合规项躲不掉基础加固必须做扎实。2. 分层适配把工控扩展要求按管理层、监控层、现场层拆开2.1 工控网络的三层结构与控制点映射等保2.0强调同步规划、同步建设、同步使用落到工控场景工程上最有效的做法就是按工控网络的三层结构来分批适配而不是把一堆测评项堆给一个部门。管理层、监控层、现场设备层各有各的控制点重点嵌入式设备的主要职责集中在下两层。层级主要设备主要适用控制点嵌入式设备承担的职责管理层ERP、MES、生产调度系统安全通信网络、安全区域边界、安全计算环境日志外送、对接运维审计平台监控层SCADA服务器、历史数据库、工程师站安全计算环境、集中管控、安全审计作为数据采集终端上报运行日志和指令记录现场设备层PLC、RTU、DTU、传感器控制设备安全、安全物理环境设备自身加固、指令级过滤、本地日志留存这个分层的意义在于测评专家组到现场检查时不会只看一份总的等保制度文件而是按服务器区控制网络现场设备逐层看证据。嵌入式设备这一层最能拿得出手的证据就是设备自身的加固配置和实际的报文过滤记录。2.2 设备侧适配的能做的和不必做的嵌入式设备资源有限不可能像服务器一样装全功能安全Agent。我的经验是分清楚哪些必须在设备本地做哪些可以用管理措施做补偿设备本地必须做的口令策略修改默认口令、强制强口令、连续失败锁定。服务最小化关闭telnet、SNMP、未使用的Web端口只留业务必需端口。指令级访问控制对Modbus/TCP等功能码做白名单过滤。审计日志本地循环存储关键操作日志。固件验签升级包签名验证防止被植入后门。不必在设备本地死磕、可以用管理措施补偿的高强度链路加密如果设备算力实在扛不住可以在边界部署工业加密网关内部网络继续跑明文协议。完整可信计算体系MCU只有几百KB RAM硬上TPM不现实可以用上位机集中校验固件哈希作为补偿。集中身份认证设备本地做不了RADIUS就统一对接安全的运维管理平台来管人。这里有一个关键认知补偿措施不是不做而是用另一个控制点去覆盖。测评专家最反感的是现场口头解释我们设备做不到只要你把补偿控制措施写进整改文档、给出执行证据大多数情况是能接受的。2.3 一张把测评项翻译成工程任务的映射表等保2.0的条款语言是给安全管理人员看的开发工程师拿到整改单常常一脸懵。我习惯做一层翻译把测评控制点转换成嵌入式侧的工程任务测评控制点测评要求核心语义嵌入式侧的工程落地身份鉴别设备应支持身份鉴别修改默认口令、启用强口令策略、登录失败锁定访问控制应限制未授权访问端口白名单、来源IP白名单、功能码白名单安全审计日志记录、留存本地循环存储并支持syslog外送入侵防范抵御已知攻击关闭调试接口、应用层功能码深度过滤通信传输保障完整性和保密性关键指令采用签名/消息鉴别或走专用加密链路可信验证启动和运行过程可追溯、防篡改Secure Boot、配置分区只读和启动时校验集中管控设备应可集中管理支持对接SCADA或安全管理平台的北向接口有了这张表开发和运维团队就能逐项打勾而不是抱着整改单在会议室里猜这个要求到底想让我干什么。3. 功能码深度防护Modbus/TCP白名单怎么落进C代码3.1 为什么只做端口放行等于没做防护传统网络层的访问控制本质上是允许来自某IP的主机访问某端口。但在工控现场SCADA服务器和工程师站的IP地址往往是固定的攻击者一旦拿下其中一台上位机就直接拿到了合法IP此时网络层ACL形同虚设。更麻烦的是Modbus/TCP是一个明文协议502端口必须开放给上位机做轮询不可能直接封掉。安全的关键就从谁能连变成了连上之后允许做什么——也就是功能码级别的白名单。举例来说正常生产运行中上位机定期读保持寄存器功能码0x03和输入寄存器0x04偶尔写一下设定参数0x06单寄存器、0x10多寄存器。如果某台设备突然开始频繁执行写操作或者读取了超出业务范围的寄存器地址这就是危险行为必须在报文路径上把它拦下来。这就是功能码深度防护的出发点把安全规则下沉到工控协议语义层做精细的指令级管控。3.2 报文过滤状态机的设计与关键代码我曾在串口服务器前置机上实现过一版Modbus/TCP过滤模块核心思路分四步解析MBAP头检查协议标识符必须为0长度字段必须与实际报文长度一致防止畸形包绕过。提取功能码按白名单规则查表只放行规则允许的功能码和寄存器地址范围。速率限制对同一来源IP、同一功能码做每分钟次数限制拦截指令风暴。分方向检查主站到从站的写操作重点审查从站到主站的异常响应也要拦截。以下是裁剪后的代码骨架可以直接移植到嵌入式C工程里/* 功能码白名单规则表 */ typedef struct { uint8_t func; /* 01 02 03 04 05 06 0F 10 08 等 */ uint16_t addr_lo; /* 寄存器起始地址 */ uint16_t addr_hi; /* 寄存器结束地址 */ uint32_t rate_limit; /* 每分钟最多允许次数 */ uint8_t direction; /* 0x01: 主站-从站, 0x02: 从站-主站响应 */ } modbus_acl_rule_t; static const modbus_acl_rule_t g_acl[] { {0x03, 0x0000, 0x0100, 1200, 0x01}, /* 读保持寄存器: 轮询密集 */ {0x04, 0x0000, 0x0100, 1200, 0x01}, /* 读输入寄存器 */ {0x06, 0x0000, 0x0010, 10, 0x01}, /* 写单寄存器: 低频操作 */ {0x10, 0x0000, 0x0010, 10, 0x01}, /* 写多寄存器 */ {0x08, 0x0000, 0x0000, 5, 0x01}, /* 0x08诊断: 只允许子功能0 */ }; modbus_filter_result_t modbus_filter(uint8_t *pkt, uint16_t len, uint32_t src_ip) { uint16_t proto_id, mbap_len, unit_id, func; if (len 8) return FILTER_DROP; /* 协议标识符必须为0, 否则不是标准Modbus/TCP */ proto_id (pkt[2] 8) | pkt[3]; if (proto_id ! 0) return FILTER_DROP; /* 长度字段校验: 防止畸形包绕过规则 */ mbap_len (pkt[4] 8) | pkt[5]; if (mbap_len 6 ! len) return FILTER_DROP; unit_id pkt[6]; if (unit_id ! g_local_unit) return FILTER_DROP; func pkt[7]; /* 遍历白名单规则 */ for (int i 0; i sizeof(g_acl) / sizeof(g_acl[0]); i) { const modbus_acl_rule_t *r g_acl[i]; if (func ! r-func) continue; /* 寄存器地址范围检查 */ uint16_t addr (pkt[8] 8) | pkt[9]; if (addr r-addr_lo || addr r-addr_hi) continue; /* 方向检查 */ if ((r-direction DIR_HOST_TO_SLAVE) 0) continue; /* 速率限制 */ if (!rate_check(src_ip, func, r-rate_limit)) return FILTER_DROP; return FILTER_ACCEPT; } /* 不在白名单, 丢弃并告警 */ return FILTER_DROP; }这段代码里有几个容易被忽略的细节功能码0x08是诊断功能字节8之后还有子功能码比如子功能0x00是查询设备状态。规则里必须在解析时再取子功能码判断只放行允许的项。地址范围判断一定要在长度校验之后做保证pkt[8]和pkt[9]不会越界否则过滤模块本身就成了漏洞入口。速率限制推荐用固定时间窗口计数器,每个桶的key用来源IP功能码组合只需要一个计数数组加时间戳MCU上资源开销很小。3.3 白名单规则表与现场调参经验功能码深度防护最大的工程难点不是写代码而是白名单怎么定才不误伤生产。我在上线第一套规则时因为把0x03的速率阈值卡得太死结果SCADA做一次批量初始化直接把整批轮询请求误拦了中控室画面瞬间全红。后来总结出一套调参流程先采集合法工作流。新设备上线前持续抓包一周统计所有功能码、寄存器范围、访问频次再根据统计结果生成白名单。写操作宁紧勿松。生产运行中写操作频率极低06、10、05这些写功能码的阈值给到每分钟5到10次就足够不需要留太大余量。读操作按轮询周期估算。SCADA一般5秒轮询一次03、04功能码每分钟大概在几百到上千次不等阈值先按实测峰值上浮50%再设。拦截策略别一刀切。第一周可以先切告警记录模式确认规则无误后再切丢弃。否则半夜误挡一次生产流量背锅的肯定是你。特殊协议变体要单独加规则。有些PLC厂商用Modbus封装私有协议功能码会有扩展规则表要能覆盖抓包发现的真实业务。提示功能码深度防护既可以在嵌入式设备内部实现也可以部署在嵌入式前置机或串口服务器上两者的代码逻辑完全一致差别只在于过滤模块放在谁的报文路径上。设备资源极度紧张时先在前置机落地也比裸奔强一个量级。4. 合规自查脚本把整改验收从人工填表变成机器巡检4.1 自查项设计对齐等保测评的检查点测评机构到现场复查时最烦的就是人工填表——填出来的数据跟设备实际状态经常对不上。我的做法是写一个只读的合规自查脚本每次巡检自动扫描设备关键配置项输出统一的检查结果测评专家可以直接引用这些输出作为证据。脚本的自查项设计逻辑就是把等保测评的检查点翻译成具体的设备检查动作测评检查点自动检查项取证输出身份鉴别密码策略、默认口令、连续失败锁定配置shadow配置摘要、PAM配置服务最小化监听端口清单对比允许白名单端口列表标出未授权端口访问控制防火墙规则、ACL配置活动规则快照安全审计syslog进程状态、日志转发配置日志服务状态、转发目标地址入侵防范功能码过滤配置、调试接口状态过滤器加载情况、telnet进程状态固件安全固件版本、升级签名文件、关键文件哈希固件版本号、哈希值4.2 脚本核心实现端口、服务、登录与日志审计嵌入式Linux设备环境差异大psutil这类依赖不一定装得上所以直接读/proc最稳。以下是一个裁剪版脚本的核心逻辑#!/usr/bin/env python3 # 合规自查脚本: 面向嵌入式Linux工控终端 import json, os, re, subprocess, sys, socket, datetime DEVICE_ID sys.argv[1] if len(sys.argv) 1 else socket.gethostname() ALLOWED_PORTS {22, 443, 502} # 按现场白名单调整 def get_listening_ports(): ports [] with open(/proc/net/tcp) as f: for line in f.readlines()[1:]: fields line.split() if len(fields) 4 and fields[3] 0A: # LISTEN状态 port int(fields[1].split(:)[1], 16) ports.append(port) return sorted(set(ports)) def check_ports(): listening get_listening_ports() abnormal [p for p in listening if p not in ALLOWED_PORTS] status PASS if not abnormal else FAIL return { key: listening_ports, status: status, detail: flistening{listening}, abnormal{abnormal} } def check_telnet(): # 检查telnet进程与xinetd/inetd配置 proc subprocess.run([pgrep, -l, telnetd], capture_outputTrue, textTrue) cfg_hit False for path in (/etc/inetd.conf, /etc/xinetd.d/telnet): if os.path.exists(path): try: cfg open(path).read() if telnet in cfg: cfg_hit True except Exception: pass status PASS if proc.returncode ! 0 and not cfg_hit else FAIL return {key: telnet_disabled, status: status, detail: telnetd running if proc.returncode 0 else not found} def check_log_forward(): # 检查syslog是否配置了远程日志服务器 detail no remote log server status FAIL for path in (/etc/rsyslog.conf, /etc/rsyslog.d/, /etc/syslog-ng/syslog-ng.conf): if os.path.isfile(path) and not os.path.isdir(path): text open(path, errorsignore).read() if re.search(r[\w.\-]:\d, text): detail re.findall(r[\w.\-]:\d, text) status PASS break return {key: syslog_forward, status: status, detail: detail}脚本汇总所有检查项后输出统一JSON{ device_id: rtu-03, timestamp: 2026-02-20 14:03:11, checks: [ {key: listening_ports, status: FAIL, detail: listening[23, 502], abnormal[23]}, {key: telnet_disabled, status: FAIL, detail: telnetd running}, {key: syslog_forward, status: PASS, detail: 192.168.1.10:514} ] }这里有个实用细节检查项不能只输出PASS/FAIL一定要带detail证据字段。测评专家要的是你能证明我查过并得出这个结论一个孤零零的FAIL说服力远不如发现23端口开放对应进程telnetd。4.3 让脚本结果能被测评报告直接引用的技巧脚本写了几年之后我总结出几个容易被忽视的实战要点脚本必须只读。它只负责采集和判断绝不自作主张改配置。否则测评专家复查时看到配置被脚本改过整个整改证据链都会被打折扣。每次巡检结果要留存。把脚本挂到cron里定期执行输出统一放到/var/log/compliance/目录并按日期命名。持续合规记录比一次性截图有说服力得多。设备标识最好能自动识别。现场几十台设备如果每台都手动输设备名脚本输出很容易出错。优先从/proc/sys/kernel/hostname或/etc/device-id里自动读取。脚本本身要做sha256校验。把脚本放到只读存储防止被篡改后自查永远是PASS。5. 第18篇课后思考题完整解析上一篇第18讲的主题是通信安全审计与设备身份鉴别结尾留了五道思考题。集中解答如下顺便把前面几章的内容串起来。5.1 为什么嵌入式设备不能用MAC地址作为唯一身份凭证MAC地址是二层地址本身不带任何加密信息。工控网络很多还挂着镜像口抓包攻击者抓一次包就能看到合法主机的MAC地址然后在自己的设备上直接伪造同一个MAC配合IP白名单也拦不住——IP和MAC都是可以被伪造的静态标识。正确做法是往应用层加消息鉴别通信双方预置共享密钥对Modbus报文的关键字段功能码、寄存器地址、数据、时间戳计算HMAC或CMAC摘要附加在报文中接收方验签通过后才执行指令。MCU上跑一个轻量HMAC计算开销几百微秒完全可接受。5.2 从Modbus/TCP会话中识别异常功能码序列正常现场工作流很规律主站周期性03、04读操作偶尔06、10写参数。异常场景通常表现为三类短时间内高频写操作非业务时段的大量读请求从站响应中夹带未请求的功能码。深度防护模块不需要引入复杂的AI模型用功能码频率结合时间窗口就能发现大部分异常。比如某RTU正常每5秒轮询一次某天凌晨3点突然出现每100毫秒一条03请求伴随多次06写操作——这种流量模式直接用白名单规则里的速率限制就能挡掉。再叠加方向检查如果主站从来没有请求过05写单线圈而流量里出现了05直接丢弃并告警。5.3 资源受限设备上的防暴力破解设计口令暴力破解防护的关键是让攻击者每次尝试的成本递增。在嵌入式设备上可以用一个简单的失败计数窗口同一账号或同一来源IP在10分钟内失败5次就锁定15分钟。锁定窗口必须同时覆盖账号和IP维度否则攻击者换一个IP就能继续试。口令存储不能是明文MCU上计算资源有限可以选择加盐的PBKDF2或bcrypt迭代次数根据硬件性能调低一些但盐值和哈希结果绝对不能省。对管理端口和配置接口更推荐直接用证书双向认证或预共享密钥把口令降级为辅助手段。5.4 安全审计日志至少保留哪些字段一份能被等保测评认可的审计日志至少要包含发生时间、源IP、源端口、目的IP、目的端口、协议/功能码、操作对象寄存器地址/线圈、操作动作、执行结果、设备标识。时间戳统一是审计日志里最容易被忽略但又最关键的点。多台设备的安全事件要串成攻击链前提是时间能对齐。设备必须支持SNTP/NTP时间同步暂时没有网络对时的也要在日志里标注设备本地RTC偏差值。本地日志循环存储建议不少于30天同时外送到集中的日志平台防止设备被物理破坏后日志跟着丢。5.5 可信验证落到嵌入式设备上的现实做法等保2.0工控扩展要求里的可信验证控制点在嵌入式设备上并不等于必须上TPM安全芯片。对MCU类设备现实可行的组合是启动阶段对固件做签名校验相当于精简版Secure Boot关键配置分区设置只读属性配合启动时哈希校验固件升级包必须带数字签名设备验签通过才允许写入。如果设备算力确实支撑不了就用补偿措施由上位机安全管理平台集中审计关键配置变更定期巡检并比对固件哈希把设备可信转变为可验证的持续记录。测评沟通时把设备侧基础防护管理侧集中补偿的映射写进整改文档通常没有问题——怕就怕什么都不写临时口头解释。最后说点个人体会。把几套水厂和燃气站控系统整改做完后我最深的感受是合规整改这个东西只要先把设备侧的关闭、限制、记录这三件事做扎实再配合边界防护复查时的返工量能少一大半。功能码白名单和合规自查脚本是我每次整改必带的两个工具一个管住能干什么一个证明干了什么、查了什么。建议你也从这两个点先动手比闷头改配置文件出效果快得多。
返回列表