Package: release.debian.org
Severity: normal
Tags: bullseye
User: release.debian....@packages.debian.org
Usertags: pu
X-Debbugs-Cc: airw...@gmail.com, christian.fol...@netnea.com


[ Reason ]

modsecurity-crs has been released today [1]. It fixes a security issue,
here is the announcement:
--------
CVE-2022-39956 - Content-Type or Content-Transfer-Encoding MIME header fields
abuse

The OWASP ModSecurity Core Rule Set (CRS) is affected by a partial rule set
bypass for HTTP multipart requests by submitting a payload that uses a
character encoding scheme via the Content-Type or the deprecated
Content-Transfer-Encoding multipart MIME header fields that will not be
decoded and inspected by the web application firewall engine and the rule set.
The multipart payload will therefore bypass detection. A vulnerable backend
that supports these encoding schemes can potentially be exploited. The legacy
CRS versions 3.0.x and 3.1.x are affected, as well as the currently supported
versions 3.2.1 and 3.3.2. Integrators and users are advised to upgrade to
3.2.2 and 3.3.3 respectively.

Important: The mitigation against these vulnerabilities depends on the
installation of the latest ModSecurity version (v2.9.6/v3.0.8) or an updated
version with backports of the security fixes in these versions.
If you fail to update ModSecurity, the webserver / engine will refuse to start
with the following error message: "Error creating rule: Unknown variable:
MULTIPART_PART_HEADERS".
You can disable / remove the rule file REQUEST-922-MULTIPART-ATTACK.conf from
the release in order to allow you to run the latest CRS without a fix to
CVE-2022-39956, however we advise against this workaround.
------

As you may see in [1] a newer modsecurity is needed in other to apply
this fix. We, modsecurity packaging team, are preparing a patched
version of both modsecurity-apache (this bug report) and libmodsecurity3
(coming up). After that we'll upload the updated modsecurity-crs.


[ Impact ]
No support for the fixed version of modsecurity-crs.

[ Risks ]
Patch is not big. It has been tested. No risks should be expected.


[ Checklist ]
  [x] *all* changes are documented in the d/changelog|patch
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

[ Changes ]
Added patch to support new required variable "MULTIPART_PART_HEADERS".


Will wait for your OK before uploading.

Thanks.

[1] https://github.com/coreruleset/coreruleset/releases
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 16:04:02.000000000 
+0100
+++ modsecurity-apache-2.9.3/debian/changelog   2022-09-08 23:59:34.000000000 
+0200
@@ -1,3 +1,9 @@
+modsecurity-apache (2.9.3-3+deb11u2) bullseye; urgency=medium
+
+  * Added multipart_part_headers.patch
+
+ -- Ervin Hegedus <airw...@gmail.com>  Thu, 08 Sep 2022 23:59:34 +0200
+
 modsecurity-apache (2.9.3-3+deb11u1) bullseye-security; urgency=high
 
   * Added json_depth_limit.patch
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 01:00:00.000000000 +0100
+++ modsecurity-apache-2.9.3/debian/patches/multipart_part_headers.patch        
2022-09-08 23:59:34.000000000 +0200
@@ -0,0 +1,410 @@
+Description: This patch adds MULTIPART_PART_HEADERS variable
+ ModSecurity creates from now a new variable: MULTIPART_PART_HEADERS
+ This needs for some special CoreRuleSet rules, which has allocated CVE's.
+Author: Ervin Hegedus <airw...@gmail.com>
+
+---
+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"
++                
++                b...@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 
16:04:02.000000000 +0100
+++ modsecurity-apache-2.9.3/debian/patches/series      2022-09-08 
23:59:34.000000000 +0200
@@ -2,3 +2,4 @@
 improve_defaults.patch
 970833_fix.patch
 json_depth_limit.patch
+multipart_part_headers.patch

Reply via email to