工控安全入门:基于ISF框架的S7-200 PLC密码检测实战 📅 发布时间:2026/9/20 6:38:33 👁 浏览次数: 1. 工控安全入门从PLC密码检测说起工控安全这几年被提得越来越多但真正落到实处的项目其实不多。大部分做安全的同行习惯性地把精力放在Web渗透、内网横向、域控这些传统方向上一提到工控第一反应就是“那玩意儿碰不得一碰产线就停了”。这种心态可以理解但反过来也说明一个问题大家对工控系统的了解太少了少到连它到底怎么运转、有哪些攻击面都说不清楚。我最早接触PLC密码检测这个方向是因为一个实际需求。某次做资产梳理的时候发现现场有几十台S7-200系列的老PLC这些设备分布在不同的控制柜里有些还在跑产线有些已经闲置但没断电。问题来了这些PLC到底有没有设密码设了密码的密码强度怎么样有没有还在用默认密码或者空密码的这些问题没人能回答因为设备台账上根本没记录。于是就有了这个项目——用ISFIndustrial Security Framework这个工控渗透框架对S7-200系列PLC做一次系统性的密码检测。这篇文章适合谁看如果你是做安全服务的想了解工控渗透的基本流程和工具链如果你是工控运维人员想知道自己手里的PLC到底安不安全或者你只是对PLC这个“黑盒子”好奇想搞清楚它到底怎么通信、怎么认证的——那这篇内容应该能给你一些实在的参考。我会从ISF框架的选型讲起把S7-200的通信协议、密码认证机制、检测脚本的编写和调试过程都拆开来讲最后再分享一些实际踩过的坑和排查经验。需要提前说明的是这里讨论的所有内容都基于授权测试场景。工控系统跟普通IT系统不一样它直接连着物理设备一个误操作可能导致阀门误动、电机误启后果不是“重启一下就好”能解决的。所以下面提到的所有操作都默认你已经在实验室环境或者有明确授权的现场做过充分验证。2. 为什么选ISF框架做PLC密码检测2.1 ISF框架的核心定位与能力边界ISF全称Industrial Security Framework是一个专门针对工业控制系统的渗透测试框架。它跟Metasploit那种通用渗透框架最大的区别在于ISF内置了大量工控协议的支持模块包括Modbus、S7comm、DNP3、IEC 60870-5-104、EtherNet/IP等。你不需要自己去写协议栈框架已经把底层通信的脏活累活干完了。我选ISF而不是自己用Python写脚本主要基于几个考虑。第一S7comm协议虽然文档不少但实际实现的时候有很多细节坑比如TPKT和COTP的封装、PDU协商、参数区的编码方式自己从头写一遍至少得花两三天调试。ISF里已经有现成的S7comm模块直接调用就行。第二ISF的模块化设计比较清晰你要加一个新的检测逻辑只需要继承现有的exploit或者auxiliary模块重写几个方法就行不用关心底层socket怎么收发。第三ISF自带了一些工控协议的身份认证探测功能虽然不完整但可以作为参考实现。当然ISF也不是没有缺点。它的更新频率不高有些模块还是Python 2时代的代码跑在Python 3环境下需要做一些兼容性处理。另外它的文档比较简陋很多功能得自己读源码才能搞明白怎么用。但总体来说对于PLC密码检测这个需求ISF是目前比较顺手的选择。2.2 S7-200系列PLC的通信特点S7-200是西门子早期的小型PLC虽然官方已经停产多年但在国内存量巨大。很多老产线、水处理站、楼宇自控系统里还在跑。它的通信接口主要是PPIPoint-to-Point Interface和MPIMulti-Point Interface后来也有以太网模块比如CP243-1。我们这次检测主要针对以太网接入的场景因为通过以太网做密码检测不需要串口线操作更方便也更容易批量执行。S7-200的S7comm通信跟S7-300/400/1200/1500有一些差异。最明显的一点是S7-200的密码保护机制相对简单。它有一个“密码级别”的概念分为4级级别1是完全权限级别2是部分读写权限级别3是只读权限级别4是禁止上传下载。密码本身是8位字符存储在PLC的特定内存区域。当你尝试建立通信或者执行某些操作时PLC会要求你提供密码。这里有个关键点S7-200的密码验证是在S7comm的“用户数据”PDU里完成的而不是在TCP握手阶段。也就是说你可以先跟PLC建立TCP连接然后通过发送特定的S7comm请求来探测密码状态。这个特性让密码检测变得可行——你不需要真的“登录”进去只需要观察PLC对不同请求的响应就能判断密码是否存在、是否启用。2.3 密码检测与密码破解的区别这里要区分两个概念密码检测和密码破解。密码检测是判断“有没有密码”“密码是否启用”“密码级别是什么”它不涉及暴力枚举或者字典攻击。密码破解则是尝试猜出具体密码内容这需要大量的尝试对PLC来说风险很高——频繁的失败尝试可能导致PLC进入保护状态甚至影响正常通信。我们这次做的是密码检测不是破解。原因很简单在工控环境里破解密码的代价太大。S7-200的密码尝试次数虽然没有严格的锁定机制但每次尝试都会产生通信流量如果PLC同时在跟HMI或者上位机通信额外的流量可能导致通信超时或者数据错乱。而且很多现场根本不允许你做暴力破解哪怕是在测试窗口期。所以我们的目标很明确通过发送特定的S7comm请求观察PLC的响应判断密码保护是否启用、当前会话的权限级别是什么。如果发现某台PLC没有启用密码保护那本身就是个高危问题——任何人都能直接上传下载程序修改控制逻辑。如果启用了密码保护我们再记录下密码级别作为资产风险评分的依据。3. S7-200密码检测的核心原理与实操细节3.1 S7comm协议中与密码相关的PDU结构要理解密码检测的原理得先搞清楚S7comm的PDU长什么样。S7comm是建立在TPKT和COTP之上的应用层协议。一个完整的S7comm请求从外到内依次是TPKT头4字节、COTP头可变长度、S7comm头10字节、参数区可变长度、数据区可变长度。跟密码检测相关的主要是S7comm头里的“ROSCTR”字段和参数区里的“功能码”。ROSCTR表示请求类型比如0x01是Job请求0x02是Ack0x03是Ack-Data0x07是Userdata。密码验证相关的交互通常走的是Userdata类型。具体来说S7-200的密码验证流程是这样的客户端发送一个Userdata请求里面包含“密码”相关的功能组和功能码。PLC收到后如果密码正确会返回一个Ack-Data里面包含权限级别信息如果密码错误会返回一个错误码。如果PLC根本没有启用密码保护那它对任何请求都会直接返回正常响应不会要求密码。这里有个细节需要注意S7-200的密码验证不是每次通信都做的。一旦你通过密码验证建立了会话后续的请求在同一个TCP连接里就不需要重复验证了。但如果你断开重连就得重新验证。这个特性意味着我们可以通过观察“建立连接后的第一个请求”的响应来判断密码状态。3.2 密码检测脚本的关键逻辑基于上面的原理密码检测脚本的核心逻辑可以概括为三步第一步建立TCP连接。目标端口通常是102这是S7comm的默认端口。连接建立后先发送一个COTP连接请求协商通信参数。这一步跟普通S7通信没有区别。第二步发送密码探测请求。这里有两种策略一种是直接发送一个需要权限的操作请求比如“上传程序块”或者“读取系统信息”观察PLC是否返回“权限不足”的错误另一种是发送专门的Userdata请求直接查询密码状态。第一种策略更简单但可能会触发PLC的审计日志第二种策略更隐蔽但需要构造特定的PDU。我实际用的是第一种策略的变种发送一个“读取CPU信息”的请求。这个请求在S7-200上不需要密码就能执行但如果PLC启用了密码保护返回的数据里会包含一个“保护级别”字段。通过解析这个字段就能知道当前密码保护的状态。第三步解析响应并记录结果。如果响应里保护级别是0说明没有密码保护如果是1、2、3分别对应不同的权限级别。如果响应超时或者返回错误码说明PLC可能不支持这个请求或者通信参数不对需要调整。3.3 实操中的参数配置与注意事项在实际写脚本的时候有几个参数需要特别注意。目标IP和端口S7-200的以太网模块默认IP通常是192.168.0.1或者192.168.2.1但现场一般会改。端口默认102如果改了端口需要相应调整。COTP连接参数S7-200对COTP的TSAPTransport Service Access Point有特定要求。本地TSAP通常设为0x0100远程TSAP设为0x0200。如果设错了PLC会直接拒绝连接。超时时间工控网络的延迟通常比办公网大尤其是经过交换机级联或者无线网桥的时候。超时时间建议设成5秒以上否则容易误判。我一开始用2秒超时结果有一半的PLC都报“连接超时”后来改成8秒才稳定。重试次数S7-200的通信处理能力有限如果同时收到多个请求可能会丢包。建议每次请求之间间隔至少500毫秒重试次数不超过3次。重试太频繁可能导致PLC通信模块死机那就得断电重启了。注意在产线运行的PLC上做密码检测一定要避开生产高峰期。最好先跟现场运维确认有没有通信余量否则你的检测流量可能把正常的HMI通信挤掉导致操作员看不到数据。4. 完整实操流程从环境搭建到结果分析4.1 测试环境搭建与工具准备先说一下我用的环境。操作系统是Kali LinuxISF框架直接从GitHub克隆下来。Python版本是3.9需要额外装几个依赖python-snap7用于S7comm通信、scapy用于构造原始数据包、pymodbus备用有些场景需要Modbus探测。ISF的安装比较简单但有几个坑。第一它的requirements.txt里有些包版本很老直接pip install会报错。我的做法是手动装核心依赖跳过那些不重要的。第二ISF的S7comm模块默认用的是Python 2的语法比如print语句没加括号。你需要用2to3工具转一遍或者手动改几个文件。第三ISF的日志模块会往/var/log/isf/写日志如果权限不够会报错建议用root跑或者提前建好目录。测试环境里我放了三台S7-200一台是CPU 226带CP243-1以太网模块固件版本V2.0一台是CPU 224XP同样带CP243-1还有一台是CPU 222没有以太网模块用PPI转以太网的网关接入。三台PLC分别设置了不同的密码状态第一台无密码第二台有密码但级别为2第三台有密码且级别为3。4.2 密码检测脚本的编写与调试ISF框架里已经有一个s7_200_password_check的auxiliary模块但功能比较简陋只支持单目标检测而且没有结果输出格式化。我在它的基础上做了几个改进支持批量目标、增加超时重试、输出JSON格式的结果。核心代码逻辑大概是这样from scapy.all import * from scapy.contrib.s7comm import * def check_password(ip, port102, timeout8): # 建立TCP连接 sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) try: sock.connect((ip, port)) except: return {ip: ip, status: unreachable} # 构造COTP连接请求 cotp_cr COTP(CR1, dst_ref0x0000, src_ref0x0001, pdu_type0x0e, tpdu_size0x0a) sock.send(bytes(cotp_cr)) resp sock.recv(1024) # 构造S7comm读取CPU信息请求 s7_req S7Comm(rosctr0x01, pdu_ref0x0001, paramS7CommParam(func0x1c)) sock.send(bytes(s7_req)) resp sock.recv(1024) # 解析响应中的保护级别 protection_level parse_protection_level(resp) sock.close() return {ip: ip, protection_level: protection_level}这段代码看起来简单但调试的时候花了我不少时间。最大的问题是S7comm的PDU封装。scapy的s7comm contrib模块虽然提供了S7Comm类但它的字段定义跟S7-200的实际协议有些出入。比如pdu_ref字段S7-200要求必须是0x0001到0x000f之间的值但scapy默认给的是0。如果不改PLC会返回“无效PDU引用”的错误。另一个坑是COTP的tpdu_size字段。S7-200只支持0x0a即1024字节和0x0b即2048字节如果你设成其他值PLC会拒绝连接。我一开始设成0x07128字节结果一直连不上后来查了S7-200的通信手册才发现这个问题。4.3 批量检测与结果记录单台检测跑通之后批量检测就简单了。我把目标IP列表写到一个文本文件里用多线程并发执行但并发数控制在5以内避免对网络造成太大压力。每个目标的检测结果输出到JSON文件包含IP、端口、保护级别、响应时间、错误信息等字段。实际跑下来30台PLC的检测大概用了3分钟。其中28台成功返回结果2台因为网络不通被标记为unreachable。28台里有6台没有启用密码保护15台密码级别为27台密码级别为3。这个结果跟现场运维的预期基本吻合——那些没有密码保护的PLC都是早期安装的当时没有安全要求密码级别为3的是后来改造时统一设置的。结果分析的时候我重点关注两类风险一是无密码保护的PLC这些设备可以直接上传下载程序风险最高二是密码级别为2的PLC虽然不能上传程序但可以修改部分数据区比如定时器、计数器的预设值同样可能影响生产。密码级别为3的PLC只能读取数据风险相对较低但如果读取的数据包含敏感信息比如配方参数也需要关注。5. 常见问题与排查技巧实录5.1 连接建立失败的原因与排查连接建立失败是最常见的问题表现就是socket.connect()直接抛异常。原因通常有三类网络不通、端口不对、PLC通信模块忙。网络不通最好排查ping一下就知道。但要注意有些工控网络禁用了ICMPping不通不代表TCP不通。这时候可以用telnet或者nc试一下102端口。端口不对的情况比较少见但确实遇到过。有一台PLC的以太网模块被配置成了非标准端口具体是多少现场人员也说不清。我的做法是用nmap扫一下常见的工控端口102、502、44818、20000都试一遍。PLC通信模块忙是最麻烦的。S7-200的通信资源有限如果HMI正在跟PLC通信你的连接请求可能会被排队或者直接拒绝。表现就是连接能建立但发送请求后没有响应或者响应超时。这时候可以尝试降低请求频率或者在HMI通信的间隙插入检测。如果实在不行只能跟现场协商一个停机窗口。5.2 响应解析错误的处理响应解析错误通常是因为PDU结构跟预期不一致。S7-200的响应PDU里参数区的长度是可变的如果你按固定长度去解析就会读错字段。我的做法是先读S7comm头的参数长度字段然后根据这个长度去截取参数区再解析里面的功能码和数据。另一个常见问题是字节序。S7comm协议里多字节字段用的是大端序但有些字段比如错误码用的是小端序。如果不注意解析出来的保护级别可能是错的。我一开始就犯过这个错误把0x0200解析成了0x0002结果把密码级别2误判成了无密码。5.3 对PLC运行的影响与规避这是最需要谨慎对待的问题。密码检测本身是只读操作理论上不会改变PLC的运行状态。但实际中如果检测流量过大可能导致PLC的通信模块过载进而影响HMI的数据刷新甚至导致PLC进入停止模式。我遇到过两次比较危险的情况。一次是并发数设成了10结果一台S7-200的通信模块直接死机HMI黑屏产线停了5分钟。另一次是检测脚本里有个bug发送了错误的PDUPLC返回了“非法请求”的错误然后通信就断了必须断电重启才能恢复。从那以后我给自己定了几个规矩并发数不超过3每次请求间隔至少1秒检测前必须确认PLC有通信余量检测过程中安排人盯着HMI。如果HMI出现数据不刷新或者报警立即停止检测。5.4 常见问题速查表问题现象可能原因排查方法解决措施连接超时网络不通或端口错误ping测试、nmap扫描检查网络配置、确认端口连接被拒绝PLC通信模块忙查看HMI是否正常降低频率、避开高峰期响应超时请求PDU格式错误抓包对比正常通信检查COTP和S7comm字段保护级别解析错误字节序或长度字段错误手动解析原始字节按协议文档逐字段核对PLC通信死机请求频率过高观察HMI状态断电重启、降低并发返回“非法请求”PDU引用或功能码不支持对比S7-200通信手册调整PDU参数提示如果你在检测过程中发现PLC返回了从未见过的错误码不要反复尝试。先停下来抓包分析确认PDU结构没问题再继续。反复发送错误请求可能导致PLC进入保护状态。6. 工控密码检测的边界与经验总结做PLC密码检测这几年我最大的体会是技术本身不难难的是对工控场景的理解和敬畏。IT渗透里你扫端口、发payload、拿shell最坏的结果就是服务器宕机重启一下还能恢复。但工控不一样PLC后面连着的是电机、阀门、传感器你的一个数据包可能直接导致物理世界的动作。这种责任不是技术能力能覆盖的它需要你对现场工艺有基本的了解知道哪些设备能碰、哪些不能碰、碰了会有什么后果。另一个体会是工控安全检测的工具链还很不成熟。ISF已经算是比较好用的了但跟Metasploit、Burp Suite这些IT工具比差距还是很大。很多功能需要自己写脚本补全很多协议需要自己抓包分析。这既是挑战也是机会——如果你愿意花时间研究很容易在这个领域做出有价值的东西。最后分享一个实用技巧做PLC密码检测之前先跟现场运维要一份通信点表。点表里会列出PLC的IP、端口、通信协议、HMI的轮询周期。有了这份表你就能知道哪些PLC在跑、哪些在闲、通信余量大概有多少。这比你自己去扫、去猜要靠谱得多。而且跟运维沟通的过程也是建立信任的过程——让他们知道你在做什么、为什么做、有什么风险后续的检测会顺利很多。这个方向后续还可以往几个方向扩展一是支持更多PLC型号比如S7-1200/1500的密码检测它们的认证机制跟S7-200完全不同二是做密码强度评估不是破解密码而是通过分析PLC的配置信息判断密码策略是否合规三是跟资产管理系统对接把密码检测结果自动同步到CMDB实现持续监控。这些方向我都试过一些有机会再单独写。