Support configurable limit on depth of JSON parsing

This commit is contained in:
Martin Vierula
2021-11-15 18:51:25 -08:00
parent ec86b242e1
commit ac79c1c29b
11 changed files with 5749 additions and 5554 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -604,6 +604,7 @@ using namespace modsecurity::operators;
CONFIG_SEC_CONN_W_STATE_LIMIT "CONFIG_SEC_CONN_W_STATE_LIMIT"
CONFIG_SEC_SENSOR_ID "CONFIG_SEC_SENSOR_ID"
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_IN_MEMORY_LIMIT "CONFIG_DIR_REQ_BODY_IN_MEMORY_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_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 */
| CONFIG_DIR_REQ_BODY_LIMIT
{

File diff suppressed because it is too large Load Diff

View File

@@ -361,6 +361,7 @@ CONFIG_SEC_STREAM_OUT_BODY_INSPECTION (?i:SecStreamOutBodyInspection)
CONFIG_DIR_PCRE_MATCH_LIMIT (?i:SecPcreMatchLimit)
CONFIG_DIR_PCRE_MATCH_LIMIT_RECURSION (?i:SecPcreMatchLimitRecursion)
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_IN_MEMORY_LIMIT (?i:SecRequestBodyInMemoryLimit)
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}[ \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_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_LIMIT_ACTION} { return p::make_CONFIG_DIR_REQ_BODY_LIMIT_ACTION(yytext, *driver.loc.back()); }

View File

@@ -26,9 +26,15 @@
namespace modsecurity {
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),
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
* 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);
/* We need to free the yajl error message later, how to do this? */
err->assign((const char *)e);
if (m_depth_limit_exceeded) {
err->append(json_depth_limit_exceeded_msg);
}
yajl_free_error(m_handle, e);
return false;
}
@@ -106,6 +115,9 @@ bool JSON::complete(std::string *err) {
unsigned char *e = yajl_get_error(m_handle, 0, NULL, 0);
/* We need to free the yajl error message later, how to do this? */
err->assign((const char *)e);
if (m_depth_limit_exceeded) {
err->append(json_depth_limit_exceeded_msg);
}
yajl_free_error(m_handle, e);
return false;
}
@@ -226,6 +238,11 @@ int JSON::yajl_start_array(void *ctx) {
std::string name = tthis->getCurrentKey();
tthis->m_containers.push_back(
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;
}
@@ -233,6 +250,7 @@ int JSON::yajl_start_array(void *ctx) {
int JSON::yajl_end_array(void *ctx) {
JSON *tthis = reinterpret_cast<JSON *>(ctx);
if (tthis->m_containers.empty()) {
tthis->m_current_depth--;
return 1;
}
@@ -246,6 +264,7 @@ int JSON::yajl_end_array(void *ctx) {
ja->m_elementCounter++;
}
}
tthis->m_current_depth--;
return 1;
}
@@ -256,6 +275,11 @@ int JSON::yajl_start_map(void *ctx) {
std::string name(tthis->getCurrentKey());
tthis->m_containers.push_back(
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;
}
@@ -267,6 +291,7 @@ int JSON::yajl_start_map(void *ctx) {
int JSON::yajl_end_map(void *ctx) {
JSON *tthis = reinterpret_cast<JSON *>(ctx);
if (tthis->m_containers.empty()) {
tthis->m_current_depth--;
return 1;
}
@@ -282,6 +307,7 @@ int JSON::yajl_end_map(void *ctx) {
}
}
tthis->m_current_depth--;
return 1;
}

View File

@@ -29,7 +29,6 @@
#include "modsecurity/rules_set.h"
namespace modsecurity {
namespace RequestBodyProcessor {
@@ -104,12 +103,19 @@ class JSON {
return ret;
}
void setMaxDepth(double max_depth) {
m_max_depth = max_depth;
}
private:
std::deque<JSONContainer *> m_containers;
Transaction *m_transaction;
yajl_handle m_handle;
yajl_status m_status;
std::string m_current_key;
double m_max_depth;
int64_t m_current_depth;
bool m_depth_limit_exceeded;
};

View File

@@ -830,6 +830,9 @@ int Transaction::processRequestBody() {
if (m_requestBodyProcessor == JSONRequestBody) {
#endif
std::string error;
if (m_rules->m_requestBodyJsonDepthLimit.m_set) {
m_json->setMaxDepth(m_rules->m_requestBodyJsonDepthLimit.m_value);
}
if (m_json->init() == true) {
m_json->processChunk(m_requestBody.str().c_str(),
m_requestBody.str().size(),