Skip to content

Commit a682f13

Browse files
committed
Allow partial processing of multipart, JSON, and XML request body
1 parent 24e94a8 commit a682f13

11 files changed

Lines changed: 334 additions & 20 deletions

File tree

apache2/apache2_io.c

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -299,15 +299,16 @@ apr_status_t read_request_body(modsec_rec *msr, char **error_msg) {
299299
#endif
300300
}
301301

302+
if (msr->reqbody_length + buflen > (apr_size_t)msr->txcfg->reqbody_limit && msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_PARTIAL) {
303+
buflen = (apr_size_t)msr->txcfg->reqbody_limit - msr->reqbody_length;
304+
finished_reading = 1;
305+
modsecurity_request_body_enable_partial_processing(msr);
306+
}
307+
302308
msr->reqbody_length += buflen;
303309

304310
if (buflen != 0) {
305311
int rcbs = modsecurity_request_body_store(msr, buf, buflen, error_msg);
306-
307-
if (msr->reqbody_length > (apr_size_t)msr->txcfg->reqbody_limit && msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_PARTIAL) {
308-
finished_reading = 1;
309-
}
310-
311312
if (rcbs < 0) {
312313
if (rcbs == -5) {
313314
if((msr->txcfg->is_enabled == MODSEC_ENABLED) && (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_REJECT)) {
@@ -351,11 +352,13 @@ apr_status_t read_request_body(modsec_rec *msr, char **error_msg) {
351352

352353
msr->if_status = IF_STATUS_WANTS_TO_RUN;
353354

354-
if (rcbe == -5) {
355-
return HTTP_REQUEST_ENTITY_TOO_LARGE;
356-
}
357-
if (rcbe < 0) {
358-
return HTTP_INTERNAL_SERVER_ERROR;
355+
if (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_REJECT) {
356+
if (rcbe == -5) {
357+
return HTTP_REQUEST_ENTITY_TOO_LARGE;
358+
}
359+
if (rcbe < 0) {
360+
return HTTP_INTERNAL_SERVER_ERROR;
361+
}
359362
}
360363
return APR_SUCCESS;
361364
}

apache2/modsecurity.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,8 @@ apr_status_t DSOLOCAL modsecurity_request_body_start(modsec_rec *msr, char **err
736736
apr_status_t DSOLOCAL modsecurity_request_body_store(modsec_rec *msr,
737737
const char *data, apr_size_t length, char **error_msg);
738738

739+
void DSOLOCAL modsecurity_request_body_enable_partial_processing(modsec_rec *msr);
740+
739741
apr_status_t DSOLOCAL modsecurity_request_body_end(modsec_rec *msr, char **error_msg);
740742

741743
apr_status_t DSOLOCAL modsecurity_request_body_to_stream(modsec_rec *msr, const char *buffer, int buflen, char **error_msg);

apache2/msc_json.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,10 @@ int json_init(modsec_rec *msr, char **error_msg) {
367367
return 1;
368368
}
369369

370+
void json_allow_partial_values(modsec_rec *msr) {
371+
(void)yajl_config(msr->json->handle, yajl_allow_partial_values, 1);
372+
}
373+
370374
/**
371375
* Feed one chunk of data to the JSON parser.
372376
*/

apache2/msc_json.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ struct json_data {
4848

4949
int DSOLOCAL json_init(modsec_rec *msr, char **error_msg);
5050

51+
void DSOLOCAL json_allow_partial_values(modsec_rec *msr);
52+
5153
int DSOLOCAL json_process(modsec_rec *msr, const char *buf,
5254
unsigned int size, char **error_msg);
5355

apache2/msc_multipart.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1054,7 +1054,7 @@ int multipart_complete(modsec_rec *msr, char **error_msg) {
10541054
}
10551055
}
10561056

1057-
if (msr->mpd->is_complete == 0) {
1057+
if (msr->mpd->is_complete == 0 && msr->mpd->allow_process_partial == 0) {
10581058
*error_msg = apr_psprintf(msr->mp, "Multipart: Final boundary missing.");
10591059
return -1;
10601060
}

apache2/msc_multipart.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ struct multipart_data {
124124

125125
int seen_data;
126126
int is_complete;
127+
int allow_process_partial;
127128

128129
int flag_error;
129130
int flag_data_before;

apache2/msc_reqbody.c

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,12 +413,13 @@ apr_status_t modsecurity_request_body_store(modsec_rec *msr,
413413
msr_log(msr, 1, "%s", *error_msg);
414414
}
415415

416-
msr->msc_reqbody_error = 1;
416+
if (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_REJECT)
417+
msr->msc_reqbody_error = 1;
417418

418-
if ((msr->txcfg->is_enabled == MODSEC_ENABLED) && (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_REJECT)) {
419+
if ((msr->txcfg->is_enabled == MODSEC_ENABLED) && (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_REJECT)) {
419420
return -5;
420-
} else if (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_PARTIAL) {
421-
if(msr->txcfg->is_enabled == MODSEC_ENABLED)
421+
} else if (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_PARTIAL) {
422+
if (msr->txcfg->is_enabled == MODSEC_ENABLED)
422423
return -5;
423424
}
424425
}
@@ -438,6 +439,24 @@ apr_status_t modsecurity_request_body_store(modsec_rec *msr,
438439
return -1;
439440
}
440441

442+
/**
443+
* Enable partial processing of request body data.
444+
*/
445+
void modsecurity_request_body_enable_partial_processing(modsec_rec *msr) {
446+
if (strcmp(msr->msc_reqbody_processor, "MULTIPART") == 0) {
447+
msr->mpd->allow_process_partial = 1;
448+
msr_log(msr, 4, "Multipart: Allow partial processing of request body");
449+
}
450+
else if (strcmp(msr->msc_reqbody_processor, "XML") == 0) {
451+
msr->xml->allow_ill_formed = 1;
452+
msr_log(msr, 4, "XML: Allow partial processing of request body");
453+
}
454+
else if (strcmp(msr->msc_reqbody_processor, "JSON") == 0) {
455+
json_allow_partial_values(msr);
456+
msr_log(msr, 4, "JSON: Allow partial processing of request body");
457+
}
458+
}
459+
441460
apr_status_t modsecurity_request_body_to_stream(modsec_rec *msr, const char *buffer, int buflen, char **error_msg) {
442461
assert(msr != NULL);
443462
assert(error_msg != NULL);

apache2/msc_xml.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ int xml_process_chunk(modsec_rec *msr, const char *buf, unsigned int size, char
267267
if (msr->xml->parsing_ctx != NULL &&
268268
msr->txcfg->parse_xml_into_args != MSC_XML_ARGS_ONLYARGS) {
269269
xmlParseChunk(msr->xml->parsing_ctx, buf, size, 0);
270-
if (msr->xml->parsing_ctx->wellFormed != 1) {
270+
if (!msr->xml->allow_ill_formed && msr->xml->parsing_ctx->wellFormed != 1) {
271271
*error_msg = apr_psprintf(msr->mp, "XML: Failed to parse document.");
272272
return -1;
273273
}
@@ -318,7 +318,7 @@ int xml_complete(modsec_rec *msr, char **error_msg) {
318318
msr->xml->parsing_ctx = NULL;
319319
msr_log(msr, 4, "XML: Parsing complete (well_formed %u).", msr->xml->well_formed);
320320

321-
if (msr->xml->well_formed != 1) {
321+
if (!msr->xml->allow_ill_formed && msr->xml->well_formed != 1) {
322322
*error_msg = apr_psprintf(msr->mp, "XML: Failed to parse document.");
323323
return -1;
324324
}

apache2/msc_xml.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ struct xml_data {
4343
xmlDocPtr doc;
4444

4545
unsigned int well_formed;
46+
unsigned int allow_ill_formed;
4647

4748
/* error reporting and XML array flag */
4849
char *xml_error;

tests/regression/rule/10-xml.t

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,3 +428,96 @@
428428
),
429429
),
430430
},
431+
{
432+
type => "rule",
433+
comment => "xml ProcessPartial, bad value and whole body before limit",
434+
conf => qq(
435+
SecRuleEngine On
436+
SecRequestBodyAccess On
437+
SecRequestBodyLimitAction ProcessPartial
438+
SecRequestBodyLimit 61
439+
SecXmlExternalEntity Off
440+
SecDebugLog $ENV{DEBUG_LOG}
441+
SecDebugLogLevel 9
442+
SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500005, \\
443+
phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML"
444+
SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500006
445+
SecRule XML:/* "bad_value" "id:'500007',phase:2,t:none,deny"
446+
),
447+
match_log => {
448+
error => [ qr/Access denied with code 403 \(phase 2\). Pattern match "bad_value" at XML\./, 1 ],
449+
},
450+
match_response => {
451+
status => qr/^403$/,
452+
},
453+
request => new HTTP::Request(
454+
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
455+
[
456+
"Content-Type" => "text/xml",
457+
],
458+
normalize_raw_request_data(
459+
q(<?xml version="1.0" encoding="utf-8"?><a><b>bad_value</b></a>),
460+
),
461+
),
462+
},
463+
{
464+
type => "rule",
465+
comment => "xml ProcessPartial, bad value before limit",
466+
conf => qq(
467+
SecRuleEngine On
468+
SecRequestBodyAccess On
469+
SecRequestBodyLimitAction ProcessPartial
470+
SecRequestBodyLimit 61
471+
SecXmlExternalEntity Off
472+
SecDebugLog $ENV{DEBUG_LOG}
473+
SecDebugLogLevel 9
474+
SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500005, \\
475+
phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML"
476+
SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500006
477+
SecRule XML:/* "bad_value" "id:'500007',phase:2,t:none,deny"
478+
),
479+
match_log => {
480+
error => [ qr/Access denied with code 403 \(phase 2\). Pattern match "bad_value" at XML\./, 1 ],
481+
},
482+
match_response => {
483+
status => qr/^403$/,
484+
},
485+
request => new HTTP::Request(
486+
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
487+
[
488+
"Content-Type" => "text/xml",
489+
],
490+
normalize_raw_request_data(
491+
q(<?xml version="1.0" encoding="utf-8"?><a><b>bad_value</b><c>ok_value</c></a>),
492+
),
493+
),
494+
},
495+
{
496+
type => "rule",
497+
comment => "xml ProcessPartial, bad value after limit",
498+
conf => qq(
499+
SecRuleEngine On
500+
SecRequestBodyAccess On
501+
SecRequestBodyLimitAction ProcessPartial
502+
SecRequestBodyLimit 61
503+
SecXmlExternalEntity Off
504+
SecDebugLog $ENV{DEBUG_LOG}
505+
SecDebugLogLevel 9
506+
SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500005, \\
507+
phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML"
508+
SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500006
509+
SecRule XML:/* "bad_value" "id:'500007',phase:2,t:none,deny"
510+
),
511+
match_response => {
512+
status => qr/^200$/,
513+
},
514+
request => new HTTP::Request(
515+
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
516+
[
517+
"Content-Type" => "text/xml",
518+
],
519+
normalize_raw_request_data(
520+
q(<?xml version="1.0" encoding="utf-8"?><a><b>12</b><c>bad_value</c></a>),
521+
),
522+
),
523+
},

0 commit comments

Comments
 (0)