Fix CVE-2022-48279
(cherry picked from commit af16f81ec3f6dc0cb2ce54c4e50105a6d801efd9)
This commit is contained in:
parent
8e8df1b856
commit
0820ca61ca
405
CVE-2022-48279.patch
Normal file
405
CVE-2022-48279.patch
Normal file
@ -0,0 +1,405 @@
|
||||
From 7a489bd07c66d3df19a320b4306e00c49716dbb0 Mon Sep 17 00:00:00 2001
|
||||
From: Martin Vierula <martin.vierula@trustwave.com>
|
||||
Date: Wed, 7 Sep 2022 11:09:47 -0700
|
||||
Subject: [PATCH] Multipart parsing fixes and new MULTIPART_PART_HEADERS
|
||||
collection
|
||||
|
||||
---
|
||||
CHANGES | 2 +
|
||||
apache2/msc_multipart.c | 148 ++++++++++++++------
|
||||
apache2/msc_multipart.h | 19 +++
|
||||
apache2/re_variables.c | 57 ++++++++
|
||||
tests/regression/misc/00-multipart-parser.t | 45 ++++++
|
||||
5 files changed, 230 insertions(+), 41 deletions(-)
|
||||
|
||||
diff --git a/apache2/msc_multipart.c b/apache2/msc_multipart.c
|
||||
index d087c863e..4128ab17e 100644
|
||||
--- a/apache2/msc_multipart.c
|
||||
+++ b/apache2/msc_multipart.c
|
||||
@@ -325,7 +325,14 @@ static int multipart_process_part_header(modsec_rec *msr, char **error_msg) {
|
||||
}
|
||||
|
||||
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. */
|
||||
|
||||
@@ -379,12 +386,28 @@ static int multipart_process_part_header(modsec_rec *msr, char **error_msg) {
|
||||
*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.",
|
||||
@@ -438,6 +461,8 @@ static int multipart_process_part_data(modsec_rec *msr, char **error_msg) {
|
||||
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') )
|
||||
@@ -680,10 +705,14 @@ static int multipart_process_boundary(modsec_rec *msr, int last_part, char **err
|
||||
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;
|
||||
@@ -983,6 +1012,19 @@ int multipart_complete(modsec_rec *msr, char **error_msg) {
|
||||
&& (*(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;
|
||||
@@ -1075,54 +1117,63 @@ int multipart_process_chunk(modsec_rec *msr, const char *buf,
|
||||
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;
|
||||
+ 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;
|
||||
+
|
||||
+ /* 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;
|
||||
+ }
|
||||
+ }
|
||||
|
||||
- /* Is this the final boundary? */
|
||||
- if ((*boundary_end == '-') && (*(boundary_end + 1)== '-')) {
|
||||
- is_final = 1;
|
||||
- boundary_end += 2;
|
||||
+ /* 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 (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) {
|
||||
+ msr->mpd->flag_error = 1;
|
||||
+ return -1;
|
||||
+ }
|
||||
|
||||
- /* 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 (is_final) {
|
||||
+ msr->mpd->is_complete = 1;
|
||||
+ }
|
||||
|
||||
- if (multipart_process_boundary(msr, (is_final ? 1 : 0), error_msg) < 0) {
|
||||
+ 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;
|
||||
@@ -1221,6 +1272,21 @@ int multipart_process_chunk(modsec_rec *msr, const char *buf,
|
||||
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)) {
|
||||
diff --git a/apache2/msc_multipart.h b/apache2/msc_multipart.h
|
||||
index a0f6a08dd..13db0658f 100644
|
||||
--- a/apache2/msc_multipart.h
|
||||
+++ b/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
|
||||
diff --git a/apache2/re_variables.c b/apache2/re_variables.c
|
||||
index 400738615..f3015acd9 100644
|
||||
--- a/apache2/re_variables.c
|
||||
+++ b/apache2/re_variables.c
|
||||
@@ -1394,6 +1394,52 @@ static int var_files_combined_size_generate(modsec_rec *msr, msre_var *var, msre
|
||||
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,
|
||||
@@ -2966,6 +3012,17 @@ void msre_engine_register_default_variables(msre_engine *engine) {
|
||||
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,
|
||||
"GEO",
|
||||
diff --git a/tests/regression/misc/00-multipart-parser.t b/tests/regression/misc/00-multipart-parser.t
|
||||
index 3c1f41b7d..e5ee4c13c 100644
|
||||
--- a/tests/regression/misc/00-multipart-parser.t
|
||||
+++ b/tests/regression/misc/00-multipart-parser.t
|
||||
@@ -1849,3 +1849,48 @@
|
||||
),
|
||||
},
|
||||
|
||||
+# 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--
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+},
|
||||
+
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
Name: mod_security
|
||||
Version: 2.9.5
|
||||
Release: 8
|
||||
Release: 9
|
||||
Summary: Security module for the Apache HTTP Server
|
||||
License: ASL 2.0
|
||||
URL: http://www.modsecurity.org/
|
||||
@ -22,6 +22,8 @@ Patch0003: modsecurity-2.9.5-Add-SecRequestBodyJsonDepthLimit-to-modsecurity.con
|
||||
Patch0004: modsecurity-2.9.5-Fix-memory-leak-that-occurs-on-JSON-parsing-error.patch
|
||||
Patch0005: modsecurity-2.9.5-Set-SecStatusEngine-Off-in-modsecurity.conf.patch
|
||||
Patch0006: modsecurity-2.9.5-Allow-no-key-single-value-JSON-body.patch
|
||||
# https://github.com/SpiderLabs/ModSecurity/commit/51a30d7b406af95c4143560d9753cf0b6d2151f5
|
||||
Patch0007: CVE-2022-48279.patch
|
||||
|
||||
Requires: httpd httpd-mmn = %{_httpd_mmn}
|
||||
BuildRequires: gcc make perl-generators httpd-devel yajl yajl-devel
|
||||
@ -104,6 +106,9 @@ install -m0755 mlogc/mlogc-batch-load.pl %{buildroot}%{_bindir}/mlogc-batch-load
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Tue Mar 26 2024 yaoxin <yao_xin001@hoperun.com> - 2.9.5-9
|
||||
- Fix CVE-2022-48279
|
||||
|
||||
* Mon Jan 9 2023 yaoguangzhong <yaoguangzhong@xfusion.com> - 2.9.5-8
|
||||
- backport allow no-key, single-value JSON body
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user