From: Tom Peters (thopeter) Date: Thu, 21 Apr 2022 16:14:16 +0000 (+0000) Subject: Pull request #3360: mime: handle MIME header lines split between inspection sections... X-Git-Tag: 3.1.28.0~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3fa178836432f815cfce1f0dd0337d358e2e854b;p=thirdparty%2Fsnort3.git Pull request #3360: mime: handle MIME header lines split between inspection sections and improve folded header line processing Merge in SNORT/snort3 from ~KATHARVE/snort3:mime_header_parsing_copy to master Squashed commit of the following: commit 37fe918d4680d3c0528937889fa7a73f1a650db8 Author: Katura Harvey Date: Mon Mar 28 10:48:51 2022 -0400 mime: handle MIME header lines split between inspection sections and improve folded header line processing --- diff --git a/src/mime/file_mime_decode.cc b/src/mime/file_mime_decode.cc index 7d388777b..b360a9432 100644 --- a/src/mime/file_mime_decode.cc +++ b/src/mime/file_mime_decode.cc @@ -49,85 +49,95 @@ void MimeDecode::reset_decoded_bytes() void MimeDecode::clear_decode_state() { decode_type = DECODE_NONE; - if (decoder) - decoder->reset_decode_state(); + delete decoder; + decoder = nullptr; } -void MimeDecode::process_decode_type(const char* start, int length, bool cnt_xf, - MimeStats* mime_stats) +// Called if MIME_FLAG_FILE_ATTACH flag is set +void MimeDecode::finalize_decoder(MimeStats* mime_stats) { + // If this isn't the first body section, the decoder is already set if (decoder) - delete decoder; + return; - decoder = nullptr; + switch (decode_type) + { + case DECODE_B64: + if (mime_stats) + mime_stats->b64_attachments++; + decoder = new B64Decode(config->get_max_depth(config->get_b64_depth()), + config->get_b64_depth()); + break; + case DECODE_QP: + if (mime_stats) + mime_stats->qp_attachments++; + decoder = new QPDecode(config->get_max_depth(config->get_qp_depth()), + config->get_qp_depth()); + break; + case DECODE_UU: + if (mime_stats) + mime_stats->uu_attachments++; + decoder = new UUDecode(config->get_max_depth(config->get_uu_depth()), + config->get_uu_depth()); + break; + case DECODE_NONE: + // This occurs if we only saw Content-Type header and no Cont-Trans-Enc header + if (config->get_bitenc_depth() < 0) + break; + decode_type = DECODE_BITENC; + // Fallthrough + case DECODE_BITENC: + if (mime_stats) + mime_stats->bitenc_attachments++; + decoder = new BitDecode(config->get_max_depth(config->get_bitenc_depth()), + config->get_bitenc_depth()); + break; + } +} - if (cnt_xf) +// This may be called more than once due to folding. +void MimeDecode::process_decode_type(const char* start, int length) +{ + if (config->get_b64_depth() > -1) { - if (config->get_b64_depth() > -1) + const char* tmp = SnortStrcasestr(start, length, "base64"); + if ( tmp ) { - const char* tmp = SnortStrcasestr(start, length, "base64"); - - if ( tmp ) - { - decode_type = DECODE_B64; - if (mime_stats) - mime_stats->b64_attachments++; - decoder = new B64Decode(config->get_max_depth(config->get_b64_depth()), - config->get_b64_depth()); - file_decomp_reset(); - return; - } + decode_type = DECODE_B64; + return; } + } - if (config->get_qp_depth() > -1) + if (config->get_qp_depth() > -1) + { + const char* tmp = SnortStrcasestr(start, length, "quoted-printable"); + if ( tmp ) { - const char* tmp = SnortStrcasestr(start, length, "quoted-printable"); - - if ( tmp ) - { - decode_type = DECODE_QP; - if (mime_stats) - mime_stats->qp_attachments++; - decoder = new QPDecode(config->get_max_depth(config->get_qp_depth()), - config->get_qp_depth()); - file_decomp_reset(); - return; - } + decode_type = DECODE_QP; + return; } + } - if (config->get_uu_depth() > -1) + if (config->get_uu_depth() > -1) + { + const char* tmp = SnortStrcasestr(start, length, "uuencode"); + if ( tmp ) { - const char* tmp = SnortStrcasestr(start, length, "uuencode"); - - if ( tmp ) - { - decode_type = DECODE_UU; - if (mime_stats) - mime_stats->uu_attachments++; - decoder = new UUDecode(config->get_max_depth(config->get_uu_depth()), - config->get_uu_depth()); - file_decomp_reset(); - return; - } + decode_type = DECODE_UU; + return; } } - if (config->get_bitenc_depth() > -1) - { + // Default and "7bit" / "8bit" / "binary". This means the body is not encoded. This will not + // overwrite any of the three encoding types above + if (config->get_bitenc_depth() > -1 and decode_type == DECODE_NONE) decode_type = DECODE_BITENC; - if (mime_stats) - mime_stats->bitenc_attachments++; - decoder = new BitDecode(config->get_max_depth(config->get_bitenc_depth()), - config->get_bitenc_depth()); - file_decomp_reset(); - return; - } } DecodeResult MimeDecode::decode_data(const uint8_t* start, const uint8_t* end) { uint8_t* decode_buf = MimeDecodeContextData::get_decode_buf(); - return (decoder ? decoder->decode_data(start,end, decode_buf) : DECODE_SUCCESS); + return (decoder ? decoder->decode_data(start, end, decode_buf) : DECODE_SUCCESS); } int MimeDecode::get_detection_depth() diff --git a/src/mime/file_mime_decode.h b/src/mime/file_mime_decode.h index 3095bff96..709edd7f5 100644 --- a/src/mime/file_mime_decode.h +++ b/src/mime/file_mime_decode.h @@ -35,11 +35,10 @@ namespace snort enum DecodeType { DECODE_NONE = 0, + DECODE_BITENC, DECODE_B64, DECODE_QP, DECODE_UU, - DECODE_BITENC, - DECODE_ALL }; struct MimeStats @@ -61,8 +60,8 @@ public: ~MimeDecode(); // get the decode type from buffer - // bool cnt_xf: true if there is transfer encode defined, false otherwise - void process_decode_type(const char* start, int length, bool cnt_xf, MimeStats* mime_stats); + void process_decode_type(const char* start, int length); + void finalize_decoder(MimeStats* mime_stats); // Main function to decode file data DecodeResult decode_data(const uint8_t* start, const uint8_t* end); diff --git a/src/mime/file_mime_process.cc b/src/mime/file_mime_process.cc index 6e29cf22b..fa1bc609b 100644 --- a/src/mime/file_mime_process.cc +++ b/src/mime/file_mime_process.cc @@ -78,7 +78,7 @@ static THREAD_LOCAL MIMESearch* mime_current_search = nullptr; SearchTool* mime_hdr_search_mpse = nullptr; MIMESearch mime_hdr_search[HDR_LAST]; -static void get_mime_eol(const uint8_t* ptr, const uint8_t* end, +static bool get_mime_eol(const uint8_t* ptr, const uint8_t* end, const uint8_t** eol, const uint8_t** eolm) { const uint8_t* tmp_eol; @@ -89,14 +89,14 @@ static void get_mime_eol(const uint8_t* ptr, const uint8_t* end, if ( !ptr or !end ) { *eol = *eolm = end; - return; + return false; } tmp_eol = (const uint8_t*)memchr(ptr, '\n', end - ptr); if (tmp_eol == nullptr) { - tmp_eol = end; - tmp_eolm = end; + *eol = *eolm = end; + return false; } else { @@ -117,6 +117,7 @@ static void get_mime_eol(const uint8_t* ptr, const uint8_t* end, *eol = tmp_eol; *eolm = tmp_eolm; + return true; } /* @@ -141,7 +142,7 @@ static int search_str_found(void* id, void*, int index, void*, void*) return 1; } -void MimeSession::setup_decode(const char* data, int size, bool cnt_xf) +void MimeSession::setup_attachment_processing() { /* Check for Encoding Type */ if ( decode_conf && decode_conf->is_decoding_enabled()) @@ -150,12 +151,8 @@ void MimeSession::setup_decode(const char* data, int size, bool cnt_xf) { decode_state = new MimeDecode(decode_conf); } - if (decode_state != nullptr) - { - decode_state->process_decode_type(data, size, cnt_xf, mime_stats); state_flags |= MIME_FLAG_FILE_ATTACH; - } } } @@ -173,239 +170,239 @@ const uint8_t* MimeSession::process_mime_header(Packet* p, const uint8_t* ptr, { const uint8_t* eol = data_end_marker; const uint8_t* eolm = eol; - const uint8_t* colon; - const uint8_t* content_type_ptr = nullptr; - const uint8_t* cont_trans_enc = nullptr; - const uint8_t* cont_disp = nullptr; - int header_found; const uint8_t* start_hdr; start_hdr = ptr; + bool cont = true; + while (cont and ptr < data_end_marker) + { + bool found_end_marker = get_mime_eol(ptr, data_end_marker, &eol, &eolm); + // Save partial header lines until we have the full line + if (!found_end_marker) + { + if (partial_header) + { + // Single header line split into >2 sections, accumulate + // FIXIT-P: could allocate reasonable-size buf and only reallocate if needed + const uint32_t new_len = data_end_marker - ptr; + const uint32_t cum_len = partial_header_len + new_len; + uint8_t* tmp = new uint8_t[cum_len]; + memcpy(tmp, partial_header, partial_header_len); + memcpy(tmp + partial_header_len, ptr, new_len); + delete[] partial_header; + partial_header_len = cum_len; + partial_header = tmp; + } + else + { + partial_header_len = data_end_marker - ptr; + partial_header = new uint8_t[partial_header_len]; + memcpy(partial_header, ptr, partial_header_len); + } + return data_end_marker; + } + if (partial_header) + { + const uint32_t remainder_header_len = eol - ptr; + const uint32_t full_header_len = partial_header_len + remainder_header_len; + const uint8_t* full_header = new uint8_t[full_header_len]; + const uint8_t* head_ptr = full_header; + memcpy((void*)full_header, partial_header, partial_header_len); + memcpy((void*)(full_header + partial_header_len), ptr, remainder_header_len); + ptr = eol; + // Update eol and eolm to point to full_header buffer. eol points to byte after end + // marker and eolm points to start of the end marker - either \n or \r if CR precedes LF + eol = full_header + full_header_len; + if ((full_header_len > 1) && (*(eol - 2) == '\r')) + eolm = eol - 2; + else + eolm = eol - 1; - /* if we got a content-type in a previous packet and are - * folding, the boundary still needs to be checked for */ - if (state_flags & MIME_FLAG_IN_CONTENT_TYPE) - content_type_ptr = ptr; + cont = process_header_line(head_ptr, eol, eolm, start_hdr, p); - if (state_flags & MIME_FLAG_IN_CONT_TRANS_ENC) - cont_trans_enc = ptr; + delete[] partial_header; + partial_header = nullptr; + partial_header_len = 0; + delete[] full_header; + } + else + { + cont = process_header_line(ptr, eol, eolm, start_hdr, p); + } + state_flags |= MIME_FLAG_SEEN_HEADERS; + } + if (!cont) + { + data_state = STATE_DATA_BODY; + } + return ptr; +} - if (state_flags & MIME_FLAG_IN_CONT_DISP) - cont_disp = ptr; +static bool is_wsp(const uint8_t* c) +{ + return (*c == ' ' or *c == '\t'); +} - while (ptr < data_end_marker) +bool MimeSession::process_header_line(const uint8_t*& ptr, const uint8_t* eol, const uint8_t* eolm, + const uint8_t* start_hdr, Packet* p) +{ + const uint8_t* colon; + const uint8_t* header_value_ptr = nullptr; + int max_header_name_len = 0; + + int header_found; + /* got a line with only end of line marker should signify end of header */ + if (eolm == ptr) { - int max_header_name_len = 0; - get_mime_eol(ptr, data_end_marker, &eol, &eolm); + /* if no headers, treat as data */ + if ((ptr == start_hdr) and !(state_flags & MIME_FLAG_SEEN_HEADERS)) + ptr = eolm; + else + ptr = eol; + return false; + } - /* got a line with only end of line marker should signify end of header */ - if (eolm == ptr) - { - /* reset global header state values */ - state_flags &= - ~(MIME_FLAG_FOLDING | MIME_FLAG_IN_CONTENT_TYPE | MIME_FLAG_DATA_HEADER_CONT - | MIME_FLAG_IN_CONT_TRANS_ENC ); + // If we're not folding, process the header field name + if (!is_wsp(ptr)) + { + // Clear flags from last header line + state_flags &= ~(MIME_FLAG_IN_CONTENT_TYPE | MIME_FLAG_IN_CONT_TRANS_ENC); - data_state = STATE_DATA_BODY; + bool got_non_printable_in_header_name = false; - /* if no headers, treat as data */ - if (ptr == start_hdr) - return eolm; - else - return eol; + // If we're not folding and the first char is a whitespace (other than space (0x20) or htab + // (0x09) that means folding), it's not a header + if (isspace((int)*ptr) || *ptr == ':') + { + return false; } - /* if we're not folding, see if we should interpret line as a data line - * instead of a header line */ - if (!(state_flags & (MIME_FLAG_FOLDING | MIME_FLAG_DATA_HEADER_CONT))) + /* look for header field colon - if we're not folding then we need + * to find a header which will be all printables (except colon) + * followed by a colon */ + colon = ptr; + while ((colon < eolm) && (*colon != ':')) { - char got_non_printable_in_header_name = 0; - - /* if we're not folding and the first char is a space or - * colon, it's not a header */ - if (isspace((int)*ptr) || *ptr == ':') - { - data_state = STATE_DATA_BODY; - return ptr; - } - - /* look for header field colon - if we're not folding then we need - * to find a header which will be all printables (except colon) - * followed by a colon */ - colon = ptr; - while ((colon < eolm) && (*colon != ':')) - { - if (((int)*colon < 33) || ((int)*colon > 126)) - got_non_printable_in_header_name = 1; + if (((int)*colon < 33) || ((int)*colon > 126)) + got_non_printable_in_header_name = true; - colon++; - } - - /* Check for Exim 4.32 exploit where number of chars before colon is greater than 64 */ - int header_name_len = colon - ptr; + colon++; + } + + if (colon + 1 < eolm) + header_value_ptr = colon + 1; - if ((colon < eolm) && (header_name_len > MAX_HEADER_NAME_LEN)) - { - max_header_name_len = header_name_len; - } + /* Check for Exim 4.32 exploit where number of chars before colon is greater than 64 */ + int header_name_len = colon - ptr; - /* If the end on line marker and end of line are the same, assume - * header was truncated, so stay in data header state */ - if ((eolm != eol) && - ((colon == eolm) || got_non_printable_in_header_name)) - { - /* no colon or got spaces in header name (won't be interpreted as a header) - * assume we're in the body */ - state_flags &= - ~(MIME_FLAG_FOLDING | MIME_FLAG_IN_CONTENT_TYPE | MIME_FLAG_DATA_HEADER_CONT - |MIME_FLAG_IN_CONT_TRANS_ENC); + if ((colon < eolm) && (header_name_len > MAX_HEADER_NAME_LEN)) + { + max_header_name_len = header_name_len; + } - data_state = STATE_DATA_BODY; + /* If the end on line marker and end of line are the same, assume + * header was truncated, so stay in data header state */ + if ((eolm != eol) && + ((colon == eolm) || got_non_printable_in_header_name)) + { + /* no colon or got spaces in header name (won't be interpreted as a header) + * assume we're in the body */ + return false; + } - return ptr; - } + if (tolower((int)*ptr) == 'c') + { + mime_current_search = &mime_hdr_search[0]; + header_found = mime_hdr_search_mpse->find( + (const char*)ptr, eolm - ptr, search_str_found, true); - if (tolower((int)*ptr) == 'c') + /* Headers must start at beginning of line */ + if ((header_found > 0) && (mime_search_info.index == 0)) { - mime_current_search = &mime_hdr_search[0]; - header_found = mime_hdr_search_mpse->find( - (const char*)ptr, eolm - ptr, search_str_found, true); - - /* Headers must start at beginning of line */ - if ((header_found > 0) && (mime_search_info.index == 0)) + switch (mime_search_info.id) { - switch (mime_search_info.id) - { case HDR_CONTENT_TYPE: - content_type_ptr = ptr + mime_search_info.length; state_flags |= MIME_FLAG_IN_CONTENT_TYPE; break; case HDR_CONT_TRANS_ENC: - cont_trans_enc = ptr + mime_search_info.length; state_flags |= MIME_FLAG_IN_CONT_TRANS_ENC; break; case HDR_CONT_DISP: - cont_disp = ptr + mime_search_info.length; state_flags |= MIME_FLAG_IN_CONT_DISP; break; default: + assert(false); break; - } } } - else if (tolower((int)*ptr) == 'e') + } + else if (tolower((int)*ptr) == 'e') + { + if ((eolm - ptr) >= 9) { - if ((eolm - ptr) >= 9) + if (strncasecmp((const char*)ptr, "Encoding:", 9) == 0) { - if (strncasecmp((const char*)ptr, "Encoding:", 9) == 0) - { - cont_trans_enc = ptr + 9; - state_flags |= MIME_FLAG_IN_CONT_TRANS_ENC; - } + state_flags |= MIME_FLAG_IN_CONT_TRANS_ENC; } } } - else - { - state_flags &= ~MIME_FLAG_DATA_HEADER_CONT; - } + } + else if (!(state_flags & MIME_FLAG_SEEN_HEADERS)) + { + // If the line starts with folding whitespace but we haven't seen any headers yet, we can't + // be folding. Treat as body. + return false; + } + else + { + // If we are folding, we've already processed the header field and are somewhere in the + // header value + header_value_ptr = ptr; + } - int ret = handle_header_line(ptr, eol, max_header_name_len, p); - if (ret < 0) - return nullptr; - else if (ret > 0) - { - /* assume we guessed wrong and are in the body */ - data_state = STATE_DATA_BODY; - state_flags &= - ~(MIME_FLAG_FOLDING | MIME_FLAG_IN_CONTENT_TYPE | MIME_FLAG_DATA_HEADER_CONT - | MIME_FLAG_IN_CONT_TRANS_ENC | MIME_FLAG_IN_CONT_DISP); - return ptr; - } + int ret = handle_header_line(ptr, eol, max_header_name_len, p); + if (ret != 0) + return false; - /* check for folding - * if char on next line is a space and not \n or \r\n, we are folding */ - if ((eol < data_end_marker) && isspace((int)eol[0]) && (eol[0] != '\n')) - { - if ((eol < (data_end_marker - 1)) && (eol[0] != '\r') && (eol[1] != '\n')) - { - state_flags |= MIME_FLAG_FOLDING; - } - else - { - state_flags &= ~MIME_FLAG_FOLDING; - } - } - else if (eol != eolm) - { - state_flags &= ~MIME_FLAG_FOLDING; - } + // Now handle the header value + const uint32_t header_value_len = eolm - header_value_ptr; - /* check if we're in a content-type header and not folding. if so we have the whole - * header line/lines for content-type - see if we got a multipart with boundary - * we don't check each folded line, but wait until we have the complete header - * because boundary=BOUNDARY can be split across multiple folded lines before - * or after the '=' */ - if ((state_flags & - (MIME_FLAG_IN_CONTENT_TYPE | MIME_FLAG_FOLDING)) == MIME_FLAG_IN_CONTENT_TYPE) + if (state_flags & MIME_FLAG_IN_CONTENT_TYPE) + { + if (data_state == STATE_MIME_HEADER) { - if ((data_state == STATE_MIME_HEADER) && !(state_flags & - MIME_FLAG_FILE_ATTACH)) - { - setup_decode((const char*)content_type_ptr, (eolm - content_type_ptr), false); - } - - state_flags &= ~MIME_FLAG_IN_CONTENT_TYPE; - content_type_ptr = nullptr; + setup_attachment_processing(); } - else if ((state_flags & - (MIME_FLAG_IN_CONT_TRANS_ENC | MIME_FLAG_FOLDING)) == MIME_FLAG_IN_CONT_TRANS_ENC) + // We don't need the value, so it doesn't matter if we're folding + state_flags &= ~MIME_FLAG_IN_CONTENT_TYPE; + } + else if (state_flags & MIME_FLAG_IN_CONT_TRANS_ENC) + { + setup_attachment_processing(); + if (decode_state != nullptr and header_value_len > 0) { - setup_decode((const char*)cont_trans_enc, (eolm - cont_trans_enc), true); - - state_flags &= ~MIME_FLAG_IN_CONT_TRANS_ENC; - - cont_trans_enc = nullptr; + decode_state->process_decode_type((const char*)header_value_ptr, header_value_len); } - else if (((state_flags & - (MIME_FLAG_IN_CONT_DISP | MIME_FLAG_FOLDING)) == MIME_FLAG_IN_CONT_DISP) && - cont_disp) - { - bool disp_cont = (state_flags & MIME_FLAG_IN_CONT_DISP_CONT) ? true : false; - int len = extract_file_name((const char*&)cont_disp, eolm - cont_disp, &disp_cont); - - if (len > 0) - { - filename.assign((const char*)cont_disp, len); + // Don't clear the MIME_FLAG_IN_CONT_TRANS_ENC flag in case of folding + } + else if (state_flags & MIME_FLAG_IN_CONT_DISP) + { + int len = extract_file_name((const char*&)header_value_ptr, header_value_len); - if (log_config->log_filename && log_state) - { - log_state->log_file_name(cont_disp, len); - } - } - else - filename.clear(); + if (len > 0) + { + filename.assign((const char*)header_value_ptr, len); - if (disp_cont) + if (log_config->log_filename && log_state) { - state_flags |= MIME_FLAG_IN_CONT_DISP_CONT; + log_state->log_file_name(header_value_ptr, len); } - else - { - state_flags &= ~MIME_FLAG_IN_CONT_DISP; - state_flags &= ~MIME_FLAG_IN_CONT_DISP_CONT; - } - - cont_disp = nullptr; + state_flags &= ~MIME_FLAG_IN_CONT_DISP; } - - ptr = eol; - - if (ptr == data_end_marker) - state_flags |= MIME_FLAG_DATA_HEADER_CONT; } - return ptr; + ptr = eol; + return true; } /* Get the end of data body (excluding boundary)*/ @@ -466,6 +463,7 @@ const uint8_t* MimeSession::process_mime_body(const uint8_t* ptr, if (( attach_start < attach_end ) && decode_state) { + decode_state->finalize_decoder(mime_stats); if (decode_state->decode_data(attach_start, attach_end) == DECODE_FAIL ) { decode_alert(); @@ -634,10 +632,16 @@ const uint8_t* MimeSession::process_mime_data_paf( void MimeSession::reset_part_state() { state_flags = 0; + filename_state = CONT_DISP_FILENAME_PARAM_NAME; + delete[] partial_header; if (decode_state) + { decode_state->clear_decode_state(); + decode_state->file_decomp_reset(); + } // Clear MIME's file data to prepare for next file + filename.clear(); file_counter++; file_process_offset = 0; current_file_cache_file_id = 0; @@ -727,63 +731,72 @@ MailLogState* MimeSession::get_log_state() return log_state; } -int MimeSession::extract_file_name(const char*& start, int length, bool* disp_cont) +int MimeSession::extract_file_name(const char*& start, int length) { - const char* tmp = nullptr; + const char* tmp = start; const char* end = start+length; if (length <= 0) return -1; - if (!(*disp_cont)) + while (tmp < end) { - tmp = SnortStrcasestr(start, length, "filename"); - - if ( tmp == nullptr ) - return -1; - - tmp = tmp + 8; - while ( (tmp < end) && ((isspace(*tmp)) || (*tmp == '=') )) - { - tmp++; - } - } - else - tmp = start; - - if (tmp < end) - { - if (*tmp == '"' || (*disp_cont)) + switch (filename_state) { - if (*tmp == '"') + case CONT_DISP_FILENAME_PARAM_NAME: { - if (*disp_cont) - { - *disp_cont = false; - return (tmp - start); - } - tmp++; + tmp = SnortStrcasestr(start, length, "filename"); + if ( tmp == nullptr ) + return -1; + tmp = tmp + 8; + filename_state = CONT_DISP_FILENAME_PARAM_EQUALS; + break; } - start = tmp; - tmp = SnortStrnPbrk(start,(end - tmp),"\""); - if (tmp == nullptr ) + case CONT_DISP_FILENAME_PARAM_EQUALS: { - if ((end - tmp) > 0 ) + //skip past whitespace and '=' + if (isspace(*tmp) or (*tmp == '=')) + tmp++; + else if (*tmp == '"') { - tmp = end; - *disp_cont = true; + // Skip past the quote + tmp++; + filename_state = CONT_DISP_FILENAME_PARAM_VALUE_QUOTE; } else - return -1; + { + filename_state = CONT_DISP_FILENAME_PARAM_VALUE; + start = tmp; + } + break; } - else - *disp_cont = false; - end = tmp; - } - else - { - start = tmp; + case CONT_DISP_FILENAME_PARAM_VALUE_QUOTE: + start = tmp; + tmp = SnortStrnPbrk(start,(end - tmp),"\""); + if (tmp) + { + end = tmp; + return (end - start); + } + // Since we have the full header line and there can't be wrapping within a quoted + // string, getting here means the line is malformed. Treat like unquoted string + filename_state = CONT_DISP_FILENAME_PARAM_VALUE; + tmp = start; + break; + case CONT_DISP_FILENAME_PARAM_VALUE: + // Go until we get a ';' or whitespace + if ((*tmp == ';') or isspace(*tmp)) + { + end = tmp; + return (end - start); + } + tmp++; + break; } + } + if (filename_state == CONT_DISP_FILENAME_PARAM_VALUE) + { + // The filename is ended by the eol marker return (end - start); } return -1; @@ -829,8 +842,8 @@ MimeSession::MimeSession(Packet* p, const DecodeConfig* dconf, MailLogConfig* lc MimeSession::~MimeSession() { - if ( decode_state ) - delete(decode_state); + delete decode_state; + delete[] partial_header; } // File verdicts get cached with key (file_id, sip, dip). File_id is hash of filename if available. diff --git a/src/mime/file_mime_process.h b/src/mime/file_mime_process.h index 616a947e4..1ec2a1621 100644 --- a/src/mime/file_mime_process.h +++ b/src/mime/file_mime_process.h @@ -33,10 +33,9 @@ namespace snort { /* state flags */ -#define MIME_FLAG_FOLDING 0x00000001 #define MIME_FLAG_IN_CONTENT_TYPE 0x00000002 #define MIME_FLAG_GOT_BOUNDARY 0x00000004 -#define MIME_FLAG_DATA_HEADER_CONT 0x00000008 +#define MIME_FLAG_SEEN_HEADERS 0x00000008 #define MIME_FLAG_IN_CONT_TRANS_ENC 0x00000010 #define MIME_FLAG_FILE_ATTACH 0x00000020 #define MIME_FLAG_MULTIPLE_EMAIL_ATTACH 0x00000040 @@ -49,6 +48,14 @@ namespace snort #define STATE_DATA_BODY 2 /* Data body section of data state */ #define STATE_MIME_HEADER 3 /* MIME header section within data section */ +enum FilenameState +{ + CONT_DISP_FILENAME_PARAM_NAME, + CONT_DISP_FILENAME_PARAM_EQUALS, + CONT_DISP_FILENAME_PARAM_VALUE_QUOTE, + CONT_DISP_FILENAME_PARAM_VALUE +}; + /* Maximum length of header chars before colon, based on Exim 4.32 exploit */ #define MAX_HEADER_NAME_LEN 64 @@ -87,6 +94,7 @@ private: MailLogConfig* log_config = nullptr; MailLogState* log_state = nullptr; MimeStats* mime_stats = nullptr; + FilenameState filename_state = CONT_DISP_FILENAME_PARAM_NAME; std::string filename; bool continue_inspecting_file = true; // This counter is not an accurate count of files; used only for creating a unique mime_file_id @@ -112,12 +120,17 @@ private: virtual bool is_end_of_data(Flow*) { return false; } void reset_mime_state(); - void setup_decode(const char* data, int size, bool cnt_xf); + void setup_attachment_processing(); const uint8_t* process_mime_header(Packet*, const uint8_t* ptr, const uint8_t* data_end_marker); + bool process_header_line(const uint8_t*& ptr, const uint8_t* eol, const uint8_t* eolm, const + uint8_t* start_hdr, Packet* p); const uint8_t* process_mime_body(const uint8_t* ptr, const uint8_t* data_end,bool is_data_end); const uint8_t* process_mime_data_paf(Packet*, const uint8_t* start, const uint8_t* end, bool upload, FilePosition); - int extract_file_name(const char*& start, int length, bool* disp_cont); + int extract_file_name(const char*& start, int length); + + uint8_t* partial_header = nullptr; + uint32_t partial_header_len = 0; }; } #endif