uC-HTTP漏洞分析( CVE-2023-28379 CVE-2023-25181......)
2023-12-13 10:23:52 Author: xz.aliyun.com(查看原文) 阅读量:2 收藏

描述

uC-HTTP 服务器实现设计用于运行 µC/OS II 或 µC/OS III RTOS 内核的嵌入式系统。该 HTTP 服务器支持许多功能,包括持久连接、表单处理、分块传输编码、HTTP 标头字段处理、HTTP 查询字符串处理和动态内容

uC-HTTP v3.01.01中存在多个漏洞

下载链接:https://github.com/weston-embedded/uC-HTTP/archive/refs/tags/v3.01.01.zip

uC-LIB下载:https://github.com/weston-embedded/uC-LIB.git

漏洞分析

首先分析一下http-s_req.c中的HTTPsReq_Handle函数

这个函数就是一个http的请求处理

通过一个循环,如果对应的State正确则会对对应的请求字段进行解析

字段正常解析之后会依次循环解析下一个字段

void  HTTPsReq_Handle (HTTPs_INSTANCE  *p_instance,
                       HTTPs_CONN      *p_conn)
{
......

    done = DEF_NO;
    while (done != DEF_YES) {
        switch (p_conn->State) {

            case HTTPs_CONN_STATE_REQ_INIT:
                 HTTPs_STATS_INC(p_ctr_stats->Req_StatRxdCtr);
#if (HTTPs_CFG_HDR_RX_EN == DEF_ENABLED)
                 p_conn->HdrType = HTTPs_HDR_TYPE_REQ;
#endif
                 p_conn->State   = HTTPs_CONN_STATE_REQ_PARSE_METHOD;
                 break;

                                                                /* ---------------- PARSE REQ METHOD ------------------ */
            case HTTPs_CONN_STATE_REQ_PARSE_METHOD:
                 HTTPsReq_MethodParse(p_instance, p_conn, &err);
                 switch (err) {
                     case HTTPs_ERR_NONE:                                    /* If the Method parsing is successful...   */
                          p_conn->State = HTTPs_CONN_STATE_REQ_PARSE_URI;    /* ...go to the next step.                  */
                          break;

                     default:                                                /* If the Method parsing has failed...      */
                          HTTPs_ERR_INC(p_ctr_err->Req_ErrInvalidCtr);       /* ...generate an error...                  */
                          p_conn->ErrCode   = err;
                          p_conn->State     = HTTPs_CONN_STATE_ERR_INTERNAL;
                          p_conn->SockState = HTTPs_SOCK_STATE_NONE;
                          done              = DEF_YES;                       /* ...and exit the state machine.           */
                          break;
                 }
                 break;

                                                                /* ------------------ PARSE REQ URI ------------------- */
            case HTTPs_CONN_STATE_REQ_PARSE_URI:
                 is_query_str_found = HTTPsReq_URI_Parse(p_instance, p_conn, &err);
                 switch (err) {
                     case HTTPs_ERR_NONE:                            /* If the URI parsing is successful...              */
                          if (is_query_str_found == DEF_YES) {       /* ...check if query string need to be parse.       */
                              p_conn->State = HTTPs_CONN_STATE_REQ_PARSE_QUERY_STRING;
                          } else {
                              p_conn->State = HTTPs_CONN_STATE_REQ_PARSE_PROTOCOL_VERSION;
                          }
                          break;

                     case HTTPs_ERR_REQ_MORE_DATA_REQUIRED:          /* If more data is required to complete the...      */
                          p_conn->SockState = HTTPs_SOCK_STATE_RX;   /* ...URI Parsing, exit the state machine.          */
                          done                = DEF_YES;
                          break;

                     default:                                        /* If the URI parsing has failed...                 */
                          HTTPs_ERR_INC(p_ctr_err->Req_ErrInvalidCtr); /* ...generate an error...                        */
                          p_conn->SockState = HTTPs_SOCK_STATE_NONE;
                          p_conn->ErrCode   = err;
                          p_conn->State     = HTTPs_CONN_STATE_ERR_INTERNAL;
                          done              = DEF_YES;               /* ...and exit the state machine.                   */
                          break;
                 }
                 break;

                                                                /* --------------- PARSE REQ QUERY STR ---------------- */
            case HTTPs_CONN_STATE_REQ_PARSE_QUERY_STRING:
                 HTTPsReq_QueryStrParse(p_instance, p_conn, &err);
                 switch (err) {
                     case HTTPs_ERR_NONE:                            /* If the Query Str parsing is successful...        */
                                                                     /* ...go to the next step.                          */
                          p_conn->State = HTTPs_CONN_STATE_REQ_PARSE_PROTOCOL_VERSION;
                          break;

                     case HTTPs_ERR_REQ_MORE_DATA_REQUIRED:          /* If more data is required to complete the...      */
                          p_conn->SockState = HTTPs_SOCK_STATE_RX;   /* ...Query Str Parsing, exit the state machine.    */
                          done              = DEF_YES;
                          break;

                     default:                                        /* If the Query Str parsing has failed...           */
                          HTTPs_ERR_INC(p_ctr_err->Req_ErrInvalidCtr); /* ...generate an error...                        */
                          p_conn->SockState = HTTPs_SOCK_STATE_NONE;
                          p_conn->ErrCode   = err;
                          p_conn->State     = HTTPs_CONN_STATE_ERR_INTERNAL;
                          done              = DEF_YES;               /* ...and exit the state machine.                   */
                          break;
                 }
                 break;

                                                                /* -------------- PARSE REQ PROTOCOL VER -------------- */
            case HTTPs_CONN_STATE_REQ_PARSE_PROTOCOL_VERSION:
                 HTTPsReq_ProtocolVerParse(p_instance, p_conn, &err);
                    switch (err) {
                        case HTTPs_ERR_NONE:                            /* If the Protocol Ver parsing is successful... */
                                                                        /* ...go to the next step.                      */
                             p_conn->State        = HTTPs_CONN_STATE_REQ_PARSE_HDR;
                             p_conn->SockState    = HTTPs_SOCK_STATE_NONE;
                             DEF_BIT_CLR(p_conn->Flags, HTTPs_FLAG_RESP_LOCATION);
                             break;

                        case HTTPs_ERR_REQ_MORE_DATA_REQUIRED:          /* If more data is required to complete the...  */
                             p_conn->SockState = HTTPs_SOCK_STATE_RX;   /* ...Protocol Ver parsing, exit the state...   */
                             done              = DEF_YES;               /* ...machine.                                  */
                             break;

                        default:                                      /* If the Protocol Ver parsing has failed...      */
                             HTTPs_ERR_INC(p_ctr_err->Req_ErrInvalidCtr); /* ...generate an error...                    */
                             p_conn->SockState = HTTPs_SOCK_STATE_NONE;
                             p_conn->ErrCode   = err;
                             p_conn->State     = HTTPs_CONN_STATE_ERR_INTERNAL;
                             done                = DEF_YES;             /* ...and exit the state machine.               */
                             break;
                    }
                    break;

                                                                /* ------------------ PARSE REQ HDR ------------------- */
            case HTTPs_CONN_STATE_REQ_PARSE_HDR:
                 HTTPsReq_HdrParse(p_instance, p_conn, &err);   /* See Note #2.                                         */
                 switch (err) {
                     case HTTPs_ERR_NONE:                       /* If the Protocol Ver parsing is successful...         */
                                                                /* ...go to the next step.                              */
                          p_conn->State     = HTTPs_CONN_STATE_REQ_LINE_HDR_HOOK;
                          p_conn->SockState = HTTPs_SOCK_STATE_NONE;
                          HTTPs_STATS_INC(p_ctr_stats->Req_StatProcessedCtr);
                          break;

                     case HTTPs_ERR_REQ_MORE_DATA_REQUIRED:     /* If more data is required to complete the...          */
                          p_conn->SockState = HTTPs_SOCK_STATE_RX; /* ...Protocol Ver parsing, exit the state...        */
                          done              = DEF_YES;          /* ...machine.                                          */
                          break;

                     default:                                    /* If the Header parsing has failed...                  */
                         HTTPs_ERR_INC(p_ctr_err->Req_ErrInvalidCtr); /* ...generate an error...                         */
                         p_conn->ErrCode = err;
                         p_conn->State   = HTTPs_CONN_STATE_ERR_INTERNAL;
                         done            = DEF_YES;              /* ...and exit the state machine.                       */
                         break;
                 }
                 break;

                                                                /* --------------- CONN REQ EXT PROCESS --------------- */
            case HTTPs_CONN_STATE_REQ_LINE_HDR_HOOK:
                 hook_def = HTTPs_HOOK_DEFINED(p_cfg->HooksPtr, OnReqHook);
                 if (hook_def == DEF_YES) {
                     accepted = p_cfg->HooksPtr->OnReqHook(p_instance,
                                                           p_conn,
                                                           p_cfg->Hooks_CfgPtr);
                     if (accepted != DEF_YES) {
                                                                /* If the connection is not authorized ...              */
                         if (p_conn->StatusCode == HTTP_STATUS_OK) {
                             p_conn->StatusCode = HTTP_STATUS_UNAUTHORIZED;
                         }
                         DEF_BIT_SET(p_conn->Flags, HTTPs_FLAG_REQ_FLUSH);
                         p_conn->State = HTTPs_CONN_STATE_REQ_BODY_FLUSH_DATA;
                     }
                 }
                                                                /* Otherwise, receive the body.                         */
                 p_conn->State     = HTTPs_CONN_STATE_REQ_BODY_INIT;
                 done              = DEF_YES;                   /* ... exit the state machine.                          */
                 break;


            default:
                HTTPs_ERR_INC(p_ctr_err->Req_ErrStateUnkownCtr);
                p_conn->ErrCode = HTTPs_ERR_STATE_UNKNOWN;
                p_conn->State   = HTTPs_CONN_STATE_ERR_INTERNAL;
                done            = DEF_YES;
                break;
        }
    }
}

此次漏洞分析的主要漏洞都在字段解析中

oob write vulnerability

Method解析中存在长度重置错误引起后续的out-of-bounds write vulnerability

  • 首先在[1]中,p_request_method_start会获取整个数据包的第一个有效字符
  • 计算长度,将第一个有效字符之前的无效字符长度给去掉[2]
  • 接着利用Str_Char_N函数截取空格,此时将位置返回给p_request_method_end[3]
  • 然后取得p_request_method_end - p_request_method_start的长度,也就是method的长度[4]
  • p_conn->RxBufLenRem会减去上面的method长度[5]

漏洞点出在了[4]这里,这里的len直接被赋值成了method的长度,没有考虑到前面无效字符,导致p_conn->RxBufLenRem -= len这里多出了无效字符的长度,也就是有多少个无效字符,长度就多出多少

而[6]这里就是正确的计算

static  void  HTTPsReq_MethodParse (HTTPs_INSTANCE  *p_instance,
                                HTTPs_CONN      *p_conn,
                                HTTPs_ERR       *p_err)
{
...
    len = p_conn->RxBufLenRem;
...
                                                                /* Move the start ptr to the first meanningful char.    */
    p_request_method_start = HTTP_StrGraphSrchFirst(p_conn->RxBufPtr, len);             /* [1] p_request_method_start advances to the 
                                                                                            first character between 0x21 - 0x7e*/
    if (p_request_method_start == DEF_NULL) {
    *p_err = HTTPs_ERR_REQ_FORMAT_INVALID;
        return;
    }
    len -= p_request_method_start - p_conn->RxBufPtr ;                                  /* [2] len is correctly calculated */
                                                                /* Find the end of method string.                       */
    p_request_method_end =  Str_Char_N(p_request_method_start, len, ASCII_CHAR_SPACE);  /* [3] */
    if (p_request_method_end == DEF_NULL) {
    *p_err = HTTPs_ERR_REQ_FORMAT_INVALID;
        return;
    }
    len = p_request_method_end - p_request_method_start;                                /* [4] This is the bug */
...
    p_conn->RxBufLenRem -= len;                                                         /* [5] */
    p_conn->RxBufPtr     = p_request_method_end;                                        /* [6] */
}

在处理BodyForm的时候会将上面这个计算错误的长度赋值到len_str

然后HTTPsReq_BodyFormAppKeyValBlkAdd函数调用这个长度

static  CPU_BOOLEAN  HTTPsReq_BodyFormAppParse (HTTPs_INSTANCE  *p_instance,
                                                HTTPs_CONN      *p_conn,
                                                HTTPs_ERR       *p_err)
{
...
    while (done != DEF_YES) {
                                                                /* ----------- VALIDATE CUR KEY/VAL PAIRS ------------- */
        p_key_next = Str_Char_N(p_key_name,                     /* Srch beginning of next key/val pairs.                */
                                p_conn->RxBufLenRem,
                                ASCII_CHAR_AMPERSAND);

        if (p_key_next == DEF_NULL) {                           /* If next key/val pairs not found ...                  */
                                                                /* ... determine if all data are received or next ...   */
                                                                /* ... key/val pairs are missing.                       */
            len_content_rxd = p_conn->ReqContentLenRxd
                            + p_conn->RxBufLenRem;

            if (len_content_rxd < p_conn->ReqContentLen) {      /* If data are missing ...                              */
            *p_err = HTTPs_ERR_REQ_MORE_DATA_REQUIRED;       /* ... receive more data.                               */
                goto exit;

            } else {                                            /* If all data received ...                             */
                len_str = p_conn->RxBufLenRem;                  /* [1] 
                                                                /* ... last key/val pairs to parse.                     */
            }

        } else {                                                /* Next key/val pairs found ...                         */
            len_str = (p_key_next - p_key_name);                /* ... parse key/val pairs.                             */
        }

                                                                /* Add key-Value block to list.                         */
        result = HTTPsReq_BodyFormAppKeyValBlkAdd(p_instance,
                                                p_conn,
                                                p_key_name,
                                                len_str,
                                                p_err);         /* [2] */
...
}

HTTPsReq_BodyFormAppKeyValBlkAdd中调用了HTTPsReq_URL_EncodeStrParse函数,str_len是一个用户控制的长度,在[1]这里导致oob write null

static  CPU_BOOLEAN  HTTPsReq_URL_EncodeStrParse (HTTPs_INSTANCE  *p_instance,
                                                HTTPs_CONN      *p_conn,
                                                HTTPs_KEY_VAL   *p_key_val,
                                                CPU_BOOLEAN      from_query,
                                                CPU_CHAR        *p_str,
                                                CPU_SIZE_T       str_len)
{
...
                                                                /* Find separator "=".                                  */
    p_str_sep = Str_Char_N(p_str, str_len, ASCII_CHAR_EQUALS_SIGN);

    p_str[str_len] = ASCII_CHAR_NULL;                           /* [1] */
...
}

off by null vulnerability

header解析异常后引起的单字节NULL溢出

  • 如果上面的header解析没有符合要求的,则会进入default
  • 在[1]中得到解析失败的header长度len
  • 在[2]中header长度len会与p_cfg->HdrRxCfgPtr->DataLenMax最大长度进行比较,如果超出则return
  • 接着在最后补上一个NULL[4]

漏洞点出在了[2]这里,这里的len缺少了验证len等于p_cfg->HdrRxCfgPtr->DataLenMax长度的情况

如果len等于p_cfg->HdrRxCfgPtr->DataLenMax,则[3]这里会指向现有标准长度的最后一位,而[4]这里又会继续添加一个NULL,所以造成了一个单字节NULL溢出

File: http-s_req.c
1759:                     default:
1760: #if (HTTPs_CFG_HDR_RX_EN == DEF_ENABLED)
1761:                          if ((p_cfg->HdrRxCfgPtr != DEF_NULL) &&
1762:                              (p_cfg->HooksPtr    != DEF_NULL)) {
1763:                              keep = p_cfg->HooksPtr->OnReqHdrRxHook(p_instance,                       /* [0] */
1764:                                                                     p_conn,
1765:                                                                     p_cfg->Hooks_CfgPtr,
1766:                                                                     field);
...
1776:                              if (keep == DEF_YES) {
...
1789:                                 p_val = HTTPsReq_HdrParseValGet(p_field,
1790:                                                                 p_field_dict_entry->StrLen,
1791:                                                                 p_field_end,
1792:                                                                &len);                                 /* [1] */
...
1796:                                     if (len > p_cfg->HdrRxCfgPtr->DataLenMax) {                       /* [2] */
1797:                                         HTTPs_ERR_INC(p_ctr_errs->Req_ErrHdrDataLenInv);
1798:                                        *p_err = HTTPS_ERR_REQ_HDR_INVALID_VAL_LEN;
1799:                                         return;
1800:                                     }
...
1807:                                     p_str                 = (CPU_CHAR *)p_req_hdr_blk->ValPtr + len;  /* [3] */
1808:                                    *p_str                 =  ASCII_CHAR_NULL;                         /* [4] */
1809:                                     p_req_hdr_blk->ValLen =  len + 1;

off by null vulnerability

host解析后引起的单字节NULL溢出

  • [1]中确定HOST长度
  • [2]这里检查长度,最长为p_cfg->HostNameLenMax
  • 利用Str_Copy_N将HOST拷贝到p_conn->HostPtr
  • 最后补上一个NULL截断

如果[2]这里的长度刚好等于p_cfg->HostNameLenMax,此时[3]这里就会溢出一个单字节NULL

File: http-s_req.c
1713:                                                                 /* Find beginning of host string val.                   */
1714:                          p_val = HTTPsReq_HdrParseValGet(p_field,
1715:                                                          HTTP_STR_HDR_FIELD_HOST_LEN,
1716:                                                          p_field_end,
1717:                                                         &len);                        /* [1] */
1718: 
1719:                          len   = DEF_MIN(len, p_cfg->HostNameLenMax);                 /* [2] */
1720: 
1721:                                                                 /* Copy host name val in Conn struct.                   */
1722:                          (void)Str_Copy_N(p_conn->HostPtr, p_val, len);
1723:                                                                 /* Make sure to create a string.                        */
1724:                          p_conn->HostPtr[len] = ASCII_CHAR_NULL;                      /* [3] */

Heap Overflow Vulnerability

boundary解析后引起的堆溢出

在解析之后,可以看到len并没有进行任何的限制,直接利用[0]进行了Str_Copy_N到了p_conn->FormBoundaryPtr中,导致了堆溢出

File: http-s_req.c
1661:                                               p_val++;          /* Remove space before boundary val.                    */
1662:                                               p_val = HTTP_StrGraphSrchFirst(p_val,
1663:                                                                              len);
1664:                                               len   = p_field_end - p_val;
1665: 
1666:                                                                 /* Copy boundary val to Conn struct.                    */
1667:                                               Str_Copy_N(p_conn->FormBoundaryPtr,                         /* [0] */
1668:                                                          p_val,
1669:                                                          len);

Buffer Overflow Vulnerability

在解析Protocol时引起的整数下溢,最后导致Buffer Overflow Vulnerability

  • [1]这里会得到第一个有效字符
  • [2]这里找到\r\n判断最后一个字符,这样就得到了一个Protocol字段
  • 然后[3]这里会更新长度、位置

最终的漏洞点发生在[3]这里,整数下溢。如果前面存在无效字符,[1]这里跳过无效字符之后,len并没有更新长度,还是原始的RxBufLenRem

而后面搜索\r\n时使用这个len值就会越界访问,在缓冲区之外搜索,由于p_protocol_ver_end可能位于原始缓冲区之外,在更新RxBufLenRem时这个p_protocol_ver_end - p_conn->RxBufPtr + 2可能是一个负数,[3]这里减去这个负数后,RxBufLenRem发生整数下溢

/* HTTPsReq_ProtocolVerParse */
static  void  HTTPsReq_ProtocolVerParse (HTTPs_INSTANCE  *p_instance,
                                     HTTPs_CONN      *p_conn,
                                     HTTPs_ERR       *p_err)
{
...
    len = p_conn->RxBufLenRem;
...
                                                                /* Move the pointer to the next meaningful char.        */
    p_protocol_ver_start = HTTP_StrGraphSrchFirst(p_conn->RxBufPtr, len);       /* [1] p_protocol_ver_start advances to the first character 
                                                                                    between 0x21 - 0x7e */
    if (p_protocol_ver_start == DEF_NULL) {
    *p_err               = HTTPs_ERR_REQ_FORMAT_INVALID;
        return;
    }
                                                                /* Find the end of the request line.                    */
    p_protocol_ver_end = Str_Str_N(p_protocol_ver_start, STR_CR_LF, len);       /* [2] this will search outside of the buffer since len 
                                                                                    does not account for the previously skipped characters in [1] */
    if (p_protocol_ver_end == DEF_NULL) {                       /* If not found, check to get more data.                */
        if (p_conn->RxBufPtr != p_conn->BufPtr) {
        *p_err = HTTPs_ERR_REQ_MORE_DATA_REQUIRED;
        } else {
        *p_err = HTTPs_ERR_REQ_FORMAT_INVALID;
        }
        return;
    }
...
                                                                /* Update the RxBuf ptr.                                */
    p_conn->RxBufLenRem      -= (p_protocol_ver_end - p_conn->RxBufPtr) + 2;    /* [3] Since p_protocol_ver_end can be outside of the 
                                                                                    original buffer, this could lead to an integer underflow */
    p_conn->RxBufPtr          =  p_protocol_ver_end + 2;
...
}

发生整数下溢后,下溢的长度值p_conn->RxBufLenRem会变得很大,于Mem_Copy发生溢出,接下来,下溢的长度值再次用于计算将在后续调用接收[2] 时使用的指针,这会导致攻击者控制的数据被写入接收缓冲区的边界之外

CPU_BOOLEAN  HTTPsSock_ConnDataRx (HTTPs_INSTANCE  *p_instance,
                               HTTPs_CONN      *p_conn)
{
...
    if ((p_conn->RxBufLenRem > 0) &&
        (p_conn->RxBufPtr   != p_conn->BufPtr)) {               /* If data is still present in the rx buf.              */
                                                                /* Move rem data to the beginning of the rx buf.        */
            Mem_Copy(p_conn->BufPtr, p_conn->RxBufPtr, p_conn->RxBufLenRem);        //[1] the length used here is very large because of the underflow
    }
    p_buf   = p_conn->BufPtr + p_conn->RxBufLenRem;                             //[2] p_buf now points very far outside of the original buffer
    buf_len = p_conn->BufLen - p_conn->RxBufLenRem;
...
    rx_len = (CPU_INT16U)NetSock_RxDataFrom(        p_conn->SockID,
                                            (void *)p_buf,
                                                    buf_len,
                                                    NET_SOCK_FLAG_NO_BLOCK,
                                                &p_conn->ClientAddr,
                                                &addr_len_client,
                                                    DEF_NULL,
                                                    DEF_NULL,
                                                    DEF_NULL,
                                                &err);
...
}

至此漏洞分析完成,还有漏洞验证写poc,但是由于时间原因这里只写了漏洞分析

Reference

https://talosintelligence.com/vulnerability_reports/TALOS-2023-1725


文章来源: https://xz.aliyun.com/t/13174
如有侵权请联系:admin#unsafe.sh