AHA! has discovered an issue with Mongoose from Cesanta, and is publishing this disclosure in accordance with AHA!’s standard disclosure policy today, on Tuesday, August 8, 2023. CVE-2023-2905 has been assigned to this issue.
Any questions about this disclosure should be directed to [email protected].
Due to a failure in validating the length of a provided MQTT_CMD_PUBLISH
parsed message with a variable length header, the dual-licensed Cesanta Mongoose embeddable web server version 7.10 is susceptible to a heap-based buffer overflow vulnerability in the default configuration. CVE-2023-2905 appears to be an instance of CWE-122. Version 7.9 and prior does not appear to be vulnerable.
Below is the code used to parse mqtt messages with lines 365-370 being used to ensure that the variable length portion of the buffer is not larger than 4 bytes and breaking out of the loop if lc & 0x80
evaluates to false (based on the MSB).
src/mqtt.c
352 int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
353 struct mg_mqtt_message *m) {
354 uint8_t lc = 0, *p, *end;
355 uint32_t n = 0, len_len = 0;
356
357 memset(m, 0, sizeof(*m));
358 m->dgram.ptr = (char *) buf;
359 if (len < 2) return MQTT_INCOMPLETE;
360 m->cmd = (uint8_t) (buf[0] >> 4);
361 m->qos = (buf[0] >> 1) & 3;
362
363 n = len_len = 0;
364 p = (uint8_t *) buf + 1;
365 while ((size_t) (p - buf) < len) {
366 lc = *((uint8_t *) p++);
367 n += (uint32_t) ((lc & 0x7f) << 7 * len_len);
368 len_len++;
369 if (!(lc & 0x80)) break;
370 if (len_len >= 4) return MQTT_MALFORMED;
371 }
Then lines 393-406 show the code path to the vulnerable function decode_variable_length
, containing the remaining to be read mqtt buffer after additions to the p
pointer on lines 394, 397, 402.
src/mqtt.c
393 case MQTT_CMD_PUBLISH: {
394 if (p + 2 > end) return MQTT_MALFORMED;
395 m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
396 m->topic.ptr = (char *) p + 2;
397 p += 2 + m->topic.len;
398 if (p > end) return MQTT_MALFORMED;
399 if (m->qos > 0) {
400 if (p + 2 > end) return MQTT_MALFORMED;
401 m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
402 p += 2;
403 }
404 if (p > end) return MQTT_MALFORMED;
405 if (version == 5 && p + 2 < end) {
406 m->props_size = decode_variable_length((char *) p, &len_len);
The below shows the buffer from the crash file showing a 10 byte mqtt message with the 8th byte having the MSB set.
Thread 1 "fuzzer" hit Breakpoint 7, LLVMFuzzerTestOneInput (data=0xffffb4500790 "5\b", size=10) at test/fuzz.c:38
38 mg_mqtt_parse(data, size, 5, &mm);
(gdb) x/12bx data
0xffffb4500790: 0x35 0x08 0x00 0x01 0x00 0x00 0x5a 0x8a
0xffffb4500798: 0xff 0xff 0x00 0x00
This results in a variable length header being detected but only containing 3 bytes, as can be seen below
Thread 1 "fuzzer" hit Breakpoint 8, mg_mqtt_parse (buf=<optimized out>, len=<optimized out>, version=<optimized out>, m=<optimized out>) at src/mqtt.c:415
415 m->props_size = decode_variable_length((char *) p, &len_len);
(gdb) x/10x p
0xffffb0b00797: 0x8a 0xff 0xff 0x00 0x00 0x00 0x00 0x00
0xffffb0b0079f: 0x00 0x02
And finally, the decode_variable_length
function code, which contains no bounds checks on the size of the buffer being read.
src/mqtt.c
92 static uint32_t decode_variable_length(const char *buf,
93 uint32_t *bytes_consumed) {
94 uint32_t value = 0, multiplier = 1, offset;
95
96 for (offset = 0; offset < 4; offset++) {
97 uint8_t encoded_byte = ((uint8_t *) buf)[offset];
98 value += (encoded_byte & 0x7F) * multiplier;
99 multiplier *= 128;
100
101 if (!(encoded_byte & 0x80)) break;
102 }
103
104 if (bytes_consumed != NULL) *bytes_consumed = offset + 1;
105
106 return value;
107 }
This allows for a read primitive of 1-3 bytes from a heap overflow.
A base64 encoded blob of the payload needed to trigger the vulnerability can be found below.
NQgAAQAAWor//w==
Cesanta Mongoose is an embedded webserver library commonly used to add web server functionality to devices with vendor provided integrations. According to the project’s README, “Mongoose is used by hundreds of businesses, from Fortune500 giants like Siemens, Schneider Electric, Broadcom, Bosch, Google, Samsung, Qualcomm, Caterpillar to the small businesses.” Because it is an embedded software, often implemented in the context of IOT/ICS (Internet of Things / Industrial Control Systems), its presence may be difficult for affected end-users to audit for.
If an attacker has the capability of sending an MQTT message to the webserver (which would require direct network access to the network hosting the device), that attacker could use the above primitive in a chain to obtain remote code execution on an embedded device. The level of access would depend on the parent process that runs Mongoose. In most IOT/ICS contexts, this would typically result in the total compromise of the webserver, and, if it is not running in a privilege-restricted context, total compromise of the hosting device.
PR2274 fixes the issue on the main branch of the open source repository; the vendor has further advised that Mongoose Version 7.11 available for downstream users and implementors addresses the issue.
This issue is being disclosed through the AHA! CNA and is credited to: zenofex and WanderingGlitch
master
branch with PR2274.