Finish XMLArgs processing in v3

This commit is contained in:
Ervin Hegedus 2025-04-20 18:21:28 +02:00
parent 01a0615887
commit 9e41a53760
No known key found for this signature in database
GPG Key ID: 5FA5BC3F5EC41F61
12 changed files with 8399 additions and 7362 deletions

View File

@ -52,6 +52,11 @@
to = (from == PropertyNotSetBodyLimitAction) ? default : from; \
}
#define merge_xmlargparse_value(to, from, default) \
if (to == PropertyNotSetConfigXMLParseXmlIntoArgs) { \
to = (from == PropertyNotSetConfigXMLParseXmlIntoArgs) ? default : from; \
}
#ifdef __cplusplus
namespace modsecurity {
@ -177,6 +182,7 @@ class RulesSetProperties {
m_secRequestBodyAccess(PropertyNotSetConfigBoolean),
m_secResponseBodyAccess(PropertyNotSetConfigBoolean),
m_secXMLExternalEntity(PropertyNotSetConfigBoolean),
m_secXMLParseXmlIntoArgs(PropertyNotSetConfigXMLParseXmlIntoArgs),
m_tmpSaveUploadedFiles(PropertyNotSetConfigBoolean),
m_uploadKeepFiles(PropertyNotSetConfigBoolean),
m_debugLog(new DebugLog()),
@ -191,6 +197,7 @@ class RulesSetProperties {
m_secRequestBodyAccess(PropertyNotSetConfigBoolean),
m_secResponseBodyAccess(PropertyNotSetConfigBoolean),
m_secXMLExternalEntity(PropertyNotSetConfigBoolean),
m_secXMLParseXmlIntoArgs(PropertyNotSetConfigXMLParseXmlIntoArgs),
m_tmpSaveUploadedFiles(PropertyNotSetConfigBoolean),
m_uploadKeepFiles(PropertyNotSetConfigBoolean),
m_debugLog(debugLog),
@ -218,7 +225,9 @@ class RulesSetProperties {
/**
*
*
* The ConfigBoolean enumerator consists in mapping the different
* states of the configuration boolean values.
* The default value is PropertyNotSetConfigBoolean.
*/
enum ConfigBoolean {
TrueConfigBoolean,
@ -226,6 +235,18 @@ class RulesSetProperties {
PropertyNotSetConfigBoolean
};
/**
*
* The ConfigXMLParseXmlIntoArgs enumerator consists in mapping the
* different states of the configuration XMLParseXmlIntoArgs values.
* The default value is PropertyNotSetConfigXMLParseXmlIntoArgs.
*/
enum ConfigXMLParseXmlIntoArgs {
TrueConfigXMLParseXmlIntoArgs,
FalseConfigXMLParseXmlIntoArgs,
OnlyArgsConfigXMLParseXmlIntoArgs,
PropertyNotSetConfigXMLParseXmlIntoArgs
};
/**
*
@ -338,6 +359,19 @@ class RulesSetProperties {
}
}
static std::string configXMLParseXmlIntoArgsString(ConfigXMLParseXmlIntoArgs i) {
switch (i) {
case TrueConfigXMLParseXmlIntoArgs:
return "True";
case FalseConfigXMLParseXmlIntoArgs:
return "False";
case OnlyArgsConfigXMLParseXmlIntoArgs:
return "OnlyArgs";
case PropertyNotSetConfigXMLParseXmlIntoArgs:
default:
return "Not set";
}
}
static int mergeProperties(RulesSetProperties *from,
RulesSetProperties *to, std::ostringstream *err) {
@ -357,6 +391,10 @@ class RulesSetProperties {
from->m_secXMLExternalEntity,
PropertyNotSetConfigBoolean);
merge_xmlargparse_value(to->m_secXMLParseXmlIntoArgs,
from->m_secXMLParseXmlIntoArgs,
PropertyNotSetConfigXMLParseXmlIntoArgs);
merge_boolean_value(to->m_uploadKeepFiles,
from->m_uploadKeepFiles,
PropertyNotSetConfigBoolean);
@ -464,6 +502,7 @@ class RulesSetProperties {
ConfigBoolean m_secRequestBodyAccess;
ConfigBoolean m_secResponseBodyAccess;
ConfigBoolean m_secXMLExternalEntity;
ConfigXMLParseXmlIntoArgs m_secXMLParseXmlIntoArgs;
ConfigBoolean m_tmpSaveUploadedFiles;
ConfigBoolean m_uploadKeepFiles;
ConfigDouble m_argumentsLimit;

View File

@ -635,6 +635,8 @@ class Transaction : public TransactionAnchoredVariables, public TransactionSecMa
std::vector<std::shared_ptr<RequestBodyProcessor::MultipartPartTmpFile>> m_multipartPartTmpFiles;
int m_secXMLParseXmlIntoArgs;
private:
Transaction(ModSecurity *ms, RulesSet *rules, const char *id,

View File

@ -119,6 +119,7 @@ ACTIONS = \
actions/chain.cc \
actions/ctl/audit_log_parts.cc \
actions/ctl/audit_engine.cc \
actions/ctl/parse_xml_into_args.cc \
actions/ctl/rule_engine.cc \
actions/ctl/request_body_processor_json.cc \
actions/ctl/request_body_processor_xml.cc \

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -27,6 +27,7 @@ class Driver;
#include "src/actions/chain.h"
#include "src/actions/ctl/audit_engine.h"
#include "src/actions/ctl/audit_log_parts.h"
#include "src/actions/ctl/parse_xml_into_args.h"
#include "src/actions/ctl/request_body_access.h"
#include "src/actions/ctl/rule_engine.h"
#include "src/actions/ctl/request_body_processor_json.h"
@ -478,7 +479,7 @@ using namespace modsecurity::operators;
OPERATOR_VERIFY_CC "OPERATOR_VERIFY_CC"
OPERATOR_VERIFY_CPF "OPERATOR_VERIFY_CPF"
OPERATOR_VERIFY_SSN "OPERATOR_VERIFY_SSN"
OPERATOR_VERIFY_SVNR "OPERATOR_VERIFY_SVNR"
OPERATOR_VERIFY_SVNR "OPERATOR_VERIFY_SVNR"
OPERATOR_WITHIN "OPERATOR_WITHIN"
CONFIG_DIR_AUDIT_LOG_FMT
@ -502,6 +503,7 @@ using namespace modsecurity::operators;
ACTION_CTL_BDY_XML "ACTION_CTL_BDY_XML"
ACTION_CTL_BDY_URLENCODED "ACTION_CTL_BDY_URLENCODED"
ACTION_CTL_FORCE_REQ_BODY_VAR "ACTION_CTL_FORCE_REQ_BODY_VAR"
ACTION_CTL_PARSE_XML_INTO_ARGS "ACTION_CTL_PARSE_XML_INTO_ARGS"
ACTION_CTL_REQUEST_BODY_ACCESS "ACTION_CTL_REQUEST_BODY_ACCESS"
ACTION_CTL_RULE_REMOVE_BY_ID "ACTION_CTL_RULE_REMOVE_BY_ID"
ACTION_CTL_RULE_REMOVE_BY_TAG "ACTION_CTL_RULE_REMOVE_BY_TAG"
@ -649,6 +651,7 @@ using namespace modsecurity::operators;
CONFIG_VALUE_ABORT "CONFIG_VALUE_ABORT"
CONFIG_VALUE_DETC "CONFIG_VALUE_DETC"
CONFIG_VALUE_HTTPS "CONFIG_VALUE_HTTPS"
CONFIG_VALUE_ONLYARGS "CONFIG_VALUE_ONLYARGS"
CONFIG_VALUE_OFF "CONFIG_VALUE_OFF"
CONFIG_VALUE_ON "CONFIG_VALUE_ON"
CONFIG_VALUE_PARALLEL "CONFIG_VALUE_PARALLEL"
@ -658,6 +661,7 @@ using namespace modsecurity::operators;
CONFIG_VALUE_SERIAL "CONFIG_VALUE_SERIAL"
CONFIG_VALUE_WARN "CONFIG_VALUE_WARN"
CONFIG_XML_EXTERNAL_ENTITY "CONFIG_XML_EXTERNAL_ENTITY"
CONFIG_XML_PARSE_XML_INTO_ARGS "CONFIG_XML_PARSE_XML_INTO_ARGS"
CONGIG_DIR_RESPONSE_BODY_MP "CONGIG_DIR_RESPONSE_BODY_MP"
CONGIG_DIR_SEC_ARG_SEP "CONGIG_DIR_SEC_ARG_SEP"
CONGIG_DIR_SEC_COOKIE_FORMAT "CONGIG_DIR_SEC_COOKIE_FORMAT"
@ -1686,6 +1690,18 @@ expression:
{
driver.m_secXMLExternalEntity = modsecurity::RulesSetProperties::TrueConfigBoolean;
}
| CONFIG_XML_PARSE_XML_INTO_ARGS CONFIG_VALUE_ONLYARGS
{
driver.m_secXMLParseXmlIntoArgs = modsecurity::RulesSetProperties::OnlyArgsConfigXMLParseXmlIntoArgs;
}
| CONFIG_XML_PARSE_XML_INTO_ARGS CONFIG_VALUE_OFF
{
driver.m_secXMLParseXmlIntoArgs = modsecurity::RulesSetProperties::FalseConfigXMLParseXmlIntoArgs;
}
| CONFIG_XML_PARSE_XML_INTO_ARGS CONFIG_VALUE_ON
{
driver.m_secXMLParseXmlIntoArgs = modsecurity::RulesSetProperties::TrueConfigXMLParseXmlIntoArgs;
}
| CONGIG_DIR_SEC_TMP_DIR
{
/* Parser error disabled to avoid breaking default installations with modsecurity.conf-recommended
@ -2696,6 +2712,18 @@ act:
//ACTION_NOT_SUPPORTED("CtlForceReequestBody", @0);
ACTION_CONTAINER($$, new actions::Action($1));
}
| ACTION_CTL_PARSE_XML_INTO_ARGS CONFIG_VALUE_ON
{
ACTION_CONTAINER($$, new actions::ctl::ParseXmlIntoArgs("ctl:parseXmlIntoArgs=on"));
}
| ACTION_CTL_PARSE_XML_INTO_ARGS CONFIG_VALUE_OFF
{
ACTION_CONTAINER($$, new actions::ctl::ParseXmlIntoArgs("ctl:parseXmlIntoArgs=off"));
}
| ACTION_CTL_PARSE_XML_INTO_ARGS CONFIG_VALUE_ONLYARGS
{
ACTION_CONTAINER($$, new actions::ctl::ParseXmlIntoArgs("ctl:parseXmlIntoArgs=onlyargs"));
}
| ACTION_CTL_REQUEST_BODY_ACCESS CONFIG_VALUE_ON
{
ACTION_CONTAINER($$, new actions::ctl::RequestBodyAccess($1 + "true"));

File diff suppressed because it is too large Load Diff

View File

@ -90,6 +90,7 @@ ACTION_CTL_BDY_JSON (?i:ctl:requestBodyProcessor=JSO
ACTION_CTL_BDY_XML (?i:ctl:requestBodyProcessor=XML)
ACTION_CTL_BDY_URLENCODED (?i:ctl:requestBodyProcessor=URLENCODED)
ACTION_CTL_FORCE_REQ_BODY_VAR (?i:ctl:forceRequestBodyVariable)
ACTION_CTL_PARSE_XML_INTO_ARGS (?i:ctl:parseXMLintoArgs)
ACTION_CTL_REQUEST_BODY_ACCESS (?i:ctl:requestBodyAccess)
ACTION_CTL_RULE_ENGINE (?i:ctl:ruleEngine)
ACTION_CTL_RULE_REMOVE_BY_TAG (?i:ctl:ruleRemoveByTag)
@ -400,6 +401,7 @@ CONFIG_VALUE_ABORT (?i:Abort)
CONFIG_VALUE_DETC (?i:DetectionOnly)
CONFIG_VALUE_HTTPS (?i:https)
CONFIG_VALUE_NUMBER [0-9]+
CONFIG_VALUE_ONLYARGS (?i:OnlyArgs)
CONFIG_VALUE_OFF (?i:Off)
CONFIG_VALUE_ON (?i:On)
CONFIG_VALUE_PARALLEL (?i:Parallel|Concurrent)
@ -410,6 +412,7 @@ CONFIG_VALUE_RELEVANT_ONLY (?i:RelevantOnly)
CONFIG_VALUE_SERIAL (?i:Serial)
CONFIG_VALUE_WARN (?i:Warn)
CONFIG_XML_EXTERNAL_ENTITY (?i:SecXmlExternalEntity)
CONFIG_XML_PARSE_XML_INTO_ARGS (?i:SecParseXMLIntoArgs)
CONGIG_DIR_RESPONSE_BODY_MP (?i:SecResponseBodyMimeType)
CONGIG_DIR_RESPONSE_BODY_MP_CLEAR (?i:SecResponseBodyMimeTypesClear)
CONGIG_DIR_SEC_ARG_SEP (?i:SecArgumentSeparator)
@ -537,6 +540,7 @@ EQUALS_MINUS (?i:=\-)
{ACTION_CTL_BDY_XML} { return p::make_ACTION_CTL_BDY_XML(yytext, *driver.loc.back()); }
{ACTION_CTL_BDY_URLENCODED} { return p::make_ACTION_CTL_BDY_URLENCODED(yytext, *driver.loc.back()); }
{ACTION_CTL_FORCE_REQ_BODY_VAR}= { return p::make_ACTION_CTL_FORCE_REQ_BODY_VAR(yytext, *driver.loc.back()); }
{ACTION_CTL_PARSE_XML_INTO_ARGS}= { return p::make_ACTION_CTL_PARSE_XML_INTO_ARGS(yytext, *driver.loc.back()); }
{ACTION_CTL_REQUEST_BODY_ACCESS}= { return p::make_ACTION_CTL_REQUEST_BODY_ACCESS(yytext, *driver.loc.back()); }
{ACTION_CTL_RULE_ENGINE}= { return p::make_ACTION_CTL_RULE_ENGINE(*driver.loc.back()); }
{ACTION_CTL_RULE_REMOVE_BY_ID}[=]{REMOVE_RULE_BY} { return p::make_ACTION_CTL_RULE_REMOVE_BY_ID(yytext, *driver.loc.back()); }
@ -609,6 +613,7 @@ EQUALS_MINUS (?i:=\-)
{ACTION_LOG_DATA}: { BEGIN(EXPECTING_ACTION_PREDICATE); return p::make_ACTION_LOG_DATA(yytext, *driver.loc.back()); }
{CONFIG_VALUE_DETC} { return p::make_CONFIG_VALUE_DETC(yytext, *driver.loc.back()); }
{CONFIG_VALUE_ONLYARGS} { return p::make_CONFIG_VALUE_ONLYARGS(yytext, *driver.loc.back()); }
{CONFIG_VALUE_OFF} { return p::make_CONFIG_VALUE_OFF(yytext, *driver.loc.back()); }
{CONFIG_VALUE_ON} { return p::make_CONFIG_VALUE_ON(yytext, *driver.loc.back()); }
{CONFIG_VALUE_RELEVANT_ONLY} { return p::make_CONFIG_VALUE_RELEVANT_ONLY(yytext, *driver.loc.back()); }
@ -805,6 +810,7 @@ EQUALS_MINUS (?i:=\-)
{CONFIG_VALUE_ABORT} { return p::make_CONFIG_VALUE_ABORT(yytext, *driver.loc.back()); }
{CONFIG_VALUE_DETC} { return p::make_CONFIG_VALUE_DETC(yytext, *driver.loc.back()); }
{CONFIG_VALUE_HTTPS} { return p::make_CONFIG_VALUE_HTTPS(yytext, *driver.loc.back()); }
{CONFIG_VALUE_ONLYARGS} { return p::make_CONFIG_VALUE_ONLYARGS(yytext, *driver.loc.back()); }
{CONFIG_VALUE_OFF} { return p::make_CONFIG_VALUE_OFF(yytext, *driver.loc.back()); }
{CONFIG_VALUE_ON} { return p::make_CONFIG_VALUE_ON(yytext, *driver.loc.back()); }
{CONFIG_VALUE_PARALLEL} { return p::make_CONFIG_VALUE_PARALLEL(yytext, *driver.loc.back()); }
@ -814,6 +820,7 @@ EQUALS_MINUS (?i:=\-)
{CONFIG_VALUE_SERIAL} { return p::make_CONFIG_VALUE_SERIAL(yytext, *driver.loc.back()); }
{CONFIG_VALUE_WARN} { return p::make_CONFIG_VALUE_WARN(yytext, *driver.loc.back()); }
{CONFIG_XML_EXTERNAL_ENTITY} { return p::make_CONFIG_XML_EXTERNAL_ENTITY(yytext, *driver.loc.back()); }
{CONFIG_XML_PARSE_XML_INTO_ARGS} { return p::make_CONFIG_XML_PARSE_XML_INTO_ARGS(yytext, *driver.loc.back()); }
{CONGIG_DIR_RESPONSE_BODY_MP}[ \t]+{FREE_TEXT_NEW_LINE} { return p::make_CONGIG_DIR_RESPONSE_BODY_MP(strchr(yytext, ' ') + 1, *driver.loc.back()); }
{CONGIG_DIR_RESPONSE_BODY_MP_CLEAR} { return p::make_CONGIG_DIR_RESPONSE_BODY_MP_CLEAR(*driver.loc.back()); }
{CONGIG_DIR_SEC_ARG_SEP}[ \t]+{FREE_TEXT_NEW_LINE} { return p::make_CONGIG_DIR_SEC_ARG_SEP(yytext, *driver.loc.back()); }

View File

@ -25,11 +25,122 @@ namespace RequestBodyProcessor {
#ifdef WITH_LIBXML2
/*
* NodeData for parsing XML into args
*/
NodeData::NodeData() {
has_child = false;
}
NodeData::~NodeData() {};
/*
* XMLNodes for parsing XML into args
*/
XMLNodes::XMLNodes(Transaction *transaction) {
nodes = {};
node_depth = 0;
currpath = "";
currval = "";
m_transaction = transaction;
}
XMLNodes::~XMLNodes() {};
/*
* SAX handler for parsing XML into args
*/
class MSCSAXHandler {
public:
void onStartElement(void * ctx, const xmlChar *localname) {
std::string name = reinterpret_cast<const char*>(localname);
XMLNodes* xml_data = static_cast<XMLNodes*>(ctx);
xml_data->nodes.push_back(std::make_shared<NodeData>());
xml_data->node_depth++;
// FIXME - later if we want to check the depth of XML tree
/* if (max_depth > 0 && max_depth > xml_data->node_depth) {
std::cout << "Depth of XML tree reached the given maximum value " << xml_data->node_depth << std::endl;
exit(1);
} */
// if it's not the first (root) item, then append a '.'
// note, this can't occur because there is always a pseudo root element: 'xml'
if (xml_data->nodes.size() > 1) {
xml_data->currpath.append(".");
xml_data->nodes[xml_data->nodes.size()-1]->has_child = true;
}
xml_data->currpath.append(name);
}
void onEndElement(void * ctx, const xmlChar *localname) {
std::string name = reinterpret_cast<const char*>(localname);
XMLNodes* xml_data = static_cast<XMLNodes*>(ctx);
std::shared_ptr<NodeData>& nd = xml_data->nodes[xml_data->nodes.size()-1];
if (nd->has_child == true) {
// check the return value
// if it false, then stop parsing
// this means the number of arguments reached the limit
if (xml_data->m_transaction->addArgument("XML", xml_data->currpath, xml_data->currval, 0) == false) {
xmlStopParser(xml_data->parsing_ctx_arg);
}
}
if (xml_data->currpath.length() > 0) {
// set an offset to store this is the first item or not -> remove the '.' or not
int offset = (xml_data->nodes.size() > 1) ? 1 : 0;
xml_data->currpath.erase(xml_data->currpath.length() - (name.length()+offset));
}
xml_data->nodes.pop_back();
xml_data->node_depth--;
}
void onCharacters(void *ctx, const xmlChar *ch, int len) {
XMLNodes* xml_data = static_cast<XMLNodes*>(ctx);
std::string content(reinterpret_cast<const char *>(ch), len);
xml_data->currval = content;
}
};
extern "C" {
void MSC_startElement(void *userData,
const xmlChar *name,
const xmlChar *prefix,
const xmlChar *URI,
int nb_namespaces,
const xmlChar **namespaces,
int nb_attributes,
int nb_defaulted,
const xmlChar **attributes) {
MSCSAXHandler* handler = static_cast<MSCSAXHandler*>(userData);
handler->onStartElement(userData, name);
}
void MSC_endElement(
void *userData,
const xmlChar *name,
const xmlChar* prefix,
const xmlChar* URI) {
MSCSAXHandler* handler = static_cast<MSCSAXHandler*>(userData);
handler->onEndElement(userData, name);
}
void MSC_xmlcharacters(void *userData, const xmlChar *ch, int len) {
MSCSAXHandler* handler = static_cast<MSCSAXHandler*>(userData);
handler->onCharacters(userData, ch, len);
}
}
XML::XML(Transaction *transaction)
: m_transaction(transaction) {
m_data.doc = NULL;
m_data.parsing_ctx = NULL;
m_data.sax_handler = NULL;
m_data.xml_error = "";
m_data.parsing_ctx_arg = NULL;
m_data.xml_parser_state = NULL;
}
@ -44,7 +155,6 @@ XML::~XML() {
}
}
bool XML::init() {
//xmlParserInputBufferCreateFilenameFunc entity;
if (m_transaction->m_rules->m_secXMLExternalEntity
@ -55,6 +165,27 @@ bool XML::init() {
/*entity = */xmlParserInputBufferCreateFilenameDefault(
this->unloadExternalEntity);
}
if (m_transaction->m_secXMLParseXmlIntoArgs
== RulesSetProperties::TrueConfigXMLParseXmlIntoArgs ||
m_transaction->m_secXMLParseXmlIntoArgs
== RulesSetProperties::OnlyArgsConfigXMLParseXmlIntoArgs) {
ms_dbg_a(m_transaction, 9,
"XML: SecParseXMLIntoArgs is set to " \
+ RulesSetProperties::configXMLParseXmlIntoArgsString(static_cast<RulesSetProperties::ConfigXMLParseXmlIntoArgs>(m_transaction->m_secXMLParseXmlIntoArgs)));
m_data.sax_handler = std::make_unique<xmlSAXHandler>();
memset(m_data.sax_handler.get(), 0, sizeof(xmlSAXHandler));
m_data.sax_handler->initialized = XML_SAX2_MAGIC;
m_data.sax_handler->startElementNs = &MSC_startElement;
m_data.sax_handler->endElementNs = &MSC_endElement;
m_data.sax_handler->characters = &MSC_xmlcharacters;
// set the parser state struct
m_data.xml_parser_state = std::make_unique<XMLNodes>(m_transaction);
m_data.xml_parser_state->node_depth = 0;
m_data.xml_parser_state->currval = "";
m_data.xml_parser_state->currpath = "xml.";
}
return true;
}
@ -72,7 +203,7 @@ bool XML::processChunk(const char *buf, unsigned int size,
* enable us to pass it the first chunk of data so that
* it can attempt to auto-detect the encoding.
*/
if (m_data.parsing_ctx == NULL) {
if (m_data.parsing_ctx == NULL && m_data.parsing_ctx_arg == NULL) {
/* First invocation. */
ms_dbg_a(m_transaction, 4, "XML: Initialising parser.");
@ -90,27 +221,62 @@ bool XML::processChunk(const char *buf, unsigned int size,
*/
m_data.parsing_ctx = xmlCreatePushParserCtxt(NULL, NULL,
buf, size, "body.xml");
if (m_transaction->m_secXMLParseXmlIntoArgs
!= RulesSetProperties::OnlyArgsConfigXMLParseXmlIntoArgs) {
m_data.parsing_ctx = xmlCreatePushParserCtxt(NULL, NULL,
buf, size, "body.xml");
if (m_data.parsing_ctx == NULL) {
ms_dbg_a(m_transaction, 4,
"XML: Failed to create parsing context.");
error->assign("XML: Failed to create parsing context.");
return false;
if (m_data.parsing_ctx == NULL) {
ms_dbg_a(m_transaction, 4,
"XML: Failed to create parsing context.");
error->assign("XML: Failed to create parsing context.");
return false;
}
}
xmlSetGenericErrorFunc(m_data.parsing_ctx, null_error);
if (m_transaction->m_secXMLParseXmlIntoArgs
== RulesSetProperties::OnlyArgsConfigXMLParseXmlIntoArgs ||
m_transaction->m_secXMLParseXmlIntoArgs
== RulesSetProperties::TrueConfigXMLParseXmlIntoArgs) {
m_data.parsing_ctx_arg = xmlCreatePushParserCtxt(
m_data.sax_handler.get(),
m_data.xml_parser_state.get(),
buf,
size,
NULL);
if (m_data.parsing_ctx_arg == NULL) {
error->assign("XML: Failed to create parsing context for ARGS.");
return false;
}
}
return true;
}
/* Not a first invocation. */
xmlParseChunk(m_data.parsing_ctx, buf, size, 0);
if (m_data.parsing_ctx->wellFormed != 1) {
error->assign("XML: Failed to create parsing context.");
ms_dbg_a(m_transaction, 4, "XML: Failed parsing document.");
return false;
if (m_transaction->m_secXMLParseXmlIntoArgs
!= RulesSetProperties::OnlyArgsConfigXMLParseXmlIntoArgs) {
xmlSetGenericErrorFunc(m_data.parsing_ctx, null_error);
xmlParseChunk(m_data.parsing_ctx, buf, size, 0);
m_data.xml_parser_state->parsing_ctx_arg = m_data.parsing_ctx_arg;
if (m_data.parsing_ctx->wellFormed != 1) {
error->assign("XML: Failed parsing document.");
ms_dbg_a(m_transaction, 4, "XML: Failed parsing document.");
return false;
}
}
if (m_transaction->m_secXMLParseXmlIntoArgs
== RulesSetProperties::OnlyArgsConfigXMLParseXmlIntoArgs ||
m_transaction->m_secXMLParseXmlIntoArgs
== RulesSetProperties::TrueConfigXMLParseXmlIntoArgs) {
xmlSetGenericErrorFunc(m_data.parsing_ctx_arg, null_error);
xmlParseChunk(m_data.parsing_ctx_arg, buf, size, 0);
if (m_data.parsing_ctx_arg->wellFormed != 1) {
error->assign("XML: Failed parsing document for ARGS.");
ms_dbg_a(m_transaction, 4, "XML: Failed parsing document for ARGS.");
return false;
}
}
return true;
@ -119,24 +285,46 @@ bool XML::processChunk(const char *buf, unsigned int size,
bool XML::complete(std::string *error) {
/* Only if we have a context, meaning we've done some work. */
if (m_data.parsing_ctx != NULL) {
/* This is how we signalise the end of parsing to libxml. */
xmlParseChunk(m_data.parsing_ctx, NULL, 0, 1);
if (m_data.parsing_ctx != NULL || m_data.parsing_ctx_arg != NULL) {
if (m_transaction->m_secXMLParseXmlIntoArgs
!= RulesSetProperties::OnlyArgsConfigXMLParseXmlIntoArgs) {
/* This is how we signalise the end of parsing to libxml. */
xmlParseChunk(m_data.parsing_ctx, NULL, 0, 1);
/* Preserve the results for our reference. */
m_data.well_formed = m_data.parsing_ctx->wellFormed;
m_data.doc = m_data.parsing_ctx->myDoc;
/* Preserve the results for our reference. */
m_data.well_formed = m_data.parsing_ctx->wellFormed;
m_data.doc = m_data.parsing_ctx->myDoc;
/* Clean up everything else. */
xmlFreeParserCtxt(m_data.parsing_ctx);
m_data.parsing_ctx = NULL;
ms_dbg_a(m_transaction, 4, "XML: Parsing complete (well_formed " \
+ std::to_string(m_data.well_formed) + ").");
/* Clean up everything else. */
xmlFreeParserCtxt(m_data.parsing_ctx);
m_data.parsing_ctx = NULL;
ms_dbg_a(m_transaction, 4, "XML: Parsing complete (well_formed " \
+ std::to_string(m_data.well_formed) + ").");
if (m_data.well_formed != 1) {
error->assign("XML: Failed parsing document.");
ms_dbg_a(m_transaction, 4, "XML: Failed parsing document.");
return false;
if (m_data.well_formed != 1) {
error->assign("XML: Failed parsing document.");
ms_dbg_a(m_transaction, 4, "XML: Failed parsing document.");
return false;
}
}
if (m_transaction->m_secXMLParseXmlIntoArgs
== RulesSetProperties::OnlyArgsConfigXMLParseXmlIntoArgs ||
m_transaction->m_secXMLParseXmlIntoArgs
== RulesSetProperties::TrueConfigXMLParseXmlIntoArgs) {
/* This is how we signalise the end of parsing to libxml. */
if (xmlParseChunk(m_data.parsing_ctx_arg, NULL, 0, 1) != 0) {
if (m_data.xml_error != "") {
error->assign(m_data.xml_error);
}
else {
error->assign("XML: Failed parsing document for ARGS.");
}
xmlFreeParserCtxt(m_data.parsing_ctx_arg);
m_data.parsing_ctx_arg = NULL;
return false;
}
xmlFreeParserCtxt(m_data.parsing_ctx_arg);
m_data.parsing_ctx_arg = NULL;
}
}

View File

@ -16,6 +16,7 @@
#ifdef WITH_LIBXML2
#include <libxml/xmlschemas.h>
#include <libxml/xpath.h>
#include <libxml/SAX.h>
#endif
#include <string>
@ -33,12 +34,50 @@ namespace RequestBodyProcessor {
#ifdef WITH_LIBXML2
/*
* NodeData for parsing XML into args
*/
class NodeData {
public:
explicit NodeData();
~NodeData();
bool has_child;
};
/*
* XMLNodes for parsing XML into args
*/
class XMLNodes {
public:
std::vector<std::shared_ptr<NodeData>> nodes;
unsigned long int node_depth;
std::string currpath;
std::string currval;
Transaction *m_transaction;
// need to store context - this is the same as at the xml_data
// need to stop parsing if the number of arguments reached the limit
xmlParserCtxtPtr parsing_ctx_arg;
explicit XMLNodes (Transaction *);
~XMLNodes();
};
struct xml_data {
xmlSAXHandler *sax_handler;
std::unique_ptr<xmlSAXHandler> sax_handler;
xmlParserCtxtPtr parsing_ctx;
xmlDocPtr doc;
unsigned int well_formed;
/* error reporting and XML array flag */
std::string xml_error;
/* another parser context for arguments */
xmlParserCtxtPtr parsing_ctx_arg;
/* parser state for SAX parser */
std::unique_ptr<XMLNodes> xml_parser_state;
};
typedef struct xml_data xml_data;

View File

@ -149,6 +149,7 @@ Transaction::Transaction(ModSecurity *ms, RulesSet *rules, const char *id,
#endif
m_secRuleEngine(RulesSetProperties::PropertyNotSetRuleEngine),
m_logCbData(logCbData),
m_secXMLParseXmlIntoArgs(rules->m_secXMLParseXmlIntoArgs),
TransactionAnchoredVariables(this) {
m_variableUrlEncodedError.set("0", 0);
m_variableMscPcreError.set("0", 0);

View File

@ -30,7 +30,6 @@
"<some-tag>aaa</some-tag><some-tag>bbb</some-tag>",
"</bookstore>"
]
},
"server":{
"ip":"200.249.12.31",
@ -42,6 +41,543 @@
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500011,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
"SecRule XML://bookstore/*[local-name()='some-tag'] \"bbb\" \"id:500012,phase:3,t:none,t:lowercase,log,deny,status:403\""
]
},
{
"enabled":1,
"version_min":300000,
"resource":"libxml2",
"title":"Testing XML parsing to ARGS with On, check if ARGS is populated",
"expected":{
"http_code": 403
},
"client":{
"ip":"200.249.12.31",
"port":123
},
"request":{
"headers":{
"Host":"localhost",
"User-Agent":"curl/7.38.0",
"Accept":"*/*",
"Content-Type": "text/xml"
},
"uri":"/",
"method":"POST",
"body": [
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<!DOCTYPE author [",
"<!ELEMENT book ANY>",
"<!ENTITY js SYSTEM \"/etc/passwd\">",
"]>",
"<bookstore>",
"<some-tag>aaa</some-tag><some-tag>bbb</some-tag>",
"</bookstore>"
]
},
"server":{
"ip":"200.249.12.31",
"port":80
},
"rules":[
"SecRuleEngine On",
"SecRequestBodyAccess On",
"SecParseXMLIntoArgs On",
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500011,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
"SecRule ARGS:xml.bookstore.some-tag \"@rx aaa\" \"id:500012,phase:2,t:none,t:lowercase,log,deny,status:403\""
]
},
{
"enabled":1,
"version_min":300000,
"resource":"libxml2",
"title":"Testing XML parsing to ARGS with On, check if XML is populated",
"expected":{
"http_code": 403
},
"client":{
"ip":"200.249.12.31",
"port":123
},
"request":{
"headers":{
"Host":"localhost",
"User-Agent":"curl/7.38.0",
"Accept":"*/*",
"Content-Type": "text/xml"
},
"uri":"/",
"method":"POST",
"body": [
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<!DOCTYPE author [",
"<!ELEMENT book ANY>",
"<!ENTITY js SYSTEM \"/etc/passwd\">",
"]>",
"<bookstore>",
"<some-tag>aaa</some-tag><some-tag>bbb</some-tag>",
"</bookstore>"
]
},
"server":{
"ip":"200.249.12.31",
"port":80
},
"rules":[
"SecRuleEngine On",
"SecRequestBodyAccess On",
"SecParseXMLIntoArgs On",
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500011,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
"SecRule XML:/* \"@rx aaa\" \"id:500012,phase:2,t:none,t:lowercase,log,deny,status:403\""
]
},
{
"enabled":1,
"version_min":300000,
"resource":"libxml2",
"title":"Testing XML parsing to ARGS with OnlyArgs, check if ARGS is populated",
"expected":{
"http_code": 403
},
"client":{
"ip":"200.249.12.31",
"port":123
},
"request":{
"headers":{
"Host":"localhost",
"User-Agent":"curl/7.38.0",
"Accept":"*/*",
"Content-Type": "text/xml"
},
"uri":"/",
"method":"POST",
"body": [
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<!DOCTYPE author [",
"<!ELEMENT book ANY>",
"<!ENTITY js SYSTEM \"/etc/passwd\">",
"]>",
"<bookstore>",
"<some-tag>aaa</some-tag><some-tag>bbb</some-tag>",
"</bookstore>"
]
},
"server":{
"ip":"200.249.12.31",
"port":80
},
"rules":[
"SecRuleEngine On",
"SecRequestBodyAccess On",
"SecParseXMLIntoArgs OnlyArgs",
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500011,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
"SecRule ARGS:xml.bookstore.some-tag \"@rx aaa\" \"id:500012,phase:2,t:none,t:lowercase,log,deny,status:403\""
]
},
{
"enabled":1,
"version_min":300000,
"resource":"libxml2",
"title":"Testing XML parsing to ARGS with OnlyArgs, check if XML is populated",
"expected":{
"http_code": 200
},
"client":{
"ip":"200.249.12.31",
"port":123
},
"request":{
"headers":{
"Host":"localhost",
"User-Agent":"curl/7.38.0",
"Accept":"*/*",
"Content-Type": "text/xml"
},
"uri":"/",
"method":"POST",
"body": [
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<!DOCTYPE author [",
"<!ELEMENT book ANY>",
"<!ENTITY js SYSTEM \"/etc/passwd\">",
"]>",
"<bookstore>",
"<some-tag>aaa</some-tag><some-tag>bbb</some-tag>",
"</bookstore>"
]
},
"server":{
"ip":"200.249.12.31",
"port":80
},
"rules":[
"SecRuleEngine On",
"SecRequestBodyAccess On",
"SecParseXMLIntoArgs OnlyArgs",
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500011,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
"SecRule XML:/* \"@rx aaa\" \"id:500012,phase:2,t:none,t:lowercase,log,deny,status:403\""
]
},
{
"enabled":1,
"version_min":300000,
"resource":"libxml2",
"title":"Testing XML parsing to ARGS with Off, check if ARGS is populated",
"expected":{
"http_code": 200
},
"client":{
"ip":"200.249.12.31",
"port":123
},
"request":{
"headers":{
"Host":"localhost",
"User-Agent":"curl/7.38.0",
"Accept":"*/*",
"Content-Type": "text/xml"
},
"uri":"/",
"method":"POST",
"body": [
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<!DOCTYPE author [",
"<!ELEMENT book ANY>",
"<!ENTITY js SYSTEM \"/etc/passwd\">",
"]>",
"<bookstore>",
"<some-tag>aaa</some-tag><some-tag>bbb</some-tag>",
"</bookstore>"
]
},
"server":{
"ip":"200.249.12.31",
"port":80
},
"rules":[
"SecRuleEngine On",
"SecRequestBodyAccess On",
"SecParseXMLIntoArgs Off",
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500011,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
"SecRule ARGS \"@rx aaa\" \"id:500012,phase:2,t:none,t:lowercase,log,deny,status:403\""
]
},
{
"enabled":1,
"version_min":300000,
"resource":"libxml2",
"title":"Testing XML parsing to ARGS with Off, check if XML is populated",
"expected":{
"http_code": 403
},
"client":{
"ip":"200.249.12.31",
"port":123
},
"request":{
"headers":{
"Host":"localhost",
"User-Agent":"curl/7.38.0",
"Accept":"*/*",
"Content-Type": "text/xml"
},
"uri":"/",
"method":"POST",
"body": [
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<!DOCTYPE author [",
"<!ELEMENT book ANY>",
"<!ENTITY js SYSTEM \"/etc/passwd\">",
"]>",
"<bookstore>",
"<some-tag>aaa</some-tag><some-tag>bbb</some-tag>",
"</bookstore>"
]
},
"server":{
"ip":"200.249.12.31",
"port":80
},
"rules":[
"SecRuleEngine On",
"SecRequestBodyAccess On",
"SecParseXMLIntoArgs Off",
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500011,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
"SecRule XML:/* \"@rx aaa\" \"id:500012,phase:2,t:none,t:lowercase,log,deny,status:403\""
]
},
{
"enabled":1,
"version_min":300000,
"resource":"libxml2",
"title":"Testing XML parsing to ARGS with On, turn Off with ctl, check ARGS",
"expected":{
"http_code": 200
},
"client":{
"ip":"200.249.12.31",
"port":123
},
"request":{
"headers":{
"Host":"localhost",
"User-Agent":"curl/7.38.0",
"Accept":"*/*",
"Content-Type": "text/xml"
},
"uri":"/?q=xml",
"method":"POST",
"body": [
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<!DOCTYPE author [",
"<!ELEMENT book ANY>",
"<!ENTITY js SYSTEM \"/etc/passwd\">",
"]>",
"<bookstore>",
"<some-tag>aaa</some-tag><some-tag>bbb</some-tag>",
"</bookstore>"
]
},
"server":{
"ip":"200.249.12.31",
"port":80
},
"rules":[
"SecRuleEngine On",
"SecRequestBodyAccess On",
"SecParseXMLIntoArgs On",
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500011,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
"SecRule ARGS_GET:q \"@rx xml\" \"id:500012,phase:1,t:none,t:lowercase,ctl:parseXmlIntoArgs=Off\"",
"SecRule ARGS:xml.bookstore.some-tag \"@rx aaa\" \"id:500013,phase:2,t:none,t:lowercase,log,deny,status:403\""
]
},
{
"enabled":1,
"version_min":300000,
"resource":"libxml2",
"title":"Testing XML parsing to ARGS with On, turn Off with ctl, check XML",
"expected":{
"http_code": 403
},
"client":{
"ip":"200.249.12.31",
"port":123
},
"request":{
"headers":{
"Host":"localhost",
"User-Agent":"curl/7.38.0",
"Accept":"*/*",
"Content-Type": "text/xml"
},
"uri":"/?q=xml",
"method":"POST",
"body": [
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<!DOCTYPE author [",
"<!ELEMENT book ANY>",
"<!ENTITY js SYSTEM \"/etc/passwd\">",
"]>",
"<bookstore>",
"<some-tag>aaa</some-tag><some-tag>bbb</some-tag>",
"</bookstore>"
]
},
"server":{
"ip":"200.249.12.31",
"port":80
},
"rules":[
"SecRuleEngine On",
"SecRequestBodyAccess On",
"SecParseXMLIntoArgs On",
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500011,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
"SecRule ARGS_GET:q \"@rx xml\" \"id:500012,phase:1,t:none,t:lowercase,ctl:parseXmlIntoArgs=Off\"",
"SecRule XML:/* \"@rx aaa\" \"id:500013,phase:2,t:none,t:lowercase,log,deny,status:403\""
]
},
{
"enabled":1,
"version_min":300000,
"resource":"libxml2",
"title":"Testing XML parsing to ARGS with On, turn OnlyArgs with ctl, check ARGS",
"expected":{
"http_code": 403
},
"client":{
"ip":"200.249.12.31",
"port":123
},
"request":{
"headers":{
"Host":"localhost",
"User-Agent":"curl/7.38.0",
"Accept":"*/*",
"Content-Type": "text/xml"
},
"uri":"/?q=xml",
"method":"POST",
"body": [
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<!DOCTYPE author [",
"<!ELEMENT book ANY>",
"<!ENTITY js SYSTEM \"/etc/passwd\">",
"]>",
"<bookstore>",
"<some-tag>aaa</some-tag><some-tag>bbb</some-tag>",
"</bookstore>"
]
},
"server":{
"ip":"200.249.12.31",
"port":80
},
"rules":[
"SecRuleEngine On",
"SecRequestBodyAccess On",
"SecParseXMLIntoArgs On",
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500011,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
"SecRule ARGS_GET:q \"@rx xml\" \"id:500012,phase:1,t:none,t:lowercase,ctl:parseXmlIntoArgs=OnlyArgs\"",
"SecRule ARGS:xml.bookstore.some-tag \"@rx aaa\" \"id:500013,phase:2,t:none,t:lowercase,log,deny,status:403\""
]
},
{
"enabled":1,
"version_min":300000,
"resource":"libxml2",
"title":"Testing XML parsing to ARGS with On, turn OnlyArgs with ctl, check XML",
"expected":{
"http_code": 200
},
"client":{
"ip":"200.249.12.31",
"port":123
},
"request":{
"headers":{
"Host":"localhost",
"User-Agent":"curl/7.38.0",
"Accept":"*/*",
"Content-Type": "text/xml"
},
"uri":"/?q=xml",
"method":"POST",
"body": [
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<!DOCTYPE author [",
"<!ELEMENT book ANY>",
"<!ENTITY js SYSTEM \"/etc/passwd\">",
"]>",
"<bookstore>",
"<some-tag>aaa</some-tag><some-tag>bbb</some-tag>",
"</bookstore>"
]
},
"server":{
"ip":"200.249.12.31",
"port":80
},
"rules":[
"SecRuleEngine On",
"SecRequestBodyAccess On",
"SecParseXMLIntoArgs On",
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500011,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
"SecRule ARGS_GET:q \"@rx xml\" \"id:500012,phase:1,t:none,t:lowercase,ctl:parseXmlIntoArgs=OnlyArgs\"",
"SecRule XML:/* \"@rx aaa\" \"id:500013,phase:2,t:none,t:lowercase,log,deny,status:403\""
]
}
,
{
"enabled":1,
"version_min":300000,
"resource":"libxml2",
"title":"Testing XML parsing to ARGS with Off, turn On with ctl, check ARGS",
"expected":{
"http_code": 403
},
"client":{
"ip":"200.249.12.31",
"port":123
},
"request":{
"headers":{
"Host":"localhost",
"User-Agent":"curl/7.38.0",
"Accept":"*/*",
"Content-Type": "text/xml"
},
"uri":"/?q=xml",
"method":"POST",
"body": [
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<!DOCTYPE author [",
"<!ELEMENT book ANY>",
"<!ENTITY js SYSTEM \"/etc/passwd\">",
"]>",
"<bookstore>",
"<some-tag>aaa</some-tag><some-tag>bbb</some-tag>",
"</bookstore>"
]
},
"server":{
"ip":"200.249.12.31",
"port":80
},
"rules":[
"SecRuleEngine On",
"SecRequestBodyAccess On",
"SecParseXMLIntoArgs Off",
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500011,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
"SecRule ARGS_GET:q \"@rx xml\" \"id:500012,phase:1,t:none,t:lowercase,ctl:parseXmlIntoArgs=On\"",
"SecRule ARGS:xml.bookstore.some-tag \"@rx aaa\" \"id:500013,phase:2,t:none,t:lowercase,log,deny,status:403\""
]
},
{
"enabled":1,
"version_min":300000,
"resource":"libxml2",
"title":"Testing XML parsing to ARGS with Off, turn On with ctl, check XML",
"expected":{
"http_code": 200
},
"client":{
"ip":"200.249.12.31",
"port":123
},
"request":{
"headers":{
"Host":"localhost",
"User-Agent":"curl/7.38.0",
"Accept":"*/*",
"Content-Type": "text/xml"
},
"uri":"/?q=xml",
"method":"POST",
"body": [
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<!DOCTYPE author [",
"<!ELEMENT book ANY>",
"<!ENTITY js SYSTEM \"/etc/passwd\">",
"]>",
"<bookstore>",
"<some-tag>aaa</some-tag><some-tag>bbb</some-tag>",
"</bookstore>"
]
},
"server":{
"ip":"200.249.12.31",
"port":80
},
"rules":[
"SecRuleEngine On",
"SecRequestBodyAccess Off",
"SecParseXMLIntoArgs On",
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500011,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
"SecRule ARGS_GET:q \"@rx xml\" \"id:500012,phase:1,t:none,t:lowercase,ctl:parseXmlIntoArgs=On\"",
"SecRule XML:/* \"@rx aaa\" \"id:500013,phase:2,t:none,t:lowercase,log,deny,status:403\""
]
}
]