Talos Vulnerability Report

TALOS-2024-1938

AutomationDirect P3-550E Programming Software Connection FileSystem API out-of-bounds write vulnerabilities

May 28, 2024
CVE Number

CVE-2024-24956,CVE-2024-24957,CVE-2024-24959,CVE-2024-24958,CVE-2024-24955,CVE-2024-24954

SUMMARY

Several out-of-bounds write vulnerabilities exist in the Programming Software Connection FileSystem API functionality of AutomationDirect P3-550E 1.2.10.9. Specially crafted network packets can lead to heap-based memory corruption. An attacker can send malicious packets to trigger these vulnerabilities.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

AutomationDirect P3-550E 1.2.10.9

PRODUCT URLS

P3-550E - https://www.automationdirect.com/adc/shopping/catalog/programmable_controllers/productivity3000plcs(modular)/cpus/p3-550e

CVSSv3 SCORE

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

CWE

CWE-787 - Out-of-bounds Write

DETAILS

The P3-550E is the most recent CPU module released in the Productivity3000 line of Programmable Automation Controllers from AutomationDirect. It is an affordable control CPU which communicates remotely via ethernet, serial, and USB and exposes a variety of control services, including MQTT, Modbus, ENIP and the engineering workstation protocol DirectNET.

The P3-550E exposes a “Programming Software Connection” service over UDP port 9999 that is used by the engineering workstation software to program and otherwise configure the device. The protocol is not well documented and what information we do have is pieced together from reverse engineering efforts.

Each message of the protocol is prefixed with a 12-byte header containing the requesting client’s IP address and originating port, two 16-bit fields only referred to as ‘GBS’ and ‘IMM’, a 16-bit field of unknown value, followed by a payload which varies by the type of request.

0                               1
0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|              GBS              |          Client Port          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           Client IP                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|              IMM              |            UNKNOWN            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

The first byte of the IMM field acts as a function code, dictating which overarching feature is being requested, and the second byte dictates the exact functionality expected. The protocol format for the remainder of the message is dependent on the IMM value.

This set of vulnerabilities arises in the file-system related feature set, reachable when the first byte of IMM is 0xF. The function responsible for implementing these features is located at offset 0xb6784 and we refer to it as _DISCOVERY_CALLBACK_F. Within this function, the second byte of IMM is used in a switch-case to identify the exact feature being requested. These vulnerabilities appear quite often throughout the file-system features, effectively occuring in any feature that expects to receive a path argument in the payload. These vulnerable sub-functions expect a very simple payload.

 0                               1
 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                            Path Length                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               Path                            |
+                               ....                            +
|                               ....                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

CVE-2024-24954 - IMM 0x0F03 null-byte write

The first of these vulnerabilities appears within the ‘Delete File’ feature at offset 0xb69c8, but the flaw is generally the same across all instances. Below, we include the vulnerable portion of the Delete File handler, accessed via IMM[0] = 0xF, IMM[1] = 0x3.

0000b69c8                  case 0x3:
                               // [1] Attempt to null-terminate the path, using the attacker-controlled path_len value
0000b69c8                      pkt->path[pkt->path_len] = '\0';
0000b69cc                      int32_t success = FS_DEL(&pkt->path);
0000b69d4                      if (success)
0000b69e4                          pkt->success_flag = 1;
0000b69d8                      else
0000b69d8                          pkt->success_flag = 0;

The implementation is rather straightforward - the path provided in the request is passed to a function we refer to as FS_DEL and the success of that operation is returned to the client. This vulnerability arises from the null-termination step occuring at 0xb69c8, where the attacker-controlled path_len field is used to calculate a pointer to the end of the path and guarantee the path is null-terminated. An attacker can craft a request whose path_len value would cause this calculation to land outside the bounds of the path parameter, and write a null-byte to an arbitrary address. Given that the pkt variable is allocated on the heap, accurately controlling the destination of the null-byte overwrite is difficult without an info-leak, unless the attacker targets structures relative to the object itself, such as heap metadata which immediately precedes every heap allocation.

CVE-2024-24955 - IMM 0x0F04 null-byte write

The next instance of this vulnerability arises for case IMM[0] = 0x0F && IMM[1] = 0x04 which implements the mkdir operation. It expects the same structure of packet as described above.

0000b6a00              case 4:
0000b6a00              {
                           // [1] Similarly, attempt to null-terminate using attacker-controlled path_len field
0000b69fc                  pkt->path[pkt->path_len] = 0;
0000b6a04                  PCMON > FI > MKDIR handler(&pkt->path);
0000b6a10                  if (true)
0000b6a0c                  {
0000b6a20                      pkt->path_len = 1;
0000b6a20                  }
0000b6a14                  else
0000b6a14                  {
0000b6a14                      pkt->path_len = 0;
0000b6a14                  }
0000b6a24                  response_length = 0x14;
0000b6a24                  break;
0000b6a24              }

CVE-2024-24956 - IMM 0x0F06 null-byte overwrite

The next instance of this vulnerability arises for case IMM[0] = 0x0F && IMM[1] = 0x06 which implements the rmdir operation. It expects the same structure of packet as described above.

0000b6a3c              case 6:
0000b6a3c              {
                           // [1] Similarly, attempt to null-terminate using attacker-controlled path_len field
0000b6a38                  pkt->path[pkt->path_len] = 0;
0000b6a48                  if (rmdir(&pkt->path))
0000b6a44                  {
0000b6a58                      pkt->response = 1;
0000b6a58                  }
0000b6a4c                  else
0000b6a4c                  {
0000b6a4c                      pkt->response = 0;
0000b6a4c                  }
0000b6a5c                  response_length = 0x14;
0000b6a5c                  break;
0000b6a5c              }

CVE-2024-24957 - IMM 0x0F0E null-byte overwrite

The next instance of this vulnerability arises for case IMM[0] = 0x0F && IMM[1] = 0x0E which destroys a timer object used during file transfer operations. It expects the same structure of packet as described above. While it is likely that the actual names of the fields for this packet are not ‘path’ and ‘path_len’ they are treated the same and the same style of vulnerability arises.

0000b6a74              case 0xe:
0000b6a74              {
0000b6a74                  if (D11TMR_Created != 0)
0000b6a70                  {
0000b6a80                      NU_Control_Timer(&D11TMR_Timer, NU_DISABLE_TIMER);
0000b6a88                      NU_Destroy_Timer(&D11TMR_Timer);
0000b6a90                      D11TMR_Created = 0;
0000b6a90                  }
                           // [1] Similarly, attempt to null-terminate using attacker-controlled path_len field
0000b6aa4                  pkt->path[pkt->path_len] = 0;
0000b6ab4                  if (data_1d7adc != 0)
0000b6ab0                  {
0000b6abc                      pkt->path[0] = 1;
0000b6abc                  }

CVE-2024-24958 - IMM 0x0F14 null-byte overwrite

For the remaining two instances of these vulnerabilities, the vulnerable fields of the packet payload differ slightly. Instead of an invalid index caused by pkt->path[pkt->path_len] the invalid index is caused by fields we believe represent directory and directory_len which occur at offsets 0x138 and 0x134 respectively.

This instance arises when IMM[0] = 0x0F && IMM[1] = 0x14 and it is still unclear what functionality this exposes.

0000b6be0              case 0x14:
0000b6be0              {
                           // [1] Similarly, attempt to null-terminate using attacker-controlled directory_len field
0000b6bdc                  pkt->directory[pkt->directory_len] = 0;
0000b6bf0                  if (sub_b8070(pkt) != 1)
0000b6bec                  {
0000b6c00                      pkt->response_field = 1;
0000b6c00                  }
0000b6bf4                  else
0000b6bf4                  {
0000b6bf4                      pkt->response_field = 0;
0000b6bf4                  }
0000b6c04                  response_length = 0x138;
0000b6c04                  break;
0000b6c04              }

CVE-2024-24959 - IMM 0x0F16 null-byte overwrite

The final instance of this vulnerability arises for case IMM[0] = 0x0F && IMM[1] = 0x16 which implements an unknown operation. It expects the same structure of packet as described in the previous vulnerability.

0000b6c1c              case 0x16:
0000b6c1c              {
                           // [1] Similarly, attempt to null-terminate using attacker-controlled directory_len field
0000b6c18                  pkt->directory[pkt->directory_len] = 0;
0000b6c2c                  if (sub_b80f4(pkt) != 1)
0000b6c28                  {
0000b6c3c                      pkt->response_field = 1;
0000b6c3c                  }
0000b6c30                  else
0000b6c30                  {
0000b6c30                      pkt->response_field = 0;
0000b6c30                  }
0000b6c40                  response_length = 0x138;
0000b6c40                  break;
0000b6c40              }
VENDOR RESPONSE

A CISA advisory can be found here: https://www.cisa.gov/news-events/ics-advisories/icsa-24-144-01

TIMELINE

2024-02-14 - Initial Vendor Contact
2024-02-15 - Vendor Disclosure
2024-05-23 - Vendor Patch Release
2024-05-28 - Public Release

Credit

Discovered by Matt Wiseman of Cisco Talos.