Talos Vulnerability Report

TALOS-2017-0399

Cesanta Mongoose MQTT Payload Length Remote Code Execution

October 31, 2017
CVE Number

CVE-2017-2892

Summary

An exploitable arbitrary memory read vulnerability exists in the MQTT packet parsing functionality of Cesanta Mongoose 6.8. A specially crafted MQTT packet can cause an arbitrary out-of-bounds memory read and write potentially resulting in information disclosure, denial of service and remote code execution. An attacker needs to send a specially crafted MQTT packet over network to trigger this vulnerability.

Tested Versions

Cesanta Mongoose 6.8

Product URLs

https://cesanta.com/

CVSSv3 Score

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

CWE

CWE-190: Integer Overflow or Wraparound

Details

Mongoose is a monolithic library implementing a number of networking protocols, including HTTP, MQTT, MDNS and others. It is designed with embedded devices in mind and as such is used in many IoT devices and runs on virtually all platforms.

While parsing an MQTT packet with variable length header no check is performed to assure the calculated payload length corresponds to the actual received packet. An arbitrary length is used in pointer arithmetic leading to arbitrary memory access. Variable payload length in mqtt packet is encoded by 7 bit fields with 8th bit in a byte being used as continuation bit. The following code from the parse_mqtt function decodes this:

 /* decode mqtt variable length */
 do 


  len += (*p & 127) << 7 * (p - &io->buf[1]);
  while ((*p++ & 128) != 0 && ((size_t)(p - io->buf) <= io->len));

In the above code, no check is performed on the calculated len value which can be arbitrarily large. By the MQTT standard, the largest MQTT packet can be at most 256 megabytes. Further, a following check is performed:

end = p + len;
if (end > io->buf + io->len + 1) 
 return -1;

In the above code, end should point to the end of message, and the if tries to check if it’s in bounds of the buffer, but since the check is comparing pointers, an integer overflow can cause end to wrap around and point before the start of message buffer, while still having huge len value calculated before.

This can cause further memory corruption down the line when actually handling the commands sent in the packet. For example, this can be exploited by sending a “PUBLISH” command, which ends up notifying all the clients subscribed to a certain topic. Still in the parse_mqtt function we see:

 case MG_MQTT_CMD_PUBLISH: {
   if (MG_MQTT_GET_QOS(header) > 0) 
     mm->message_id = getu16(p);
     p += 2;
   
   p = scanto(p, &mm->topic);

   mm->payload.p = p;
   mm->payload.len = end - p;
   break;

The above code deals with the “PUBLISH” command and uses the end pointer and p to calculate the length, due to the previous integer overflow , end can point to before p leading to a large payload.len value which is later used when sending the notification to subscribed clients.

With precise memory layout control, this can be abused to cause an arbitrary write which could lead to remote code execution. On the other hand, there is a potential to abuse this vulnerability to leak large amount of data from the process as the overflown value is used when sending data to clients. The vulnerability can be triggered by sending the supplied proof of concept packet to sample mqtt_broker application supplied with the library. It should be noted that depending on memory layout, the proof of concept packet might not crash the application, but it does trigger the bug.

Crash Information

    Valgrind output:
==118470== Memcheck, a memory error detector
==118470== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==118470== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==118470== Command: ../../../vanilla/mongoose/examples/mqtt_broker/mqtt_broker
==118470== 
MQTT broker started on 0.0.0.0:8113
ffff==118470== Invalid read of size 1
==118470==    at 0x4C3236C: memcpy@GLIBC_2.2.5 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470==    by 0x403E95: mbuf_insert (mongoose.c:1073)
==118470==    by 0x40EB8D: mg_mqtt_prepend_header (mongoose.c:9824)
==118470==    by 0x40ECCA: mg_mqtt_publish (mongoose.c:9843)
==118470==    by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470==    by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470==    by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470==    by 0x4071B6: mg_call (mongoose.c:2051)
==118470==    by 0x408362: mg_recv_common (mongoose.c:2505)
==118470==    by 0x408393: mg_if_recv_tcp_cb (mongoose.c:2509)
==118470==    by 0x40A712: mg_handle_tcp_read (mongoose.c:3376)
==118470==    by 0x40AC8A: mg_mgr_handle_conn (mongoose.c:3501)
==118470==  Address 0x5ce0796 is 6 bytes inside a block of size 10 free'd
==118470==    at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470==    by 0x4C2FDB7: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470==    by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470==    by 0x404055: mbuf_append (mongoose.c:1096)
==118470==    by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470==    by 0x408158: mg_send (mongoose.c:2463)
==118470==    by 0x40ECA4: mg_mqtt_publish (mongoose.c:9841)
==118470==    by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470==    by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470==    by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470==    by 0x4071B6: mg_call (mongoose.c:2051)
==118470==    by 0x408362: mg_recv_common (mongoose.c:2505)
==118470==  Block was alloc'd at
==118470==    at 0x4C2FD5F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470==    by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470==    by 0x404055: mbuf_append (mongoose.c:1096)
==118470==    by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470==    by 0x408158: mg_send (mongoose.c:2463)
==118470==    by 0x40EC51: mg_mqtt_publish (mongoose.c:9836)
==118470==    by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470==    by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470==    by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470==    by 0x4071B6: mg_call (mongoose.c:2051)
==118470==    by 0x408362: mg_recv_common (mongoose.c:2505)
==118470==    by 0x408393: mg_if_recv_tcp_cb (mongoose.c:2509)
==118470== 
==118470== Invalid write of size 1
==118470==    at 0x4C32372: memcpy@GLIBC_2.2.5 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470==    by 0x403E95: mbuf_insert (mongoose.c:1073)
==118470==    by 0x40EB8D: mg_mqtt_prepend_header (mongoose.c:9824)
==118470==    by 0x40ECCA: mg_mqtt_publish (mongoose.c:9843)
==118470==    by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470==    by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470==    by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470==    by 0x4071B6: mg_call (mongoose.c:2051)
==118470==    by 0x408362: mg_recv_common (mongoose.c:2505)
==118470==    by 0x408393: mg_if_recv_tcp_cb (mongoose.c:2509)
==118470==    by 0x40A712: mg_handle_tcp_read (mongoose.c:3376)
==118470==    by 0x40AC8A: mg_mgr_handle_conn (mongoose.c:3501)
==118470==  Address 0x5ce0798 is 8 bytes inside a block of size 10 free'd
==118470==    at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470==    by 0x4C2FDB7: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470==    by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470==    by 0x404055: mbuf_append (mongoose.c:1096)
==118470==    by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470==    by 0x408158: mg_send (mongoose.c:2463)
==118470==    by 0x40ECA4: mg_mqtt_publish (mongoose.c:9841)
==118470==    by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470==    by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470==    by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470==    by 0x4071B6: mg_call (mongoose.c:2051)
==118470==    by 0x408362: mg_recv_common (mongoose.c:2505)
==118470==  Block was alloc'd at
==118470==    at 0x4C2FD5F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470==    by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470==    by 0x404055: mbuf_append (mongoose.c:1096)
==118470==    by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470==    by 0x408158: mg_send (mongoose.c:2463)
==118470==    by 0x40EC51: mg_mqtt_publish (mongoose.c:9836)
==118470==    by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470==    by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470==    by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470==    by 0x4071B6: mg_call (mongoose.c:2051)
==118470==    by 0x408362: mg_recv_common (mongoose.c:2505)
==118470==    by 0x408393: mg_if_recv_tcp_cb (mongoose.c:2509)
==118470== 
==118470== Invalid write of size 2
==118470==    at 0x4C32723: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470==    by 0x403EBE: mbuf_insert (mongoose.c:1075)
==118470==    by 0x40EB8D: mg_mqtt_prepend_header (mongoose.c:9824)
==118470==    by 0x40ECCA: mg_mqtt_publish (mongoose.c:9843)
==118470==    by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470==    by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470==    by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470==    by 0x4071B6: mg_call (mongoose.c:2051)
==118470==    by 0x408362: mg_recv_common (mongoose.c:2505)
==118470==    by 0x408393: mg_if_recv_tcp_cb (mongoose.c:2509)
==118470==    by 0x40A712: mg_handle_tcp_read (mongoose.c:3376)
==118470==    by 0x40AC8A: mg_mgr_handle_conn (mongoose.c:3501)
==118470==  Address 0x5ce0790 is 0 bytes inside a block of size 10 free'd
==118470==    at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470==    by 0x4C2FDB7: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470==    by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470==    by 0x404055: mbuf_append (mongoose.c:1096)
==118470==    by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470==    by 0x408158: mg_send (mongoose.c:2463)
==118470==    by 0x40ECA4: mg_mqtt_publish (mongoose.c:9841)
==118470==    by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470==    by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470==    by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470==    by 0x4071B6: mg_call (mongoose.c:2051)
==118470==    by 0x408362: mg_recv_common (mongoose.c:2505)
==118470==  Block was alloc'd at
==118470==    at 0x4C2FD5F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470==    by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470==    by 0x404055: mbuf_append (mongoose.c:1096)
==118470==    by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470==    by 0x408158: mg_send (mongoose.c:2463)
==118470==    by 0x40EC51: mg_mqtt_publish (mongoose.c:9836)
==118470==    by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470==    by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470==    by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470==    by 0x4071B6: mg_call (mongoose.c:2051)
==118470==    by 0x408362: mg_recv_common (mongoose.c:2505)
==118470==    by 0x408393: mg_if_recv_tcp_cb (mongoose.c:2509)
==118470== 
==118470== Syscall param socketcall.sendto(msg) points to unaddressable byte(s)
==118470==    at 0x54F799D: send (send.c:26)
==118470==    by 0x40A40E: mg_write_to_socket (mongoose.c:3316)
==118470==    by 0x40ACC2: mg_mgr_handle_conn (mongoose.c:3508)
==118470==    by 0x40B6C9: mg_socket_if_poll (mongoose.c:3694)
==118470==    by 0x407935: mg_mgr_poll (mongoose.c:2232)
==118470==    by 0x4022A6: main (mqtt_broker.c:43)
==118470==  Address 0x5ce0790 is 0 bytes inside a block of size 10 free'd
==118470==    at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470==    by 0x4C2FDB7: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470==    by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470==    by 0x404055: mbuf_append (mongoose.c:1096)
==118470==    by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470==    by 0x408158: mg_send (mongoose.c:2463)
==118470==    by 0x40ECA4: mg_mqtt_publish (mongoose.c:9841)
==118470==    by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470==    by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470==    by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470==    by 0x4071B6: mg_call (mongoose.c:2051)
==118470==    by 0x408362: mg_recv_common (mongoose.c:2505)
==118470==  Block was alloc'd at
==118470==    at 0x4C2FD5F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470==    by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470==    by 0x404055: mbuf_append (mongoose.c:1096)
==118470==    by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470==    by 0x408158: mg_send (mongoose.c:2463)
==118470==    by 0x40EC51: mg_mqtt_publish (mongoose.c:9836)
==118470==    by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470==    by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470==    by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470==    by 0x4071B6: mg_call (mongoose.c:2051)
==118470==    by 0x408362: mg_recv_common (mongoose.c:2505)
==118470==    by 0x408393: mg_if_recv_tcp_cb (mongoose.c:2509)

Timeline

2017-08-30 - Vendor Disclosure
2017-10-31 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.