mirror of
https://github.com/owasp-modsecurity/ModSecurity.git
synced 2025-08-14 05:45:59 +03:00
Support configurable limit on depth of JSON parsing
This commit is contained in:
parent
ec86b242e1
commit
ac79c1c29b
5
CHANGES
5
CHANGES
@ -1,9 +1,8 @@
|
|||||||
v3.x.y - YYYY-MMM-DD (to be released)
|
v3.x.y - YYYY-MMM-DD (to be released)
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
|
- Support configurable limit on depth of JSON parsing
|
||||||
|
[@theMiddleBlue, @martinhsv]
|
||||||
|
|
||||||
|
|
||||||
v3.0.5 - 2021-Jul-07
|
v3.0.5 - 2021-Jul-07
|
||||||
--------------------
|
--------------------
|
||||||
|
@ -366,6 +366,7 @@ class RulesSetProperties {
|
|||||||
PropertyNotSetConfigBoolean);
|
PropertyNotSetConfigBoolean);
|
||||||
|
|
||||||
to->m_argumentsLimit.merge(&from->m_argumentsLimit);
|
to->m_argumentsLimit.merge(&from->m_argumentsLimit);
|
||||||
|
to->m_requestBodyJsonDepthLimit.merge(&from->m_requestBodyJsonDepthLimit);
|
||||||
to->m_requestBodyLimit.merge(&from->m_requestBodyLimit);
|
to->m_requestBodyLimit.merge(&from->m_requestBodyLimit);
|
||||||
to->m_responseBodyLimit.merge(&from->m_responseBodyLimit);
|
to->m_responseBodyLimit.merge(&from->m_responseBodyLimit);
|
||||||
|
|
||||||
@ -464,6 +465,7 @@ class RulesSetProperties {
|
|||||||
ConfigBoolean m_tmpSaveUploadedFiles;
|
ConfigBoolean m_tmpSaveUploadedFiles;
|
||||||
ConfigBoolean m_uploadKeepFiles;
|
ConfigBoolean m_uploadKeepFiles;
|
||||||
ConfigDouble m_argumentsLimit;
|
ConfigDouble m_argumentsLimit;
|
||||||
|
ConfigDouble m_requestBodyJsonDepthLimit;
|
||||||
ConfigDouble m_requestBodyLimit;
|
ConfigDouble m_requestBodyLimit;
|
||||||
ConfigDouble m_requestBodyNoFilesLimit;
|
ConfigDouble m_requestBodyNoFilesLimit;
|
||||||
ConfigDouble m_responseBodyLimit;
|
ConfigDouble m_responseBodyLimit;
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -604,6 +604,7 @@ using namespace modsecurity::operators;
|
|||||||
CONFIG_SEC_CONN_W_STATE_LIMIT "CONFIG_SEC_CONN_W_STATE_LIMIT"
|
CONFIG_SEC_CONN_W_STATE_LIMIT "CONFIG_SEC_CONN_W_STATE_LIMIT"
|
||||||
CONFIG_SEC_SENSOR_ID "CONFIG_SEC_SENSOR_ID"
|
CONFIG_SEC_SENSOR_ID "CONFIG_SEC_SENSOR_ID"
|
||||||
CONFIG_DIR_ARGS_LIMIT "CONFIG_DIR_ARGS_LIMIT"
|
CONFIG_DIR_ARGS_LIMIT "CONFIG_DIR_ARGS_LIMIT"
|
||||||
|
CONFIG_DIR_REQ_BODY_JSON_DEPTH_LIMIT "CONFIG_DIR_REQ_BODY_JSON_DEPTH_LIMIT"
|
||||||
CONFIG_DIR_REQ_BODY "CONFIG_DIR_REQ_BODY"
|
CONFIG_DIR_REQ_BODY "CONFIG_DIR_REQ_BODY"
|
||||||
CONFIG_DIR_REQ_BODY_IN_MEMORY_LIMIT "CONFIG_DIR_REQ_BODY_IN_MEMORY_LIMIT"
|
CONFIG_DIR_REQ_BODY_IN_MEMORY_LIMIT "CONFIG_DIR_REQ_BODY_IN_MEMORY_LIMIT"
|
||||||
CONFIG_DIR_REQ_BODY_LIMIT "CONFIG_DIR_REQ_BODY_LIMIT"
|
CONFIG_DIR_REQ_BODY_LIMIT "CONFIG_DIR_REQ_BODY_LIMIT"
|
||||||
@ -1582,6 +1583,11 @@ expression:
|
|||||||
driver.m_argumentsLimit.m_set = true;
|
driver.m_argumentsLimit.m_set = true;
|
||||||
driver.m_argumentsLimit.m_value = atoi($1.c_str());
|
driver.m_argumentsLimit.m_value = atoi($1.c_str());
|
||||||
}
|
}
|
||||||
|
| CONFIG_DIR_REQ_BODY_JSON_DEPTH_LIMIT
|
||||||
|
{
|
||||||
|
driver.m_requestBodyJsonDepthLimit.m_set = true;
|
||||||
|
driver.m_requestBodyJsonDepthLimit.m_value = atoi($1.c_str());
|
||||||
|
}
|
||||||
/* Body limits */
|
/* Body limits */
|
||||||
| CONFIG_DIR_REQ_BODY_LIMIT
|
| CONFIG_DIR_REQ_BODY_LIMIT
|
||||||
{
|
{
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -361,6 +361,7 @@ CONFIG_SEC_STREAM_OUT_BODY_INSPECTION (?i:SecStreamOutBodyInspection)
|
|||||||
CONFIG_DIR_PCRE_MATCH_LIMIT (?i:SecPcreMatchLimit)
|
CONFIG_DIR_PCRE_MATCH_LIMIT (?i:SecPcreMatchLimit)
|
||||||
CONFIG_DIR_PCRE_MATCH_LIMIT_RECURSION (?i:SecPcreMatchLimitRecursion)
|
CONFIG_DIR_PCRE_MATCH_LIMIT_RECURSION (?i:SecPcreMatchLimitRecursion)
|
||||||
CONFIG_DIR_ARGS_LIMIT (?i:SecArgumentsLimit)
|
CONFIG_DIR_ARGS_LIMIT (?i:SecArgumentsLimit)
|
||||||
|
CONFIG_DIR_REQ_BODY_JSON_DEPTH_LIMIT (?i:SecRequestBodyJsonDepthLimit)
|
||||||
CONFIG_DIR_REQ_BODY (?i:SecRequestBodyAccess)
|
CONFIG_DIR_REQ_BODY (?i:SecRequestBodyAccess)
|
||||||
CONFIG_DIR_REQ_BODY_IN_MEMORY_LIMIT (?i:SecRequestBodyInMemoryLimit)
|
CONFIG_DIR_REQ_BODY_IN_MEMORY_LIMIT (?i:SecRequestBodyInMemoryLimit)
|
||||||
CONFIG_DIR_REQ_BODY_LIMIT (?i:SecRequestBodyLimit)
|
CONFIG_DIR_REQ_BODY_LIMIT (?i:SecRequestBodyLimit)
|
||||||
@ -769,6 +770,7 @@ EQUALS_MINUS (?i:=\-)
|
|||||||
{CONFIG_DIR_PCRE_MATCH_LIMIT_RECURSION}[ \t]+{CONFIG_VALUE_NUMBER} { return p::make_CONFIG_DIR_PCRE_MATCH_LIMIT_RECURSION(strchr(yytext, ' ') + 1, *driver.loc.back()); }
|
{CONFIG_DIR_PCRE_MATCH_LIMIT_RECURSION}[ \t]+{CONFIG_VALUE_NUMBER} { return p::make_CONFIG_DIR_PCRE_MATCH_LIMIT_RECURSION(strchr(yytext, ' ') + 1, *driver.loc.back()); }
|
||||||
{CONFIG_DIR_PCRE_MATCH_LIMIT}[ \t]+{CONFIG_VALUE_NUMBER} { return p::make_CONFIG_DIR_PCRE_MATCH_LIMIT(strchr(yytext, ' ') + 1, *driver.loc.back()); }
|
{CONFIG_DIR_PCRE_MATCH_LIMIT}[ \t]+{CONFIG_VALUE_NUMBER} { return p::make_CONFIG_DIR_PCRE_MATCH_LIMIT(strchr(yytext, ' ') + 1, *driver.loc.back()); }
|
||||||
{CONFIG_DIR_ARGS_LIMIT}[ \t]+{CONFIG_VALUE_NUMBER} { return p::make_CONFIG_DIR_ARGS_LIMIT(strchr(yytext, ' ') + 1, *driver.loc.back()); }
|
{CONFIG_DIR_ARGS_LIMIT}[ \t]+{CONFIG_VALUE_NUMBER} { return p::make_CONFIG_DIR_ARGS_LIMIT(strchr(yytext, ' ') + 1, *driver.loc.back()); }
|
||||||
|
{CONFIG_DIR_REQ_BODY_JSON_DEPTH_LIMIT}[ \t]+{CONFIG_VALUE_NUMBER} { return p::make_CONFIG_DIR_REQ_BODY_JSON_DEPTH_LIMIT(strchr(yytext, ' ') + 1, *driver.loc.back()); }
|
||||||
{CONFIG_DIR_REQ_BODY_IN_MEMORY_LIMIT}[ \t]+{CONFIG_VALUE_NUMBER} { return p::make_CONFIG_DIR_REQ_BODY_IN_MEMORY_LIMIT(strchr(yytext, ' ') + 1, *driver.loc.back()); }
|
{CONFIG_DIR_REQ_BODY_IN_MEMORY_LIMIT}[ \t]+{CONFIG_VALUE_NUMBER} { return p::make_CONFIG_DIR_REQ_BODY_IN_MEMORY_LIMIT(strchr(yytext, ' ') + 1, *driver.loc.back()); }
|
||||||
|
|
||||||
{CONFIG_DIR_REQ_BODY_LIMIT_ACTION} { return p::make_CONFIG_DIR_REQ_BODY_LIMIT_ACTION(yytext, *driver.loc.back()); }
|
{CONFIG_DIR_REQ_BODY_LIMIT_ACTION} { return p::make_CONFIG_DIR_REQ_BODY_LIMIT_ACTION(yytext, *driver.loc.back()); }
|
||||||
|
@ -26,9 +26,15 @@
|
|||||||
namespace modsecurity {
|
namespace modsecurity {
|
||||||
namespace RequestBodyProcessor {
|
namespace RequestBodyProcessor {
|
||||||
|
|
||||||
|
static const double json_depth_limit_default = 10000.0;
|
||||||
|
static const char* json_depth_limit_exceeded_msg = ". Parsing depth limit exceeded";
|
||||||
|
|
||||||
JSON::JSON(Transaction *transaction) : m_transaction(transaction),
|
JSON::JSON(Transaction *transaction) : m_transaction(transaction),
|
||||||
m_handle(NULL),
|
m_handle(NULL),
|
||||||
m_current_key("") {
|
m_current_key(""),
|
||||||
|
m_max_depth(json_depth_limit_default),
|
||||||
|
m_current_depth(0),
|
||||||
|
m_depth_limit_exceeded(false) {
|
||||||
/**
|
/**
|
||||||
* yajl callback functions
|
* yajl callback functions
|
||||||
* For more information on the function signatures and order, check
|
* For more information on the function signatures and order, check
|
||||||
@ -91,6 +97,9 @@ bool JSON::processChunk(const char *buf, unsigned int size, std::string *err) {
|
|||||||
(const unsigned char *)buf, size);
|
(const unsigned char *)buf, size);
|
||||||
/* 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? */
|
||||||
err->assign((const char *)e);
|
err->assign((const char *)e);
|
||||||
|
if (m_depth_limit_exceeded) {
|
||||||
|
err->append(json_depth_limit_exceeded_msg);
|
||||||
|
}
|
||||||
yajl_free_error(m_handle, e);
|
yajl_free_error(m_handle, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -106,6 +115,9 @@ bool JSON::complete(std::string *err) {
|
|||||||
unsigned char *e = yajl_get_error(m_handle, 0, NULL, 0);
|
unsigned char *e = yajl_get_error(m_handle, 0, NULL, 0);
|
||||||
/* 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? */
|
||||||
err->assign((const char *)e);
|
err->assign((const char *)e);
|
||||||
|
if (m_depth_limit_exceeded) {
|
||||||
|
err->append(json_depth_limit_exceeded_msg);
|
||||||
|
}
|
||||||
yajl_free_error(m_handle, e);
|
yajl_free_error(m_handle, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -226,6 +238,11 @@ int JSON::yajl_start_array(void *ctx) {
|
|||||||
std::string name = tthis->getCurrentKey();
|
std::string name = tthis->getCurrentKey();
|
||||||
tthis->m_containers.push_back(
|
tthis->m_containers.push_back(
|
||||||
reinterpret_cast<JSONContainer *>(new JSONContainerArray(name)));
|
reinterpret_cast<JSONContainer *>(new JSONContainerArray(name)));
|
||||||
|
tthis->m_current_depth++;
|
||||||
|
if (tthis->m_current_depth > tthis->m_max_depth) {
|
||||||
|
tthis->m_depth_limit_exceeded = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,6 +250,7 @@ int JSON::yajl_start_array(void *ctx) {
|
|||||||
int JSON::yajl_end_array(void *ctx) {
|
int JSON::yajl_end_array(void *ctx) {
|
||||||
JSON *tthis = reinterpret_cast<JSON *>(ctx);
|
JSON *tthis = reinterpret_cast<JSON *>(ctx);
|
||||||
if (tthis->m_containers.empty()) {
|
if (tthis->m_containers.empty()) {
|
||||||
|
tthis->m_current_depth--;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,6 +264,7 @@ int JSON::yajl_end_array(void *ctx) {
|
|||||||
ja->m_elementCounter++;
|
ja->m_elementCounter++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tthis->m_current_depth--;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -256,6 +275,11 @@ int JSON::yajl_start_map(void *ctx) {
|
|||||||
std::string name(tthis->getCurrentKey());
|
std::string name(tthis->getCurrentKey());
|
||||||
tthis->m_containers.push_back(
|
tthis->m_containers.push_back(
|
||||||
reinterpret_cast<JSONContainer *>(new JSONContainerMap(name)));
|
reinterpret_cast<JSONContainer *>(new JSONContainerMap(name)));
|
||||||
|
tthis->m_current_depth++;
|
||||||
|
if (tthis->m_current_depth > tthis->m_max_depth) {
|
||||||
|
tthis->m_depth_limit_exceeded = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,6 +291,7 @@ int JSON::yajl_start_map(void *ctx) {
|
|||||||
int JSON::yajl_end_map(void *ctx) {
|
int JSON::yajl_end_map(void *ctx) {
|
||||||
JSON *tthis = reinterpret_cast<JSON *>(ctx);
|
JSON *tthis = reinterpret_cast<JSON *>(ctx);
|
||||||
if (tthis->m_containers.empty()) {
|
if (tthis->m_containers.empty()) {
|
||||||
|
tthis->m_current_depth--;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,6 +307,7 @@ int JSON::yajl_end_map(void *ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tthis->m_current_depth--;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,6 @@
|
|||||||
#include "modsecurity/rules_set.h"
|
#include "modsecurity/rules_set.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace modsecurity {
|
namespace modsecurity {
|
||||||
namespace RequestBodyProcessor {
|
namespace RequestBodyProcessor {
|
||||||
|
|
||||||
@ -104,12 +103,19 @@ class JSON {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setMaxDepth(double max_depth) {
|
||||||
|
m_max_depth = max_depth;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::deque<JSONContainer *> m_containers;
|
std::deque<JSONContainer *> m_containers;
|
||||||
Transaction *m_transaction;
|
Transaction *m_transaction;
|
||||||
yajl_handle m_handle;
|
yajl_handle m_handle;
|
||||||
yajl_status m_status;
|
yajl_status m_status;
|
||||||
std::string m_current_key;
|
std::string m_current_key;
|
||||||
|
double m_max_depth;
|
||||||
|
int64_t m_current_depth;
|
||||||
|
bool m_depth_limit_exceeded;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -830,6 +830,9 @@ int Transaction::processRequestBody() {
|
|||||||
if (m_requestBodyProcessor == JSONRequestBody) {
|
if (m_requestBodyProcessor == JSONRequestBody) {
|
||||||
#endif
|
#endif
|
||||||
std::string error;
|
std::string error;
|
||||||
|
if (m_rules->m_requestBodyJsonDepthLimit.m_set) {
|
||||||
|
m_json->setMaxDepth(m_rules->m_requestBodyJsonDepthLimit.m_value);
|
||||||
|
}
|
||||||
if (m_json->init() == true) {
|
if (m_json->init() == true) {
|
||||||
m_json->processChunk(m_requestBody.str().c_str(),
|
m_json->processChunk(m_requestBody.str().c_str(),
|
||||||
m_requestBody.str().size(),
|
m_requestBody.str().size(),
|
||||||
|
@ -150,6 +150,95 @@
|
|||||||
"SecRule REQUEST_HEADERS:Content-Type \"application/json\" \"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON\"",
|
"SecRule REQUEST_HEADERS:Content-Type \"application/json\" \"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON\"",
|
||||||
"SecRule REQBODY_ERROR \"0\" \"id:'200441',phase:3,log\""
|
"SecRule REQBODY_ERROR \"0\" \"id:'200441',phase:3,log\""
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enabled":1,
|
||||||
|
"version_min":300000,
|
||||||
|
"title":"Testing JSON request body parser - depth not over limit",
|
||||||
|
"client":{
|
||||||
|
"ip":"200.249.12.31",
|
||||||
|
"port":123
|
||||||
|
},
|
||||||
|
"server":{
|
||||||
|
"ip":"200.249.12.31",
|
||||||
|
"port":80
|
||||||
|
},
|
||||||
|
"request":{
|
||||||
|
"headers":{
|
||||||
|
"Host":"localhost",
|
||||||
|
"User-Agent":"curl/7.38.0",
|
||||||
|
"Content-Type":"application/json"
|
||||||
|
},
|
||||||
|
"uri":"/?foo=bar",
|
||||||
|
"method":"POST",
|
||||||
|
"body": [
|
||||||
|
"{",
|
||||||
|
" \"key1\":",
|
||||||
|
"{",
|
||||||
|
" \"key2\":",
|
||||||
|
"{",
|
||||||
|
" \"key3\":",
|
||||||
|
"{",
|
||||||
|
" \"key4\":",
|
||||||
|
"{",
|
||||||
|
" \"key5\":\"thevalue\"",
|
||||||
|
"}}}}}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"expected":{
|
||||||
|
"debug_log": "json.key1.key2.key3.key4.key5",
|
||||||
|
"http_code":200
|
||||||
|
},
|
||||||
|
"rules":[
|
||||||
|
"SecRuleEngine On",
|
||||||
|
"SecRequestBodyJsonDepthLimit 5",
|
||||||
|
"SecRule REQUEST_HEADERS:Content-Type \"application/json\" \"id:'200001',phase:1,t:none,pass,nolog,ctl:requestBodyProcessor=JSON\"",
|
||||||
|
"SecRule REQBODY_ERROR \"!@eq 0\" \"id:'200002', phase:2,t:none,log,deny,status:403,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}'\""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enabled":1,
|
||||||
|
"version_min":300000,
|
||||||
|
"title":"Testing JSON request body parser - depth over limit",
|
||||||
|
"client":{
|
||||||
|
"ip":"200.249.12.31",
|
||||||
|
"port":123
|
||||||
|
},
|
||||||
|
"server":{
|
||||||
|
"ip":"200.249.12.31",
|
||||||
|
"port":80
|
||||||
|
},
|
||||||
|
"request":{
|
||||||
|
"headers":{
|
||||||
|
"Host":"localhost",
|
||||||
|
"User-Agent":"curl/7.38.0",
|
||||||
|
"Content-Type":"application/json"
|
||||||
|
},
|
||||||
|
"uri":"/?foo=bar",
|
||||||
|
"method":"POST",
|
||||||
|
"body": [
|
||||||
|
"{",
|
||||||
|
" \"key1\":",
|
||||||
|
"{",
|
||||||
|
" \"key2\":",
|
||||||
|
"{",
|
||||||
|
" \"key3\":",
|
||||||
|
"{",
|
||||||
|
" \"key4\":",
|
||||||
|
"{",
|
||||||
|
" \"key5\":\"thevalue\"",
|
||||||
|
"}}}}}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"expected":{
|
||||||
|
"debug_log": "Failed to parse request body",
|
||||||
|
"http_code":403
|
||||||
|
},
|
||||||
|
"rules":[
|
||||||
|
"SecRuleEngine On",
|
||||||
|
"SecRequestBodyJsonDepthLimit 4",
|
||||||
|
"SecRule REQUEST_HEADERS:Content-Type \"application/json\" \"id:'200001',phase:1,t:none,pass,nolog,ctl:requestBodyProcessor=JSON\"",
|
||||||
|
"SecRule REQBODY_ERROR \"!@eq 0\" \"id:'200002', phase:2,t:none,log,deny,status:403,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}'\""
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user