mirror of
https://github.com/owasp-modsecurity/ModSecurity.git
synced 2025-09-29 19:24:29 +03:00
Merge pull request #2644 from martinhsv/v2/master
Support configurable limit on depth of JSON parsing
This commit is contained in:
6
CHANGES
6
CHANGES
@@ -1,3 +1,9 @@
|
|||||||
|
DD mmm YYYY - 2.9.x (to be released)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
* Support configurable limit on depth of JSON parsing
|
||||||
|
[@theMiddleBlue, @airween, @dune73, @martinhsv]
|
||||||
|
|
||||||
21 Jun 2021 - 2.9.4
|
21 Jun 2021 - 2.9.4
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
@@ -50,6 +50,7 @@ void *create_directory_config(apr_pool_t *mp, char *path)
|
|||||||
dcfg->reqbody_inmemory_limit = NOT_SET;
|
dcfg->reqbody_inmemory_limit = NOT_SET;
|
||||||
dcfg->reqbody_limit = NOT_SET;
|
dcfg->reqbody_limit = NOT_SET;
|
||||||
dcfg->reqbody_no_files_limit = NOT_SET;
|
dcfg->reqbody_no_files_limit = NOT_SET;
|
||||||
|
dcfg->reqbody_json_depth_limit = NOT_SET;
|
||||||
dcfg->resbody_access = NOT_SET;
|
dcfg->resbody_access = NOT_SET;
|
||||||
|
|
||||||
dcfg->debuglog_name = NOT_SET_P;
|
dcfg->debuglog_name = NOT_SET_P;
|
||||||
@@ -332,6 +333,8 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child)
|
|||||||
? parent->reqbody_limit : child->reqbody_limit);
|
? parent->reqbody_limit : child->reqbody_limit);
|
||||||
merged->reqbody_no_files_limit = (child->reqbody_no_files_limit == NOT_SET
|
merged->reqbody_no_files_limit = (child->reqbody_no_files_limit == NOT_SET
|
||||||
? parent->reqbody_no_files_limit : child->reqbody_no_files_limit);
|
? parent->reqbody_no_files_limit : child->reqbody_no_files_limit);
|
||||||
|
merged->reqbody_json_depth_limit = (child->reqbody_json_depth_limit == NOT_SET
|
||||||
|
? parent->reqbody_json_depth_limit : child->reqbody_json_depth_limit);
|
||||||
merged->resbody_access = (child->resbody_access == NOT_SET
|
merged->resbody_access = (child->resbody_access == NOT_SET
|
||||||
? parent->resbody_access : child->resbody_access);
|
? parent->resbody_access : child->resbody_access);
|
||||||
|
|
||||||
@@ -648,6 +651,7 @@ void init_directory_config(directory_config *dcfg)
|
|||||||
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;
|
||||||
if (dcfg->reqbody_no_files_limit == NOT_SET) dcfg->reqbody_no_files_limit = REQUEST_BODY_NO_FILES_DEFAULT_LIMIT;
|
if (dcfg->reqbody_no_files_limit == NOT_SET) dcfg->reqbody_no_files_limit = REQUEST_BODY_NO_FILES_DEFAULT_LIMIT;
|
||||||
|
if (dcfg->reqbody_json_depth_limit == NOT_SET) dcfg->reqbody_json_depth_limit = REQUEST_BODY_JSON_DEPTH_DEFAULT_LIMIT;
|
||||||
if (dcfg->resbody_access == NOT_SET) dcfg->resbody_access = 0;
|
if (dcfg->resbody_access == NOT_SET) dcfg->resbody_access = 0;
|
||||||
if (dcfg->of_limit == NOT_SET) dcfg->of_limit = RESPONSE_BODY_DEFAULT_LIMIT;
|
if (dcfg->of_limit == NOT_SET) dcfg->of_limit = RESPONSE_BODY_DEFAULT_LIMIT;
|
||||||
if (dcfg->if_limit_action == NOT_SET) dcfg->if_limit_action = REQUEST_BODY_LIMIT_ACTION_REJECT;
|
if (dcfg->if_limit_action == NOT_SET) dcfg->if_limit_action = REQUEST_BODY_LIMIT_ACTION_REJECT;
|
||||||
@@ -1920,6 +1924,24 @@ static const char *cmd_request_body_no_files_limit(cmd_parms *cmd, void *_dcfg,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *cmd_request_body_json_depth_limit(cmd_parms *cmd, void *_dcfg,
|
||||||
|
const char *p1)
|
||||||
|
{
|
||||||
|
directory_config *dcfg = (directory_config *)_dcfg;
|
||||||
|
long int limit;
|
||||||
|
|
||||||
|
if (dcfg == NULL) return NULL;
|
||||||
|
|
||||||
|
limit = strtol(p1, NULL, 10);
|
||||||
|
if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) {
|
||||||
|
return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRequestBodyJsonDepthLimit: %s", p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
dcfg->reqbody_json_depth_limit = limit;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static const char *cmd_request_body_access(cmd_parms *cmd, void *_dcfg,
|
static const char *cmd_request_body_access(cmd_parms *cmd, void *_dcfg,
|
||||||
const char *p1)
|
const char *p1)
|
||||||
{
|
{
|
||||||
@@ -3553,6 +3575,14 @@ const command_rec module_directives[] = {
|
|||||||
"maximum request body size ModSecurity will accept, but excluding the size of uploaded files."
|
"maximum request body size ModSecurity will accept, but excluding the size of uploaded files."
|
||||||
),
|
),
|
||||||
|
|
||||||
|
AP_INIT_TAKE1 (
|
||||||
|
"SecRequestBodyJsonDepthLimit",
|
||||||
|
cmd_request_body_json_depth_limit,
|
||||||
|
NULL,
|
||||||
|
CMD_SCOPE_ANY,
|
||||||
|
"maximum request body JSON parsing depth ModSecurity will accept."
|
||||||
|
),
|
||||||
|
|
||||||
AP_INIT_TAKE1 (
|
AP_INIT_TAKE1 (
|
||||||
"SecRequestEncoding",
|
"SecRequestEncoding",
|
||||||
cmd_request_encoding,
|
cmd_request_encoding,
|
||||||
|
@@ -95,6 +95,7 @@ typedef struct msc_parm msc_parm;
|
|||||||
#define REQUEST_BODY_DEFAULT_INMEMORY_LIMIT 131072
|
#define REQUEST_BODY_DEFAULT_INMEMORY_LIMIT 131072
|
||||||
#define REQUEST_BODY_DEFAULT_LIMIT 134217728
|
#define REQUEST_BODY_DEFAULT_LIMIT 134217728
|
||||||
#define REQUEST_BODY_NO_FILES_DEFAULT_LIMIT 1048576
|
#define REQUEST_BODY_NO_FILES_DEFAULT_LIMIT 1048576
|
||||||
|
#define REQUEST_BODY_JSON_DEPTH_DEFAULT_LIMIT 10000
|
||||||
#define RESPONSE_BODY_DEFAULT_LIMIT 524288
|
#define RESPONSE_BODY_DEFAULT_LIMIT 524288
|
||||||
#define RESPONSE_BODY_HARD_LIMIT 1073741824L
|
#define RESPONSE_BODY_HARD_LIMIT 1073741824L
|
||||||
|
|
||||||
@@ -498,6 +499,7 @@ struct directory_config {
|
|||||||
long int reqbody_inmemory_limit;
|
long int reqbody_inmemory_limit;
|
||||||
long int reqbody_limit;
|
long int reqbody_limit;
|
||||||
long int reqbody_no_files_limit;
|
long int reqbody_no_files_limit;
|
||||||
|
long int reqbody_json_depth_limit;
|
||||||
int resbody_access;
|
int resbody_access;
|
||||||
|
|
||||||
long int of_limit;
|
long int of_limit;
|
||||||
|
@@ -164,6 +164,11 @@ static int yajl_start_array(void *ctx) {
|
|||||||
else {
|
else {
|
||||||
msr->json->prefix = apr_pstrdup(msr->mp, msr->json->current_key);
|
msr->json->prefix = apr_pstrdup(msr->mp, msr->json->current_key);
|
||||||
}
|
}
|
||||||
|
msr->json->current_depth++;
|
||||||
|
if (msr->json->current_depth > msr->txcfg->reqbody_json_depth_limit) {
|
||||||
|
msr->json->depth_limit_exceeded = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (msr->txcfg->debuglog_level >= 9) {
|
if (msr->txcfg->debuglog_level >= 9) {
|
||||||
msr_log(msr, 9, "New JSON hash context (prefix '%s')", msr->json->prefix);
|
msr_log(msr, 9, "New JSON hash context (prefix '%s')", msr->json->prefix);
|
||||||
@@ -200,6 +205,7 @@ static int yajl_end_array(void *ctx) {
|
|||||||
*/
|
*/
|
||||||
msr->json->prefix = (unsigned char *) NULL;
|
msr->json->prefix = (unsigned char *) NULL;
|
||||||
}
|
}
|
||||||
|
msr->json->current_depth--;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -229,6 +235,11 @@ static int yajl_start_map(void *ctx)
|
|||||||
else {
|
else {
|
||||||
msr->json->prefix = apr_pstrdup(msr->mp, msr->json->current_key);
|
msr->json->prefix = apr_pstrdup(msr->mp, msr->json->current_key);
|
||||||
}
|
}
|
||||||
|
msr->json->current_depth++;
|
||||||
|
if (msr->json->current_depth > msr->txcfg->reqbody_json_depth_limit) {
|
||||||
|
msr->json->depth_limit_exceeded = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (msr->txcfg->debuglog_level >= 9) {
|
if (msr->txcfg->debuglog_level >= 9) {
|
||||||
msr_log(msr, 9, "New JSON hash context (prefix '%s')", msr->json->prefix);
|
msr_log(msr, 9, "New JSON hash context (prefix '%s')", msr->json->prefix);
|
||||||
@@ -270,6 +281,7 @@ static int yajl_end_map(void *ctx)
|
|||||||
msr->json->current_key = msr->json->prefix;
|
msr->json->current_key = msr->json->prefix;
|
||||||
msr->json->prefix = (unsigned char *) NULL;
|
msr->json->prefix = (unsigned char *) NULL;
|
||||||
}
|
}
|
||||||
|
msr->json->current_depth--;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -308,6 +320,9 @@ int json_init(modsec_rec *msr, char **error_msg) {
|
|||||||
msr->json->prefix = (unsigned char *) NULL;
|
msr->json->prefix = (unsigned char *) NULL;
|
||||||
msr->json->current_key = (unsigned char *) NULL;
|
msr->json->current_key = (unsigned char *) NULL;
|
||||||
|
|
||||||
|
msr->json->current_depth = 0;
|
||||||
|
msr->json->depth_limit_exceeded = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* yajl initialization
|
* yajl initialization
|
||||||
*
|
*
|
||||||
@@ -337,7 +352,11 @@ int json_process_chunk(modsec_rec *msr, const char *buf, unsigned int size, char
|
|||||||
msr->json->status = yajl_parse(msr->json->handle, buf, size);
|
msr->json->status = yajl_parse(msr->json->handle, buf, size);
|
||||||
if (msr->json->status != yajl_status_ok) {
|
if (msr->json->status != yajl_status_ok) {
|
||||||
/* We need to free the yajl error message later, how to do this? */
|
/* We need to free the yajl error message later, how to do this? */
|
||||||
*error_msg = yajl_get_error(msr->json->handle, 0, buf, size);
|
if (msr->json->depth_limit_exceeded) {
|
||||||
|
*error_msg = "JSON depth limit exceeded";
|
||||||
|
} else {
|
||||||
|
*error_msg = yajl_get_error(msr->json->handle, 0, NULL, 0);
|
||||||
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,7 +376,12 @@ int json_complete(modsec_rec *msr, char **error_msg) {
|
|||||||
msr->json->status = yajl_complete_parse(msr->json->handle);
|
msr->json->status = yajl_complete_parse(msr->json->handle);
|
||||||
if (msr->json->status != yajl_status_ok) {
|
if (msr->json->status != yajl_status_ok) {
|
||||||
/* We need to free the yajl error message later, how to do this? */
|
/* We need to free the yajl error message later, how to do this? */
|
||||||
*error_msg = yajl_get_error(msr->json->handle, 0, NULL, 0);
|
if (msr->json->depth_limit_exceeded) {
|
||||||
|
*error_msg = "JSON depth limit exceeded";
|
||||||
|
} else {
|
||||||
|
*error_msg = yajl_get_error(msr->json->handle, 0, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -40,6 +40,8 @@ struct json_data {
|
|||||||
/* prefix is used to create data hierarchy (i.e., 'parent.child.value') */
|
/* prefix is used to create data hierarchy (i.e., 'parent.child.value') */
|
||||||
unsigned char *prefix;
|
unsigned char *prefix;
|
||||||
unsigned char *current_key;
|
unsigned char *current_key;
|
||||||
|
long int current_depth;
|
||||||
|
int depth_limit_exceeded;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Functions */
|
/* Functions */
|
||||||
|
@@ -156,5 +156,74 @@
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "rule",
|
||||||
|
comment => "json parser - parsing depth not exceeded",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRequestBodyJsonDepthLimit 5
|
||||||
|
SecRule REQUEST_HEADERS:Content-Type "application/json" \\
|
||||||
|
"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
|
||||||
|
SecRule REQBODY_ERROR "!\@eq 0" "id:'200442',phase:2,log,deny,status:403,msg:'Failed to parse request body'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/key/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/json",
|
||||||
|
],
|
||||||
|
normalize_raw_request_data(
|
||||||
|
q(
|
||||||
|
{
|
||||||
|
"key1":{"key2":{"key3":{"key4":{"key5":"thevalue"}}}}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "rule",
|
||||||
|
comment => "json parser - parsing depth exceeded",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRequestBodyJsonDepthLimit 3
|
||||||
|
SecRule REQUEST_HEADERS:Content-Type "application/json" \\
|
||||||
|
"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
|
||||||
|
SecRule REQBODY_ERROR "!\@eq 0" "id:'200443',phase:2,log,deny,status:403,msg:'Failed to parse request body'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
audit => [ qr/JSON parsing error: JSON depth limit exceeded/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/json",
|
||||||
|
],
|
||||||
|
normalize_raw_request_data(
|
||||||
|
q(
|
||||||
|
{
|
||||||
|
"key1":{"key2":{"key3":{"key4":{"key5":"thevalue"}}}}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user