Version in base suite: 2.9.3-3+deb11u1 Base version: modsecurity-apache_2.9.3-3+deb11u1 Target version: modsecurity-apache_2.9.3-3+deb11u2 Base file: /srv/ftp-master.debian.org/ftp/pool/main/m/modsecurity-apache/modsecurity-apache_2.9.3-3+deb11u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/m/modsecurity-apache/modsecurity-apache_2.9.3-3+deb11u2.dsc changelog | 15 patches/CVE-2023-24021_FILES_TMP_CONTENT.patch | 59 +++ patches/multipart_part_headers.patch | 411 +++++++++++++++++++++++++ patches/series | 2 4 files changed, 486 insertions(+), 1 deletion(-) diff -Nru modsecurity-apache-2.9.3/debian/changelog modsecurity-apache-2.9.3/debian/changelog --- modsecurity-apache-2.9.3/debian/changelog 2021-12-01 15:04:02.000000000 +0000 +++ modsecurity-apache-2.9.3/debian/changelog 2023-01-27 09:09:29.000000000 +0000 @@ -1,7 +1,20 @@ +modsecurity-apache (2.9.3-3+deb11u2) bullseye; urgency=medium + + * Non-maintainer upload by the LTS security team. + + [ Ervin Hegedus ] + * Fix CVE-2022-48279: Added multipart_part_headers.patch + + [ Tobias Frost ] + * Fix CVE-2023-24021, cherry-picking upstream commit. (Closes: #1029329) + * Fix typo in CVE number in 2.9.3-3+deb11u1 entry. (s/--/-/) + + -- Tobias Frost Fri, 27 Jan 2023 10:09:29 +0100 + modsecurity-apache (2.9.3-3+deb11u1) bullseye-security; urgency=high * Added json_depth_limit.patch - Fixes CVE--2021-42717 + Fixes CVE-2021-42717 -- Ervin Hegedus Wed, 01 Dec 2021 16:04:02 +0100 diff -Nru modsecurity-apache-2.9.3/debian/patches/CVE-2023-24021_FILES_TMP_CONTENT.patch modsecurity-apache-2.9.3/debian/patches/CVE-2023-24021_FILES_TMP_CONTENT.patch --- modsecurity-apache-2.9.3/debian/patches/CVE-2023-24021_FILES_TMP_CONTENT.patch 1970-01-01 00:00:00.000000000 +0000 +++ modsecurity-apache-2.9.3/debian/patches/CVE-2023-24021_FILES_TMP_CONTENT.patch 2023-01-27 09:09:29.000000000 +0000 @@ -0,0 +1,59 @@ +Description: CVE-2023-24021 FILES_TMP_CONTENT may sometimes lack complete content +Origin: https://github.com/SpiderLabs/ModSecurity/pull/2857 +Bug-Debian: https://bugs.debian.org/1029329 +From 4324f0ac59f8225aa44bc5034df60dbeccd1d334 Mon Sep 17 00:00:00 2001 +From: Martin Vierula +Date: Wed, 4 Jan 2023 11:34:11 -0800 +Subject: [PATCH] Fix: FILES_TMP_CONTENT may sometimes lack complete content +--- a/apache2/re_variables.c ++++ b/apache2/re_variables.c +@@ -1164,6 +1164,7 @@ + FILE *file; + size_t nread; + char *full_content = NULL; ++ char *full_content_tmp_ptr = NULL; + size_t total_lenght = 0; + msre_var *rvar = NULL; + +@@ -1173,19 +1174,23 @@ + continue; + } + ++ full_content = (char *)apr_pcalloc(mptmp, (sizeof(char)*parts[i]->length) + 1); ++ if (full_content == NULL) { ++ if (msr->txcfg->debuglog_level >= 3) { ++ msr_log(msr, 3, "Variable FILES_TMP_CONTENT will not be created, not " \ ++ "enough memory available."); ++ } ++ goto files_tmp_content_not_enough_mem; ++ } ++ full_content_tmp_ptr = full_content; ++ + while ((nread = fread(buf, 1, 1023, file)) > 0) + { +- total_lenght += nread; +- buf[nread] = '\0'; +- if (full_content == NULL) +- { +- full_content = apr_psprintf(mptmp, "%s", buf); +- } +- else +- { +- full_content = apr_psprintf(mptmp, "%s%s", full_content, buf); +- } ++ full_content_tmp_ptr = memcpy(full_content_tmp_ptr, buf, nread); ++ full_content_tmp_ptr += nread; ++ total_lenght += nread; + } ++ full_content_tmp_ptr[total_lenght] = '\0'; + fclose(file); + + rvar = apr_pmemdup(mptmp, var, sizeof(msre_var)); +@@ -1200,6 +1205,7 @@ + } + } + ++files_tmp_content_not_enough_mem: + return count; + } + diff -Nru modsecurity-apache-2.9.3/debian/patches/multipart_part_headers.patch modsecurity-apache-2.9.3/debian/patches/multipart_part_headers.patch --- modsecurity-apache-2.9.3/debian/patches/multipart_part_headers.patch 1970-01-01 00:00:00.000000000 +0000 +++ modsecurity-apache-2.9.3/debian/patches/multipart_part_headers.patch 2023-01-27 09:09:29.000000000 +0000 @@ -0,0 +1,411 @@ +Description: CVE-2022-48279: Multipart parsing fixes and new MULTIPART_PART_HEADERS collection. + ModSecurity creates from now a new variable: MULTIPART_PART_HEADERS + This needs for some special CoreRuleSet rules, which has allocated CVE's. +Origin: https://github.com/SpiderLabs/ModSecurity/pull/2797 +Author: Ervin Hegedus + +--- +Origin: other +Bug: not published yet +Last-Update: 2022-09-08 + +--- modsecurity-apache-2.9.3.orig/apache2/msc_multipart.c ++++ modsecurity-apache-2.9.3/apache2/msc_multipart.c +@@ -318,7 +318,14 @@ static int multipart_process_part_header + } + + msr->mpd->mpp_state = 1; ++ msr->mpd->mpp_substate_part_data_read = 0; + msr->mpd->mpp->last_header_name = NULL; ++ ++ /* Record the last part header line in the collection */ ++ if (msr->mpd->mpp->last_header_line != NULL) { ++ *(char **)apr_array_push(msr->mpd->mpp->header_lines) = msr->mpd->mpp->last_header_line; ++ msr_log(msr, 9, "Multipart: Added part header line \"%s\"", msr->mpd->mpp->last_header_line); ++ } + } else { + /* Header line. */ + +@@ -372,12 +379,28 @@ static int multipart_process_part_header + *error_msg = apr_psprintf(msr->mp, "Multipart: Part header too long."); + return -1; + } ++ if ((msr->mpd->mpp->last_header_line != NULL) && (msr->mpd->mpp->last_header_name != NULL) ++ && (new_value != NULL)) { ++ msr->mpd->mpp->last_header_line = apr_psprintf(msr->mp, ++ "%s: %s", msr->mpd->mpp->last_header_name, new_value); ++ } ++ + } else { + char *header_name, *header_value, *data; + + /* new header */ + ++ /* Record the most recently-seen part header line in the collection */ ++ if (msr->mpd->mpp->last_header_line != NULL) { ++ *(char **)apr_array_push(msr->mpd->mpp->header_lines) = msr->mpd->mpp->last_header_line; ++ msr_log(msr, 9, "Multipart: Added part header line \"%s\"", msr->mpd->mpp->last_header_line); ++ } ++ + data = msr->mpd->buf; ++ ++ msr->mpd->mpp->last_header_line = apr_pstrdup(msr->mp, data); ++ remove_lf_crlf_inplace(msr->mpd->mpp->last_header_line); ++ + while((*data != ':') && (*data != '\0')) data++; + if (*data == '\0') { + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid part header (colon missing): %s.", +@@ -431,6 +454,8 @@ static int multipart_process_part_data(m + if (error_msg == NULL) return -1; + *error_msg = NULL; + ++ msr->mpd->mpp_substate_part_data_read = 1; ++ + /* Preserve some bytes for later. */ + if ( ((MULTIPART_BUF_SIZE - msr->mpd->bufleft) >= 1) + && (*(p - 1) == '\n') ) +@@ -673,10 +698,14 @@ static int multipart_process_boundary(mo + if (msr->mpd->mpp == NULL) return -1; + msr->mpd->mpp->type = MULTIPART_FORMDATA; + msr->mpd->mpp_state = 0; ++ msr->mpd->mpp_substate_part_data_read = 0; + + msr->mpd->mpp->headers = apr_table_make(msr->mp, 10); + if (msr->mpd->mpp->headers == NULL) return -1; + msr->mpd->mpp->last_header_name = NULL; ++ msr->mpd->mpp->last_header_line = NULL; ++ msr->mpd->mpp->header_lines = apr_array_make(msr->mp, 2, sizeof(char *)); ++ if (msr->mpd->mpp->header_lines == NULL) return -1; + + msr->mpd->reserve[0] = 0; + msr->mpd->reserve[1] = 0; +@@ -976,6 +1005,19 @@ int multipart_complete(modsec_rec *msr, + && (*(msr->mpd->buf + 2 + strlen(msr->mpd->boundary)) == '-') + && (*(msr->mpd->buf + 2 + strlen(msr->mpd->boundary) + 1) == '-') ) + { ++ if ((msr->mpd->crlf_state_buf_end == 2) && (msr->mpd->flag_lf_line != 1)) { ++ msr->mpd->flag_lf_line = 1; ++ if (msr->mpd->flag_crlf_line) { ++ msr_log(msr, 4, "Multipart: Warning: mixed line endings used (CRLF/LF)."); ++ } else { ++ msr_log(msr, 4, "Multipart: Warning: incorrect line endings used (LF)."); ++ } ++ } ++ if (msr->mpd->mpp_substate_part_data_read == 0) { ++ /* it looks like the final boundary, but it's where part data should begin */ ++ msr->mpd->flag_invalid_part = 1; ++ msr_log(msr, 4, "Multipart: Warning: Invalid part (data contains final boundary)"); ++ } + /* Looks like the final boundary - process it. */ + if (multipart_process_boundary(msr, 1 /* final */, error_msg) < 0) { + msr->mpd->flag_error = 1; +@@ -1068,54 +1110,62 @@ int multipart_process_chunk(modsec_rec * + if ( (strlen(msr->mpd->buf) >= strlen(msr->mpd->boundary) + 2) + && (strncmp(msr->mpd->buf + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) ) + { +- char *boundary_end = msr->mpd->buf + 2 + strlen(msr->mpd->boundary); +- int is_final = 0; +- +- /* Is this the final boundary? */ +- if ((*boundary_end == '-') && (*(boundary_end + 1)== '-')) { +- is_final = 1; +- boundary_end += 2; +- +- if (msr->mpd->is_complete != 0) { +- msr->mpd->flag_error = 1; +- *error_msg = apr_psprintf(msr->mp, +- "Multipart: Invalid boundary (final duplicate)."); +- return -1; +- } ++ if (msr->mpd->crlf_state_buf_end == 2) { ++ msr->mpd->flag_lf_line = 1; + } ++ if ((msr->mpd->mpp_substate_part_data_read == 0) && (msr->mpd->boundary_count > 0)) { ++ /* string matches our boundary, but it's where part data should begin */ ++ msr->mpd->flag_invalid_part = 1; ++ msr_log(msr, 4, "Multipart: Warning: Invalid part (data contains boundary)"); ++ } else { ++ char *boundary_end = msr->mpd->buf + 2 + strlen(msr->mpd->boundary); ++ int is_final = 0; + +- /* Allow for CRLF and LF line endings. */ +- if ( ( (*boundary_end == '\r') +- && (*(boundary_end + 1) == '\n') +- && (*(boundary_end + 2) == '\0') ) +- || ( (*boundary_end == '\n') +- && (*(boundary_end + 1) == '\0') ) ) +- { +- if (*boundary_end == '\n') { +- msr->mpd->flag_lf_line = 1; +- } else { +- msr->mpd->flag_crlf_line = 1; ++ /* Is this the final boundary? */ ++ if ((*boundary_end == '-') && (*(boundary_end + 1)== '-')) { ++ is_final = 1; ++ boundary_end += 2; ++ ++ if (msr->mpd->is_complete != 0) { ++ msr->mpd->flag_error = 1; ++ *error_msg = apr_psprintf(msr->mp, ++ "Multipart: Invalid boundary (final duplicate)."); ++ return -1; ++ } + } + +- if (multipart_process_boundary(msr, (is_final ? 1 : 0), error_msg) < 0) { ++ /* Allow for CRLF and LF line endings. */ ++ if ( ( (*boundary_end == '\r') ++ && (*(boundary_end + 1) == '\n') ++ && (*(boundary_end + 2) == '\0') ) ++ || ( (*boundary_end == '\n') ++ && (*(boundary_end + 1) == '\0') ) ) ++ { ++ if (*boundary_end == '\n') { ++ msr->mpd->flag_lf_line = 1; ++ } else { ++ msr->mpd->flag_crlf_line = 1; ++ } ++ if (multipart_process_boundary(msr, (is_final ? 1 : 0), error_msg) < 0) { ++ msr->mpd->flag_error = 1; ++ return -1; ++ } ++ ++ if (is_final) { ++ msr->mpd->is_complete = 1; ++ } ++ ++ processed_as_boundary = 1; ++ msr->mpd->boundary_count++; ++ } ++ else { ++ /* error */ + msr->mpd->flag_error = 1; ++ *error_msg = apr_psprintf(msr->mp, ++ "Multipart: Invalid boundary: %s", ++ log_escape_nq(msr->mp, msr->mpd->buf)); + return -1; + } +- +- if (is_final) { +- msr->mpd->is_complete = 1; +- } +- +- processed_as_boundary = 1; +- msr->mpd->boundary_count++; +- } +- else { +- /* error */ +- msr->mpd->flag_error = 1; +- *error_msg = apr_psprintf(msr->mp, +- "Multipart: Invalid boundary: %s", +- log_escape_nq(msr->mp, msr->mpd->buf)); +- return -1; + } + } else { /* It looks like a boundary but we couldn't match it. */ + char *p = NULL; +@@ -1214,6 +1264,21 @@ int multipart_process_chunk(modsec_rec * + msr->mpd->bufptr = msr->mpd->buf; + msr->mpd->bufleft = MULTIPART_BUF_SIZE; + msr->mpd->buf_contains_line = (c == 0x0a) ? 1 : 0; ++ ++ if (c == 0x0a) { ++ if (msr->mpd->crlf_state == 1) { ++ msr->mpd->crlf_state = 3; ++ } else { ++ msr->mpd->crlf_state = 2; ++ } ++ } ++ msr->mpd->crlf_state_buf_end = msr->mpd->crlf_state; ++ } ++ ++ if (c == 0x0d) { ++ msr->mpd->crlf_state = 1; ++ } else if (c != 0x0a) { ++ msr->mpd->crlf_state = 0; + } + + if ((msr->mpd->is_complete) && (inleft != 0)) { +--- modsecurity-apache-2.9.3.orig/apache2/msc_multipart.h ++++ modsecurity-apache-2.9.3/apache2/msc_multipart.h +@@ -55,6 +55,8 @@ struct multipart_part { + + char *last_header_name; + apr_table_t *headers; ++ char *last_header_line; ++ apr_array_header_t *header_lines; + + unsigned int offset; + unsigned int length; +@@ -81,6 +83,15 @@ struct multipart_data { + char *bufptr; + int bufleft; + ++ /* line ending status seen immediately before current position. ++ * 0 = neither LF nor CR; 1 = prev char CR; 2 = prev char LF alone; ++ * 3 = prev two chars were CRLF ++ */ ++ int crlf_state; ++ ++ /* crlf_state at end of previous buffer */ ++ int crlf_state_buf_end; ++ + unsigned int buf_offset; + + /* pointer that keeps track of a part while +@@ -94,6 +105,14 @@ struct multipart_data { + */ + int mpp_state; + ++ /* part parsing substate; if mpp_state is 1 (collecting ++ * data), then for this variable: ++ * 0 means we have not yet read any data between the ++ * post-headers blank line and the next boundary ++ * 1 means we have read at some data after that blank line ++ */ ++ int mpp_substate_part_data_read; ++ + /* because of the way this parsing algorithm + * works we hold back the last two bytes of + * each data chunk so that we can discard it +--- modsecurity-apache-2.9.3.orig/apache2/re_variables.c ++++ modsecurity-apache-2.9.3/apache2/re_variables.c +@@ -1394,6 +1394,52 @@ static int var_files_combined_size_gener + return 1; + } + ++/* MULTIPART_PART_HEADERS */ ++ ++static int var_multipart_part_headers_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, ++ apr_table_t *vartab, apr_pool_t *mptmp) ++{ ++ multipart_part **parts = NULL; ++ int i, j, count = 0; ++ ++ if (msr->mpd == NULL) return 0; ++ ++ parts = (multipart_part **)msr->mpd->parts->elts; ++ for(i = 0; i < msr->mpd->parts->nelts; i++) { ++ int match = 0; ++ ++ /* Figure out if we want to include this variable. */ ++ if (var->param == NULL) match = 1; ++ else { ++ if (var->param_data != NULL) { /* Regex. */ ++ char *my_error_msg = NULL; ++ if (!(msc_regexec((msc_regex_t *)var->param_data, parts[i]->name, ++ strlen(parts[i]->name), &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1; ++ } else { /* Simple comparison. */ ++ if (strcasecmp(parts[i]->name, var->param) == 0) match = 1; ++ } ++ } ++ ++ /* If we had a match add this argument to the collection. */ ++ if (match) { ++ for (j = 0; j < parts[i]->header_lines->nelts; j++) { ++ char *header_line = ((char **)parts[i]->header_lines->elts)[j]; ++ msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var)); ++ ++ rvar->value = header_line; ++ rvar->value_len = strlen(rvar->value); ++ rvar->name = apr_psprintf(mptmp, "MULTIPART_PART_HEADERS:%s", ++ log_escape_nq(mptmp, parts[i]->name)); ++ apr_table_addn(vartab, rvar->name, (void *)rvar); ++ ++ count++; ++ } ++ } ++ } ++ ++ return count; ++} ++ + /* MODSEC_BUILD */ + + static int var_modsec_build_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, +@@ -2965,6 +3011,17 @@ void msre_engine_register_default_variab + VAR_CACHE, + PHASE_REQUEST_BODY + ); ++ ++ /* MULTIPART_PART_HEADERS */ ++ msre_engine_variable_register(engine, ++ "MULTIPART_PART_HEADERS", ++ VAR_LIST, ++ 0, 1, ++ var_generic_list_validate, ++ var_multipart_part_headers_generate, ++ VAR_CACHE, ++ PHASE_REQUEST_BODY ++ ); + + /* GEO */ + msre_engine_variable_register(engine, +--- modsecurity-apache-2.9.3.orig/modsecurity.conf-recommended ++++ modsecurity-apache-2.9.3/modsecurity.conf-recommended +@@ -19,14 +19,14 @@ SecRequestBodyAccess On + # Enable XML request body parser. + # Initiate XML Processor in case of xml content-type + # +-SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\+|/)|text/)xml" \ ++SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \ + "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + + # Enable JSON request body parser. + # Initiate JSON Processor in case of JSON content-type; change accordingly + # if your application does not use 'application/json' + # +-SecRule REQUEST_HEADERS:Content-Type "application/json" \ ++SecRule REQUEST_HEADERS:Content-Type "^application/json" \ + "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + + # Maximum request body size we will accept for buffering. If you support +--- modsecurity-apache-2.9.3.orig/tests/regression/misc/00-multipart-parser.t ++++ modsecurity-apache-2.9.3/tests/regression/misc/00-multipart-parser.t +@@ -1811,3 +1811,47 @@ + ), + }, + ++# part headers ++{ ++ type => "misc", ++ comment => "multipart parser (part headers)", ++ conf => qq( ++ SecRuleEngine On ++ SecDebugLog $ENV{DEBUG_LOG} ++ SecDebugLogLevel 9 ++ SecRequestBodyAccess On ++ SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,status:400,id:500168" ++ SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,status:400,id:500169" ++ SecRule MULTIPART_PART_HEADERS:image "\@rx content-type:.*jpeg" "phase:2,deny,status:403,id:500170,t:lowercase" ++ ), ++ match_log => { ++ debug => [ qr/500170.*against MULTIPART_PART_HEADERS:image.*Rule returned 1./s, 1 ], ++ }, ++ match_response => { ++ status => qr/^403$/, ++ }, ++ request => new HTTP::Request( ++ POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", ++ [ ++ "Content-Type" => q(multipart/form-data; boundary=0000), ++ ], ++ normalize_raw_request_data( ++ q( ++ --0000 ++ Content-Disposition: form-data; name="username" ++ ++ Bill ++ --0000 ++ Content-Disposition: form-data; name="email" ++ ++ bill@fakesite.com ++ --0000 ++ Content-Disposition: form-data; name="image"; filename="image.jpg" ++ Content-Type: image/jpeg ++ ++ BINARYDATA ++ --0000-- ++ ), ++ ), ++ ), ++}, diff -Nru modsecurity-apache-2.9.3/debian/patches/series modsecurity-apache-2.9.3/debian/patches/series --- modsecurity-apache-2.9.3/debian/patches/series 2021-12-01 15:04:02.000000000 +0000 +++ modsecurity-apache-2.9.3/debian/patches/series 2023-01-27 09:09:29.000000000 +0000 @@ -2,3 +2,5 @@ improve_defaults.patch 970833_fix.patch json_depth_limit.patch +multipart_part_headers.patch +CVE-2023-24021_FILES_TMP_CONTENT.patch