mirror of
https://github.com/owasp-modsecurity/ModSecurity.git
synced 2026-01-13 15:07:10 +03:00
Fixed parsing quoted strings in multipart Content-Disposition headers.
This commit is contained in:
9
CHANGES
9
CHANGES
@@ -1,8 +1,13 @@
|
|||||||
04 Nov 2009 - 2.5.11
|
04 Nov 2009 - 2.5.11
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
* Cleanup persistence database locking code which could deadlock
|
* Added a new multipart flag, MULTIPART_INVALID_QUOTING, which will be
|
||||||
under high stress.
|
set true if any invalid quoting is found during multipart parsing.
|
||||||
|
|
||||||
|
* Fixed parsing quoted strings in multipart Content-Disposition headers.
|
||||||
|
Discovered by Stefan Esser.
|
||||||
|
|
||||||
|
* Cleanup persistence database locking code.
|
||||||
|
|
||||||
* Added warning during configure if libcurl is found linked against
|
* Added warning during configure if libcurl is found linked against
|
||||||
gnutls for SSL. The openssl lib is recommended as gnutls has
|
gnutls for SSL. The openssl lib is recommended as gnutls has
|
||||||
|
|||||||
@@ -490,7 +490,7 @@ void init_directory_config(directory_config *dcfg) {
|
|||||||
if (dcfg->is_enabled == NOT_SET) dcfg->is_enabled = 0;
|
if (dcfg->is_enabled == NOT_SET) dcfg->is_enabled = 0;
|
||||||
|
|
||||||
if (dcfg->reqbody_access == NOT_SET) dcfg->reqbody_access = 0;
|
if (dcfg->reqbody_access == NOT_SET) dcfg->reqbody_access = 0;
|
||||||
if (dcfg->reqbody_buffering == NOT_SET) dcfg->reqbody_buffering = 0;
|
if (dcfg->reqbody_buffering == NOT_SET) dcfg->reqbody_buffering = REQUEST_BODY_FORCEBUF_OFF;
|
||||||
if (dcfg->reqbody_inmemory_limit == NOT_SET)
|
if (dcfg->reqbody_inmemory_limit == NOT_SET)
|
||||||
dcfg->reqbody_inmemory_limit = REQUEST_BODY_DEFAULT_INMEMORY_LIMIT;
|
dcfg->reqbody_inmemory_limit = REQUEST_BODY_DEFAULT_INMEMORY_LIMIT;
|
||||||
if (dcfg->reqbody_limit == NOT_SET) dcfg->reqbody_limit = REQUEST_BODY_DEFAULT_LIMIT;
|
if (dcfg->reqbody_limit == NOT_SET) dcfg->reqbody_limit = REQUEST_BODY_DEFAULT_LIMIT;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
rm -rf autom4te.cache
|
||||||
|
|
||||||
#automake --add-missing --copy
|
#automake --add-missing --copy
|
||||||
autoreconf --install
|
autoreconf --install
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ apr_status_t modsecurity_tx_init(modsec_rec *msr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Check if we are forcing buffering, then use memory only. */
|
/* Check if we are forcing buffering, then use memory only. */
|
||||||
if (msr->txcfg->reqbody_buffering) {
|
if (msr->txcfg->reqbody_buffering != REQUEST_BODY_FORCEBUF_OFF) {
|
||||||
msr->msc_reqbody_storage = MSC_REQBODY_MEMORY;
|
msr->msc_reqbody_storage = MSC_REQBODY_MEMORY;
|
||||||
msr->msc_reqbody_spilltodisk = 0;
|
msr->msc_reqbody_spilltodisk = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ typedef struct msc_string msc_string;
|
|||||||
#define RESPONSE_BODY_LIMIT_ACTION_REJECT 0
|
#define RESPONSE_BODY_LIMIT_ACTION_REJECT 0
|
||||||
#define RESPONSE_BODY_LIMIT_ACTION_PARTIAL 1
|
#define RESPONSE_BODY_LIMIT_ACTION_PARTIAL 1
|
||||||
|
|
||||||
|
#define REQUEST_BODY_FORCEBUF_OFF 0
|
||||||
|
#define REQUEST_BODY_FORCEBUF_ON 1
|
||||||
|
|
||||||
#define SECACTION_TARGETS "REQUEST_URI"
|
#define SECACTION_TARGETS "REQUEST_URI"
|
||||||
#define SECACTION_ARGS "@unconditionalMatch"
|
#define SECACTION_ARGS "@unconditionalMatch"
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ static char *multipart_construct_filename(modsec_rec *msr) {
|
|||||||
*/
|
*/
|
||||||
p = filename = apr_pstrdup(msr->mp, q);
|
p = filename = apr_pstrdup(msr->mp, q);
|
||||||
while((c = *p) != 0) {
|
while((c = *p) != 0) {
|
||||||
if (!( isalnum(c)||(c == '.') )) *p = '_';
|
if (!( isalnum(c) || (c == '.') )) *p = '_';
|
||||||
p++;
|
p++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value)
|
|||||||
/* see if there are any other parts to parse */
|
/* see if there are any other parts to parse */
|
||||||
|
|
||||||
p = c_d_value + 9;
|
p = c_d_value + 9;
|
||||||
while((*p == '\t')||(*p == ' ')) p++;
|
while((*p == '\t') || (*p == ' ')) p++;
|
||||||
if (*p == '\0') return 1; /* this is OK */
|
if (*p == '\0') return 1; /* this is OK */
|
||||||
|
|
||||||
if (*p != ';') return -2;
|
if (*p != ';') return -2;
|
||||||
@@ -79,26 +79,35 @@ static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value)
|
|||||||
char *name = NULL, *value = NULL, *start = NULL;
|
char *name = NULL, *value = NULL, *start = NULL;
|
||||||
|
|
||||||
/* go over the whitespace */
|
/* go over the whitespace */
|
||||||
while((*p == '\t')||(*p == ' ')) p++;
|
while((*p == '\t') || (*p == ' ')) p++;
|
||||||
if (*p == '\0') return -3;
|
if (*p == '\0') return -3;
|
||||||
|
|
||||||
start = p;
|
start = p;
|
||||||
while((*p != '\0')&&(*p != '=')&&(*p != '\t')&&(*p != ' ')) p++;
|
while((*p != '\0') && (*p != '=') && (*p != '\t') && (*p != ' ')) p++;
|
||||||
if (*p == '\0') return -4;
|
if (*p == '\0') return -4;
|
||||||
|
|
||||||
name = apr_pstrmemdup(msr->mp, start, (p - start));
|
name = apr_pstrmemdup(msr->mp, start, (p - start));
|
||||||
|
|
||||||
while((*p == '\t')||(*p == ' ')) p++;
|
while((*p == '\t') || (*p == ' ')) p++;
|
||||||
if (*p == '\0') return -5;
|
if (*p == '\0') return -5;
|
||||||
|
|
||||||
if (*p != '=') return -13;
|
if (*p != '=') return -13;
|
||||||
p++;
|
p++;
|
||||||
|
|
||||||
while((*p == '\t')||(*p == ' ')) p++;
|
while((*p == '\t') || (*p == ' ')) p++;
|
||||||
if (*p == '\0') return -6;
|
if (*p == '\0') return -6;
|
||||||
|
|
||||||
if (*p == '"') {
|
/* Accept both quotes as some backends will accept them, but
|
||||||
|
* technically "'" is invalid and so flag_invalid_quoting is
|
||||||
|
* set so the user can deal with it in the rules if they so wish.
|
||||||
|
*/
|
||||||
|
if ((*p == '"') || (*p == '\'')) {
|
||||||
/* quoted */
|
/* quoted */
|
||||||
|
char quote = *p;
|
||||||
|
|
||||||
|
if (quote == '\'') {
|
||||||
|
msr->mpd->flag_invalid_quoting = 1;
|
||||||
|
}
|
||||||
|
|
||||||
p++;
|
p++;
|
||||||
if (*p == '\0') return -7;
|
if (*p == '\0') return -7;
|
||||||
@@ -113,8 +122,8 @@ static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value)
|
|||||||
/* improper escaping */
|
/* improper escaping */
|
||||||
return -8;
|
return -8;
|
||||||
}
|
}
|
||||||
/* only " and \ can be escaped */
|
/* only quote and \ can be escaped */
|
||||||
if ((*(p + 1) == '"')||(*(p + 1) == '\\')) {
|
if ((*(p + 1) == quote) || (*(p + 1) == '\\')) {
|
||||||
p++;
|
p++;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -128,8 +137,7 @@ static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value)
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (*p == quote) {
|
||||||
if (*p == '"') {
|
|
||||||
*t = '\0';
|
*t = '\0';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -144,14 +152,18 @@ static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value)
|
|||||||
/* not quoted */
|
/* not quoted */
|
||||||
|
|
||||||
start = p;
|
start = p;
|
||||||
while((*p != '\0')&&(is_token_char(*p))) p++;
|
while((*p != '\0') && (is_token_char(*p))) p++;
|
||||||
value = apr_pstrmemdup(msr->mp, start, (p - start));
|
value = apr_pstrmemdup(msr->mp, start, (p - start));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* evaluate part */
|
/* evaluate part */
|
||||||
|
|
||||||
if (strcmp(name, "name") == 0) {
|
if (strcmp(name, "name") == 0) {
|
||||||
if (msr->mpd->mpp->name != NULL) return -14;
|
if (msr->mpd->mpp->name != NULL) {
|
||||||
|
msr_log(msr, 4, "Multipart: Warning: Duplicate Content-Disposition name: %s",
|
||||||
|
log_escape_nq(msr->mp, value));
|
||||||
|
return -14;
|
||||||
|
}
|
||||||
msr->mpd->mpp->name = value;
|
msr->mpd->mpp->name = value;
|
||||||
|
|
||||||
if (msr->txcfg->debuglog_level >= 9) {
|
if (msr->txcfg->debuglog_level >= 9) {
|
||||||
@@ -161,7 +173,11 @@ static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
if (strcmp(name, "filename") == 0) {
|
if (strcmp(name, "filename") == 0) {
|
||||||
if (msr->mpd->mpp->filename != NULL) return -15;
|
if (msr->mpd->mpp->filename != NULL) {
|
||||||
|
msr_log(msr, 4, "Multipart: Warning: Duplicate Content-Disposition filename: %s",
|
||||||
|
log_escape_nq(msr->mp, value));
|
||||||
|
return -15;
|
||||||
|
}
|
||||||
msr->mpd->mpp->filename = value;
|
msr->mpd->mpp->filename = value;
|
||||||
|
|
||||||
if (msr->txcfg->debuglog_level >= 9) {
|
if (msr->txcfg->debuglog_level >= 9) {
|
||||||
@@ -172,7 +188,7 @@ static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value)
|
|||||||
else return -11;
|
else return -11;
|
||||||
|
|
||||||
if (*p != '\0') {
|
if (*p != '\0') {
|
||||||
while((*p == '\t')||(*p == ' ')) p++;
|
while((*p == '\t') || (*p == ' ')) p++;
|
||||||
/* the next character must be a zero or a semi-colon */
|
/* the next character must be a zero or a semi-colon */
|
||||||
if (*p == '\0') return 1; /* this is OK */
|
if (*p == '\0') return 1; /* this is OK */
|
||||||
if (*p != ';') return -12;
|
if (*p != ';') return -12;
|
||||||
@@ -263,7 +279,7 @@ static int multipart_process_part_header(modsec_rec *msr, char **error_msg) {
|
|||||||
} else {
|
} else {
|
||||||
/* Header line. */
|
/* Header line. */
|
||||||
|
|
||||||
if ((msr->mpd->buf[0] == '\t')||(msr->mpd->buf[0] == ' ')) {
|
if ((msr->mpd->buf[0] == '\t') || (msr->mpd->buf[0] == ' ')) {
|
||||||
char *header_value, *new_value, *data;
|
char *header_value, *new_value, *data;
|
||||||
|
|
||||||
/* header folding, add data to the header we are building */
|
/* header folding, add data to the header we are building */
|
||||||
@@ -277,7 +293,7 @@ static int multipart_process_part_header(modsec_rec *msr, char **error_msg) {
|
|||||||
|
|
||||||
/* locate the beginning of data */
|
/* locate the beginning of data */
|
||||||
data = msr->mpd->buf;
|
data = msr->mpd->buf;
|
||||||
while((*data == '\t')||(*data == ' ')) data++;
|
while((*data == '\t') || (*data == ' ')) data++;
|
||||||
|
|
||||||
new_value = apr_pstrdup(msr->mp, data);
|
new_value = apr_pstrdup(msr->mp, data);
|
||||||
remove_lf_crlf_inplace(new_value);
|
remove_lf_crlf_inplace(new_value);
|
||||||
@@ -303,7 +319,7 @@ static int multipart_process_part_header(modsec_rec *msr, char **error_msg) {
|
|||||||
/* new header */
|
/* new header */
|
||||||
|
|
||||||
data = msr->mpd->buf;
|
data = msr->mpd->buf;
|
||||||
while((*data != ':')&&(*data != '\0')) data++;
|
while((*data != ':') && (*data != '\0')) data++;
|
||||||
if (*data == '\0') {
|
if (*data == '\0') {
|
||||||
*error_msg = apr_psprintf(msr->mp, "Multipart: Invalid part header (colon missing): %s.",
|
*error_msg = apr_psprintf(msr->mp, "Multipart: Invalid part header (colon missing): %s.",
|
||||||
log_escape_nq(msr->mp, msr->mpd->buf));
|
log_escape_nq(msr->mp, msr->mpd->buf));
|
||||||
@@ -320,7 +336,7 @@ static int multipart_process_part_header(modsec_rec *msr, char **error_msg) {
|
|||||||
|
|
||||||
/* extract the value value */
|
/* extract the value value */
|
||||||
data++;
|
data++;
|
||||||
while((*data == '\t')||(*data == ' ')) data++;
|
while((*data == '\t') || (*data == ' ')) data++;
|
||||||
header_value = apr_pstrdup(msr->mp, data);
|
header_value = apr_pstrdup(msr->mp, data);
|
||||||
remove_lf_crlf_inplace(header_value);
|
remove_lf_crlf_inplace(header_value);
|
||||||
|
|
||||||
@@ -713,7 +729,7 @@ int multipart_init(modsec_rec *msr, char **error_msg) {
|
|||||||
/* Check for extra characters before the boundary. */
|
/* Check for extra characters before the boundary. */
|
||||||
for (p = (char *)(msr->request_content_type + 19); p < msr->mpd->boundary; p++) {
|
for (p = (char *)(msr->request_content_type + 19); p < msr->mpd->boundary; p++) {
|
||||||
if (!isspace(*p)) {
|
if (!isspace(*p)) {
|
||||||
if ((seen_semicolon == 0)&&(*p == ';')) {
|
if ((seen_semicolon == 0) && (*p == ';')) {
|
||||||
seen_semicolon = 1; /* It is OK to have one semicolon. */
|
seen_semicolon = 1; /* It is OK to have one semicolon. */
|
||||||
} else {
|
} else {
|
||||||
msr->mpd->flag_error = 1;
|
msr->mpd->flag_error = 1;
|
||||||
@@ -761,7 +777,7 @@ int multipart_init(modsec_rec *msr, char **error_msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Is the boundary quoted? */
|
/* Is the boundary quoted? */
|
||||||
if ((len >= 2)&&(*b == '"')&&(*(b + len - 1) == '"')) {
|
if ((len >= 2) && (*b == '"') && (*(b + len - 1) == '"')) {
|
||||||
/* Quoted. */
|
/* Quoted. */
|
||||||
msr->mpd->boundary = apr_pstrndup(msr->mp, b + 1, len - 2);
|
msr->mpd->boundary = apr_pstrndup(msr->mp, b + 1, len - 2);
|
||||||
if (msr->mpd->boundary == NULL) return -1;
|
if (msr->mpd->boundary == NULL) return -1;
|
||||||
@@ -771,7 +787,7 @@ int multipart_init(modsec_rec *msr, char **error_msg) {
|
|||||||
|
|
||||||
/* Test for partial quoting. */
|
/* Test for partial quoting. */
|
||||||
if ( (*b == '"')
|
if ( (*b == '"')
|
||||||
|| ((len >= 2)&&(*(b + len - 1) == '"')) )
|
|| ((len >= 2) && (*(b + len - 1) == '"')) )
|
||||||
{
|
{
|
||||||
msr->mpd->flag_error = 1;
|
msr->mpd->flag_error = 1;
|
||||||
*error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (quote).");
|
*error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (quote).");
|
||||||
@@ -865,14 +881,15 @@ int multipart_complete(modsec_rec *msr, char **error_msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((msr->mpd->seen_data != 0)&&(msr->mpd->is_complete == 0)) {
|
if ((msr->mpd->seen_data != 0) && (msr->mpd->is_complete == 0)) {
|
||||||
if (msr->mpd->boundary_count > 0) {
|
if (msr->mpd->boundary_count > 0) {
|
||||||
/* Check if we have the final boundary (that we haven't
|
/* Check if we have the final boundary (that we haven't
|
||||||
* processed yet) in the buffer.
|
* processed yet) in the buffer.
|
||||||
*/
|
*/
|
||||||
if (msr->mpd->buf_contains_line) {
|
if (msr->mpd->buf_contains_line) {
|
||||||
if ( ((unsigned int)(MULTIPART_BUF_SIZE - msr->mpd->bufleft) == (4 + strlen(msr->mpd->boundary)))
|
if ( ((unsigned int)(MULTIPART_BUF_SIZE - msr->mpd->bufleft) == (4 + strlen(msr->mpd->boundary)))
|
||||||
&& (*(msr->mpd->buf) == '-')&&(*(msr->mpd->buf + 1) == '-')
|
&& (*(msr->mpd->buf) == '-')
|
||||||
|
&& (*(msr->mpd->buf + 1) == '-')
|
||||||
&& (strncmp(msr->mpd->buf + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0)
|
&& (strncmp(msr->mpd->buf + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0)
|
||||||
&& (*(msr->mpd->buf + 2 + strlen(msr->mpd->boundary)) == '-')
|
&& (*(msr->mpd->buf + 2 + strlen(msr->mpd->boundary)) == '-')
|
||||||
&& (*(msr->mpd->buf + 2 + strlen(msr->mpd->boundary) + 1) == '-') )
|
&& (*(msr->mpd->buf + 2 + strlen(msr->mpd->boundary) + 1) == '-') )
|
||||||
@@ -939,7 +956,7 @@ int multipart_process_chunk(modsec_rec *msr, const char *buf,
|
|||||||
char c = *inptr;
|
char c = *inptr;
|
||||||
int process_buffer = 0;
|
int process_buffer = 0;
|
||||||
|
|
||||||
if ((c == '\r')&&(msr->mpd->bufleft == 1)) {
|
if ((c == '\r') && (msr->mpd->bufleft == 1)) {
|
||||||
/* we don't want to take \r as the last byte in the buffer */
|
/* we don't want to take \r as the last byte in the buffer */
|
||||||
process_buffer = 1;
|
process_buffer = 1;
|
||||||
} else {
|
} else {
|
||||||
@@ -954,25 +971,26 @@ int multipart_process_chunk(modsec_rec *msr, const char *buf,
|
|||||||
/* until we either reach the end of the line
|
/* until we either reach the end of the line
|
||||||
* or the end of our internal buffer
|
* or the end of our internal buffer
|
||||||
*/
|
*/
|
||||||
if ((c == '\n')||(msr->mpd->bufleft == 0)||(process_buffer)) {
|
if ((c == '\n') || (msr->mpd->bufleft == 0) || (process_buffer)) {
|
||||||
int processed_as_boundary = 0;
|
int processed_as_boundary = 0;
|
||||||
|
|
||||||
*(msr->mpd->bufptr) = 0;
|
*(msr->mpd->bufptr) = 0;
|
||||||
|
|
||||||
/* Do we have something that looks like a boundary? */
|
/* Do we have something that looks like a boundary? */
|
||||||
if (msr->mpd->buf_contains_line
|
if ( msr->mpd->buf_contains_line
|
||||||
&& (strlen(msr->mpd->buf) > 3)
|
&& (strlen(msr->mpd->buf) > 3)
|
||||||
&& (((*(msr->mpd->buf) == '-'))&&(*(msr->mpd->buf + 1) == '-')) )
|
&& (*(msr->mpd->buf) == '-')
|
||||||
|
&& (*(msr->mpd->buf + 1) == '-') )
|
||||||
{
|
{
|
||||||
/* Does it match our boundary? */
|
/* Does it match our boundary? */
|
||||||
if ((strlen(msr->mpd->buf) >= strlen(msr->mpd->boundary) + 2)
|
if ( (strlen(msr->mpd->buf) >= strlen(msr->mpd->boundary) + 2)
|
||||||
&& (strncmp(msr->mpd->buf + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) )
|
&& (strncmp(msr->mpd->buf + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) )
|
||||||
{
|
{
|
||||||
char *boundary_end = msr->mpd->buf + 2 + strlen(msr->mpd->boundary);
|
char *boundary_end = msr->mpd->buf + 2 + strlen(msr->mpd->boundary);
|
||||||
int is_final = 0;
|
int is_final = 0;
|
||||||
|
|
||||||
/* Is this the final boundary? */
|
/* Is this the final boundary? */
|
||||||
if ((*boundary_end == '-')&&(*(boundary_end + 1)== '-')) {
|
if ((*boundary_end == '-') && (*(boundary_end + 1)== '-')) {
|
||||||
is_final = 1;
|
is_final = 1;
|
||||||
boundary_end += 2;
|
boundary_end += 2;
|
||||||
|
|
||||||
@@ -1057,7 +1075,8 @@ int multipart_process_chunk(modsec_rec *msr, const char *buf,
|
|||||||
char *p = msr->mpd->buf;
|
char *p = msr->mpd->buf;
|
||||||
|
|
||||||
for(i = 0; i < len; i++) {
|
for(i = 0; i < len; i++) {
|
||||||
if ((p[i] == '-')&&(i + 1 < len)&&(p[i + 1] == '-')) {
|
if ((p[i] == '-') && (i + 1 < len) && (p[i + 1] == '-'))
|
||||||
|
{
|
||||||
if (strncmp(p + i + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) {
|
if (strncmp(p + i + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) {
|
||||||
msr->mpd->flag_unmatched_boundary = 1;
|
msr->mpd->flag_unmatched_boundary = 1;
|
||||||
break;
|
break;
|
||||||
@@ -1077,7 +1096,7 @@ int multipart_process_chunk(modsec_rec *msr, const char *buf,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (msr->mpd->mpp_state == 0) {
|
if (msr->mpd->mpp_state == 0) {
|
||||||
if ((msr->mpd->bufleft == 0)||(process_buffer)) {
|
if ((msr->mpd->bufleft == 0) || (process_buffer)) {
|
||||||
/* part header lines must be shorter than
|
/* part header lines must be shorter than
|
||||||
* MULTIPART_BUF_SIZE bytes
|
* MULTIPART_BUF_SIZE bytes
|
||||||
*/
|
*/
|
||||||
@@ -1115,7 +1134,7 @@ int multipart_process_chunk(modsec_rec *msr, const char *buf,
|
|||||||
msr->mpd->buf_contains_line = (c == 0x0a) ? 1 : 0;
|
msr->mpd->buf_contains_line = (c == 0x0a) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((msr->mpd->is_complete)&&(inleft != 0)) {
|
if ((msr->mpd->is_complete) && (inleft != 0)) {
|
||||||
msr->mpd->flag_data_after = 1;
|
msr->mpd->flag_data_after = 1;
|
||||||
|
|
||||||
if (msr->txcfg->debuglog_level >= 4) {
|
if (msr->txcfg->debuglog_level >= 4) {
|
||||||
@@ -1189,7 +1208,9 @@ apr_status_t multipart_cleanup(modsec_rec *msr) {
|
|||||||
|
|
||||||
parts = (multipart_part **)msr->mpd->parts->elts;
|
parts = (multipart_part **)msr->mpd->parts->elts;
|
||||||
for(i = 0; i < msr->mpd->parts->nelts; i++) {
|
for(i = 0; i < msr->mpd->parts->nelts; i++) {
|
||||||
if ((parts[i]->type == MULTIPART_FILE)&&(parts[i]->tmp_file_size == 0)) {
|
if ( (parts[i]->type == MULTIPART_FILE)
|
||||||
|
&& (parts[i]->tmp_file_size == 0))
|
||||||
|
{
|
||||||
/* Delete empty file. */
|
/* Delete empty file. */
|
||||||
if (parts[i]->tmp_file_name != NULL) {
|
if (parts[i]->tmp_file_name != NULL) {
|
||||||
/* make sure it is closed first */
|
/* make sure it is closed first */
|
||||||
@@ -1300,7 +1321,7 @@ char *multipart_reconstruct_urlencoded_body_sanitise(modsec_rec *msr) {
|
|||||||
|
|
||||||
/* allocate the buffer */
|
/* allocate the buffer */
|
||||||
body = apr_palloc(msr->mp, body_len + 1);
|
body = apr_palloc(msr->mp, body_len + 1);
|
||||||
if ((body == NULL)||(body_len + 1 == 0)) return NULL;
|
if ((body == NULL) || (body_len + 1 == 0)) return NULL;
|
||||||
*body = 0;
|
*body = 0;
|
||||||
|
|
||||||
parts = (multipart_part **)msr->mpd->parts->elts;
|
parts = (multipart_part **)msr->mpd->parts->elts;
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ struct multipart_data {
|
|||||||
int flag_unmatched_boundary;
|
int flag_unmatched_boundary;
|
||||||
int flag_boundary_whitespace;
|
int flag_boundary_whitespace;
|
||||||
int flag_missing_semicolon;
|
int flag_missing_semicolon;
|
||||||
|
int flag_invalid_quoting;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ extern DSOLOCAL modsec_build_type_rec modsec_build_type[];
|
|||||||
|
|
||||||
#define MODSEC_VERSION_MAJOR "2"
|
#define MODSEC_VERSION_MAJOR "2"
|
||||||
#define MODSEC_VERSION_MINOR "5"
|
#define MODSEC_VERSION_MINOR "5"
|
||||||
#define MODSEC_VERSION_MAINT "10"
|
#define MODSEC_VERSION_MAINT "11"
|
||||||
#define MODSEC_VERSION_TYPE ""
|
#define MODSEC_VERSION_TYPE ""
|
||||||
#define MODSEC_VERSION_RELEASE ""
|
#define MODSEC_VERSION_RELEASE ""
|
||||||
|
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ apr_status_t modsecurity_request_body_store(modsec_rec *msr,
|
|||||||
msr->msc_reqbody_processor);
|
msr->msc_reqbody_processor);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
} else if (msr->txcfg->reqbody_buffering) {
|
} else if (msr->txcfg->reqbody_buffering != REQUEST_BODY_FORCEBUF_OFF) {
|
||||||
/* Increase per-request data length counter if forcing buffering. */
|
/* Increase per-request data length counter if forcing buffering. */
|
||||||
msr->msc_reqbody_no_files_length += length;
|
msr->msc_reqbody_no_files_length += length;
|
||||||
}
|
}
|
||||||
@@ -479,7 +479,7 @@ apr_status_t modsecurity_request_body_end(modsec_rec *msr, char **error_msg) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (msr->txcfg->reqbody_buffering) {
|
} else if (msr->txcfg->reqbody_buffering != REQUEST_BODY_FORCEBUF_OFF) {
|
||||||
/* Convert to a single continous buffer, but don't do anything else. */
|
/* Convert to a single continous buffer, but don't do anything else. */
|
||||||
return modsecurity_request_body_end_raw(msr, error_msg);
|
return modsecurity_request_body_end_raw(msr, error_msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -714,11 +714,10 @@ static char *msre_action_ctl_validate(msre_engine *engine, msre_action *action)
|
|||||||
return NULL;
|
return NULL;
|
||||||
} else
|
} else
|
||||||
if (strcasecmp(name, "forceRequestBodyVariable") == 0) {
|
if (strcasecmp(name, "forceRequestBodyVariable") == 0) {
|
||||||
if (parse_boolean(value) == -1) {
|
if (strcasecmp(value, "on") == 0) return NULL;
|
||||||
return apr_psprintf(engine->mp, "Invalid setting for ctl name "
|
if (strcasecmp(value, "off") == 0) return NULL;
|
||||||
" forceRequestBodyVariable: %s", value);
|
return apr_psprintf(engine->mp, "Invalid setting for ctl name "
|
||||||
}
|
" forceRequestBodyVariable: %s", value);
|
||||||
return NULL;
|
|
||||||
} else
|
} else
|
||||||
if (strcasecmp(name, "responseBodyAccess") == 0) {
|
if (strcasecmp(name, "responseBodyAccess") == 0) {
|
||||||
if (parse_boolean(value) == -1) {
|
if (parse_boolean(value) == -1) {
|
||||||
@@ -839,12 +838,17 @@ static apr_status_t msre_action_ctl_execute(modsec_rec *msr, apr_pool_t *mptmp,
|
|||||||
return 1;
|
return 1;
|
||||||
} else
|
} else
|
||||||
if (strcasecmp(name, "forceRequestBodyVariable") == 0) {
|
if (strcasecmp(name, "forceRequestBodyVariable") == 0) {
|
||||||
int pv = parse_boolean(value);
|
if (strcasecmp(value, "on") == 0) {
|
||||||
|
msr->txcfg->reqbody_buffering = REQUEST_BODY_FORCEBUF_ON;
|
||||||
|
msr->usercfg->reqbody_buffering = REQUEST_BODY_FORCEBUF_ON;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (strcasecmp(value, "off") == 0) {
|
||||||
|
msr->txcfg->reqbody_buffering = REQUEST_BODY_FORCEBUF_OFF;
|
||||||
|
msr->usercfg->reqbody_buffering = REQUEST_BODY_FORCEBUF_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
if (pv == -1) return -1;
|
msr_log(msr, 4, "Ctl: Set requestBodyAccess to %d.", msr->txcfg->reqbody_buffering);
|
||||||
msr->txcfg->reqbody_buffering = pv;
|
|
||||||
msr->usercfg->reqbody_buffering = pv;
|
|
||||||
msr_log(msr, 4, "Ctl: Set requestBodyAccess to %d.", pv);
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
} else
|
} else
|
||||||
@@ -880,7 +884,7 @@ static apr_status_t msre_action_ctl_execute(modsec_rec *msr, apr_pool_t *mptmp,
|
|||||||
msr->usercfg->auditlog_flag = AUDITLOG_RELEVANT;
|
msr->usercfg->auditlog_flag = AUDITLOG_RELEVANT;
|
||||||
}
|
}
|
||||||
|
|
||||||
msr_log(msr, 4, "Ctl: Set auditEngine to %d.", msr->txcfg->auditlog_flag); // TODO
|
msr_log(msr, 4, "Ctl: Set auditEngine to %d.", msr->txcfg->auditlog_flag);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
} else
|
} else
|
||||||
|
|||||||
@@ -1366,6 +1366,18 @@ static int var_multipart_missing_semicolon_generate(modsec_rec *msr, msre_var *v
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* MULTIPART_INVALID_QUOTING */
|
||||||
|
|
||||||
|
static int var_multipart_invalid_quoting_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
|
||||||
|
apr_table_t *vartab, apr_pool_t *mptmp)
|
||||||
|
{
|
||||||
|
if ((msr->mpd != NULL)&&(msr->mpd->flag_invalid_quoting != 0)) {
|
||||||
|
return var_simple_generate(var, vartab, mptmp, "1");
|
||||||
|
} else {
|
||||||
|
return var_simple_generate(var, vartab, mptmp, "0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* MULTIPART_STRICT_ERROR */
|
/* MULTIPART_STRICT_ERROR */
|
||||||
|
|
||||||
static int var_multipart_strict_error_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
|
static int var_multipart_strict_error_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
|
||||||
@@ -1381,6 +1393,7 @@ static int var_multipart_strict_error_generate(modsec_rec *msr, msre_var *var, m
|
|||||||
||(msr->mpd->flag_header_folding != 0)
|
||(msr->mpd->flag_header_folding != 0)
|
||||||
||(msr->mpd->flag_lf_line != 0)
|
||(msr->mpd->flag_lf_line != 0)
|
||||||
||(msr->mpd->flag_missing_semicolon != 0)
|
||(msr->mpd->flag_missing_semicolon != 0)
|
||||||
|
||(msr->mpd->flag_invalid_quoting != 0)
|
||||||
) {
|
) {
|
||||||
return var_simple_generate(var, vartab, mptmp, "1");
|
return var_simple_generate(var, vartab, mptmp, "1");
|
||||||
}
|
}
|
||||||
@@ -2454,6 +2467,17 @@ void msre_engine_register_default_variables(msre_engine *engine) {
|
|||||||
PHASE_REQUEST_BODY
|
PHASE_REQUEST_BODY
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* MULTIPART_INVALID_QUOTING */
|
||||||
|
msre_engine_variable_register(engine,
|
||||||
|
"MULTIPART_INVALID_QUOTING",
|
||||||
|
VAR_SIMPLE,
|
||||||
|
0, 0,
|
||||||
|
NULL,
|
||||||
|
var_multipart_invalid_quoting_generate,
|
||||||
|
VAR_DONT_CACHE, /* flag */
|
||||||
|
PHASE_REQUEST_BODY
|
||||||
|
);
|
||||||
|
|
||||||
/* MULTIPART_STRICT_ERROR */
|
/* MULTIPART_STRICT_ERROR */
|
||||||
msre_engine_variable_register(engine,
|
msre_engine_variable_register(engine,
|
||||||
"MULTIPART_STRICT_ERROR",
|
"MULTIPART_STRICT_ERROR",
|
||||||
|
|||||||
@@ -404,3 +404,84 @@
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
# Data following final boundary should set flag
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "multipart parser (data after final boundary)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
#SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,status:403"
|
||||||
|
SecRule MULTIPART_DATA_AFTER "\@eq 1" "phase:2,deny,status:403"
|
||||||
|
SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,status:403"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/name: a.*variable: 1.*Ignoring data after last boundary/s, 1 ],
|
||||||
|
-debug => [ qr/Adding request argument \(BODY\): name "b"/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646",
|
||||||
|
],
|
||||||
|
normalize_raw_request_data(
|
||||||
|
q(
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name="a"
|
||||||
|
|
||||||
|
1
|
||||||
|
-----------------------------69343412719991675451336310646--
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name="b"
|
||||||
|
|
||||||
|
2
|
||||||
|
-----------------------------69343412719991675451336310646--
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
# Single quoted data is invalid
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "multipart parser (C-D uses single quotes)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
#SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,status:403"
|
||||||
|
SecRule MULTIPART_INVALID_QUOTING "\@eq 1" "chain,phase:2,deny,status:403"
|
||||||
|
SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,status:403"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/name: a.*variable: 1.*Duplicate Content-Disposition name/s, 1 ],
|
||||||
|
-debug => [ qr/Adding request argument \(BODY\): name "b/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646",
|
||||||
|
],
|
||||||
|
normalize_raw_request_data(
|
||||||
|
q(
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name="a"
|
||||||
|
|
||||||
|
1
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name=';filename="dummy';name=b;"
|
||||||
|
|
||||||
|
2
|
||||||
|
-----------------------------69343412719991675451336310646--
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
Manual</title>
|
Manual</title>
|
||||||
|
|
||||||
<articleinfo>
|
<articleinfo>
|
||||||
<releaseinfo>Version 2.5.10 (Sep 18, 2009)</releaseinfo>
|
<releaseinfo>Version 2.5.11 (Nov 4, 2009)</releaseinfo>
|
||||||
|
|
||||||
<copyright>
|
<copyright>
|
||||||
<year>2004-2009</year>
|
<year>2004-2009</year>
|
||||||
@@ -308,6 +308,12 @@
|
|||||||
|
|
||||||
<para><ulink type=""
|
<para><ulink type=""
|
||||||
url="http://curl.haxx.se/libcurl/">http://curl.haxx.se/libcurl/</ulink></para>
|
url="http://curl.haxx.se/libcurl/">http://curl.haxx.se/libcurl/</ulink></para>
|
||||||
|
<note>
|
||||||
|
<para>Many have had issues with libcurl linked with the GnuTLS
|
||||||
|
library for SSL/TLS support. It is recommended that the
|
||||||
|
openssl library be used for SSL/TLS support in libcurl.</para>
|
||||||
|
</note>
|
||||||
|
|
||||||
</listitem>
|
</listitem>
|
||||||
</orderedlist>
|
</orderedlist>
|
||||||
|
|
||||||
@@ -3111,7 +3117,8 @@ SecRule ARGS "@pm some key words" id:12345,deny,status:500</programlisting>
|
|||||||
<literal>MULTIPART_DATA_AFTER</literal>,
|
<literal>MULTIPART_DATA_AFTER</literal>,
|
||||||
<literal>MULTIPART_HEADER_FOLDING</literal>,
|
<literal>MULTIPART_HEADER_FOLDING</literal>,
|
||||||
<literal>MULTIPART_LF_LINE</literal>,
|
<literal>MULTIPART_LF_LINE</literal>,
|
||||||
<literal>MULTIPART_SEMICOLON_MISSING</literal>. Each of these variables
|
<literal>MULTIPART_SEMICOLON_MISSING</literal>
|
||||||
|
<literal>MULTIPART_INVALID_QUOTING</literal>. Each of these variables
|
||||||
covers one unusual (although sometimes legal) aspect of the request body
|
covers one unusual (although sometimes legal) aspect of the request body
|
||||||
in <literal>multipart/form-data format</literal>. Your policies should
|
in <literal>multipart/form-data format</literal>. Your policies should
|
||||||
<emphasis>always</emphasis> contain a rule to check either this variable
|
<emphasis>always</emphasis> contain a rule to check either this variable
|
||||||
@@ -3133,7 +3140,8 @@ DB %{MULTIPART_DATA_BEFORE}, \
|
|||||||
DA %{MULTIPART_DATA_AFTER}, \
|
DA %{MULTIPART_DATA_AFTER}, \
|
||||||
HF %{MULTIPART_HEADER_FOLDING}, \
|
HF %{MULTIPART_HEADER_FOLDING}, \
|
||||||
LF %{MULTIPART_LF_LINE}, \
|
LF %{MULTIPART_LF_LINE}, \
|
||||||
SM %{MULTIPART_SEMICOLON_MISSING}'"</programlisting>
|
SM %{MULTIPART_SEMICOLON_MISSING}, \
|
||||||
|
IQ %{MULTIPART_INVALID_QUOTING}'"</programlisting>
|
||||||
|
|
||||||
<para>The <literal>multipart/form-data</literal> parser was upgraded in
|
<para>The <literal>multipart/form-data</literal> parser was upgraded in
|
||||||
ModSecurity v2.1.3 to actively look for signs of evasion. Many variables
|
ModSecurity v2.1.3 to actively look for signs of evasion. Many variables
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ DB %{MULTIPART_DATA_BEFORE}, \
|
|||||||
DA %{MULTIPART_DATA_AFTER}, \
|
DA %{MULTIPART_DATA_AFTER}, \
|
||||||
HF %{MULTIPART_HEADER_FOLDING}, \
|
HF %{MULTIPART_HEADER_FOLDING}, \
|
||||||
LF %{MULTIPART_LF_LINE}, \
|
LF %{MULTIPART_LF_LINE}, \
|
||||||
SM %{MULTIPART_SEMICOLON_MISSING}'"
|
SM %{MULTIPART_SEMICOLON_MISSING}, \
|
||||||
|
IQ %{MULTIPART_INVALID_QUOTING}'"
|
||||||
|
|
||||||
# Did we see anything that might be a boundary?
|
# Did we see anything that might be a boundary?
|
||||||
SecRule MULTIPART_UNMATCHED_BOUNDARY "!@eq 0" \
|
SecRule MULTIPART_UNMATCHED_BOUNDARY "!@eq 0" \
|
||||||
|
|||||||
Reference in New Issue
Block a user