Skip to content

Commit 08023e5

Browse files
committed
Adds support to the operator @validateDTD
Further info #1003
1 parent 6a40752 commit 08023e5

File tree

3 files changed

+313
-10
lines changed

3 files changed

+313
-10
lines changed

src/operators/validate_dtd.cc

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,90 @@
1717

1818
#include <string>
1919

20+
#include "request_body_processor/xml.h"
21+
#include "src/utils.h"
2022
#include "operators/operator.h"
2123

2224
namespace modsecurity {
2325
namespace operators {
2426

25-
bool ValidateDTD::evaluate(Transaction *transaction, const std::string &str) {
26-
/**
27-
* @todo Implement the operator ValidateDTD.
28-
* Reference: https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual#validateDTD
29-
*/
27+
28+
bool ValidateDTD::init(const std::string &file, const char **error) {
29+
m_resource = find_resource(param, file);
30+
if (m_resource == "") {
31+
std::string f("XML: File not found: " + param + ".");
32+
*error = strdup(f.c_str());
33+
return false;
34+
}
35+
36+
xmlThrDefSetGenericErrorFunc(NULL,
37+
null_error);
38+
39+
xmlSetGenericErrorFunc(NULL,
40+
null_error);
41+
42+
m_dtd = xmlParseDTD(NULL, (const xmlChar *)m_resource.c_str());
43+
if (m_dtd == NULL) {
44+
std::string err = std::string("XML: Failed to load DTD: ") \
45+
+ m_resource;
46+
*error = strdup(err.c_str());
47+
return false;
48+
}
49+
3050
return true;
3151
}
3252

3353

34-
ValidateDTD::ValidateDTD(std::string op, std::string param, bool negation)
35-
: Operator() {
36-
this->op = op;
37-
this->param = param;
54+
bool ValidateDTD::evaluate(Transaction *t, const std::string &str) {
55+
xmlValidCtxtPtr cvp;
56+
57+
if (t->m_xml->m_data.doc == NULL) {
58+
t->debug(4, "XML document tree could not "\
59+
"be found for DTD validation.");
60+
return true;
61+
}
62+
63+
if (t->m_xml->m_data.well_formed != 1) {
64+
t->debug(4, "XML: DTD validation failed because " \
65+
"content is not well formed.");
66+
return true;
67+
}
68+
69+
#if 0
70+
/* Make sure there were no other generic processing errors */
71+
if (msr->msc_reqbody_error) {
72+
*error_msg = apr_psprintf(msr->mp,
73+
"XML: DTD validation could not proceed due to previous"
74+
" processing errors.");
75+
return 1;
76+
}
77+
#endif
78+
79+
cvp = xmlNewValidCtxt();
80+
if (cvp == NULL) {
81+
t->debug(4, "XML: Failed to create a validation context.");
82+
return true;
83+
}
84+
85+
/* Send validator errors/warnings to msr_log */
86+
cvp->error = (xmlSchemaValidityErrorFunc)error_runtime;
87+
cvp->warning = (xmlSchemaValidityErrorFunc)warn_runtime;
88+
cvp->userData = t;
89+
90+
if (!xmlValidateDtd(cvp, t->m_xml->m_data.doc, m_dtd)) {
91+
t->debug(4, "XML: DTD validation failed.");
92+
xmlFreeValidCtxt(cvp);
93+
return true;
94+
}
95+
96+
t->debug(4, std::string("XML: Successfully validated " \
97+
"payload against DTD: ") + m_resource);
98+
99+
xmlFreeValidCtxt(cvp);
100+
101+
return false;
38102
}
39103

104+
40105
} // namespace operators
41106
} // namespace modsecurity

src/operators/validate_dtd.h

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
#define SRC_OPERATORS_VALIDATE_DTD_H_
1818

1919
#include <string>
20+
#include <stdio.h>
21+
#include <stdarg.h>
22+
#include <string.h>
23+
#include <libxml/xmlschemas.h>
24+
#include <libxml/xpath.h>
2025

2126
#include "operators/operator.h"
2227

@@ -27,8 +32,60 @@ namespace operators {
2732
class ValidateDTD : public Operator {
2833
public:
2934
/** @ingroup ModSecurity_Operator */
30-
ValidateDTD(std::string o, std::string p, bool i);
35+
ValidateDTD(std::string o, std::string p, bool i)
36+
: Operator(o, p, i),
37+
m_dtd(NULL) { }
38+
~ValidateDTD() {
39+
if (m_dtd != NULL) {
40+
xmlFreeDtd(m_dtd);
41+
m_dtd = NULL;
42+
}
43+
}
44+
3145
bool evaluate(Transaction *transaction, const std::string &str) override;
46+
bool init(const std::string &file, const char **error) override;
47+
48+
49+
static void error_runtime(void *ctx, const char *msg, ...) {
50+
Transaction *t = reinterpret_cast<Transaction *>(ctx);
51+
char buf[1024];
52+
std::string s;
53+
va_list args;
54+
55+
va_start(args, msg);
56+
int len = vsnprintf(buf, sizeof(buf), msg, args);
57+
va_end(args);
58+
59+
if (len > 0) {
60+
s = "XML Error: " + std::string(buf);
61+
}
62+
t->debug(4, s);
63+
}
64+
65+
66+
static void warn_runtime(void *ctx, const char *msg, ...) {
67+
Transaction *t = reinterpret_cast<Transaction *>(ctx);
68+
char buf[1024];
69+
std::string s;
70+
va_list args;
71+
72+
va_start(args, msg);
73+
int len = vsnprintf(buf, sizeof(buf), msg, args);
74+
va_end(args);
75+
76+
if (len > 0) {
77+
s = "XML Warning: " + std::string(buf);
78+
}
79+
t->debug(4, s);
80+
}
81+
82+
83+
static void null_error(void *ctx, const char *msg, ...) {
84+
}
85+
86+
private:
87+
std::string m_resource;
88+
xmlDtdPtr m_dtd;
3289
};
3390

3491
} // namespace operators
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
[
2+
{
3+
"enabled":1,
4+
"version_min":300000,
5+
"title":"Testing XML request body parser - validateDTD (validate ok)",
6+
"expected":{
7+
"debug_log": "XML: Successfully validated payload against DTD: test-cases/data/SoapEnvelope.dtd"
8+
},
9+
"client":{
10+
"ip":"200.249.12.31",
11+
"port":123
12+
},
13+
"request":{
14+
"headers":{
15+
"Host":"localhost",
16+
"User-Agent":"curl/7.38.0",
17+
"Accept":"*/*",
18+
"Cookie": "PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120",
19+
"Content-Type": "text/xml"
20+
},
21+
"uri":"/?key=value&key=other_value",
22+
"method":"POST",
23+
"body": [
24+
"<?xml version=\"1.0\" encoding=\"utf-8\"?>",
25+
" <!DOCTYPE Envelope SYSTEM \"SoapEnvelope.dtd\">",
26+
" <Envelope>",
27+
" <Body>",
28+
" <getInput>",
29+
" <id type=\"string\">12123</id>",
30+
" </getInput>",
31+
" </Body>",
32+
" </Envelope>"
33+
]
34+
},
35+
"server":{
36+
"ip":"200.249.12.31",
37+
"port":80
38+
},
39+
"rules":[
40+
"SecRuleEngine On",
41+
"SecRequestBodyAccess On",
42+
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500008,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
43+
"SecRule XML \"@validateDTD test-cases/data/SoapEnvelope.dtd\" \"id:500007,phase:3,deny\""
44+
]
45+
},
46+
{
47+
"enabled":1,
48+
"version_min":300000,
49+
"title":"Testing XML request body parser - validateDTD (validation failed)",
50+
"expected":{
51+
"debug_log": "XML Error: No declaration for element xBody",
52+
"http_code": 403
53+
},
54+
"client":{
55+
"ip":"200.249.12.31",
56+
"port":123
57+
},
58+
"request":{
59+
"headers":{
60+
"Host":"localhost",
61+
"User-Agent":"curl/7.38.0",
62+
"Accept":"*/*",
63+
"Cookie": "PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120",
64+
"Content-Type": "text/xml"
65+
},
66+
"uri":"/?key=value&key=other_value",
67+
"method":"POST",
68+
"body": [
69+
"<?xml version=\"1.0\" encoding=\"utf-8\"?>",
70+
" <!DOCTYPE Envelope SYSTEM \"SoapEnvelope.dtd\">",
71+
" <Envelope>",
72+
" <xBody>",
73+
" <getInput>",
74+
" <id type=\"string\">12123</id>",
75+
" </getInput>",
76+
" </xBody>",
77+
" </Envelope>"
78+
]
79+
},
80+
"server":{
81+
"ip":"200.249.12.31",
82+
"port":80
83+
},
84+
"rules":[
85+
"SecRuleEngine On",
86+
"SecRequestBodyAccess On",
87+
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500008,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
88+
"SecRule XML \"@validateDTD test-cases/data/SoapEnvelope.dtd\" \"id:500007,phase:3,deny\""
89+
]
90+
},
91+
{
92+
"enabled":1,
93+
"version_min":300000,
94+
"title":"Testing XML request body parser - validateDTD (bad XML)",
95+
"expected":{
96+
"debug_log": "XML: DTD validation failed because content is not well formed",
97+
"http_code": 403
98+
},
99+
"client":{
100+
"ip":"200.249.12.31",
101+
"port":123
102+
},
103+
"request":{
104+
"headers":{
105+
"Host":"localhost",
106+
"User-Agent":"curl/7.38.0",
107+
"Accept":"*/*",
108+
"Cookie": "PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120",
109+
"Content-Type": "text/xml"
110+
},
111+
"uri":"/?key=value&key=other_value",
112+
"method":"POST",
113+
"body": [
114+
"<?xml version=\"1.0\" encoding=\"utf-8\"?>",
115+
"<!DOCTYPE Envelope SYSTEM \"SoapEnvelope.dtd\">",
116+
" <Envelop>",
117+
" <Body>",
118+
" <getInput>",
119+
" <id type=\"string\">12123</id>",
120+
" </getInput>",
121+
" </Body>",
122+
" </Envelope>"
123+
]
124+
},
125+
"server":{
126+
"ip":"200.249.12.31",
127+
"port":80
128+
},
129+
"rules":[
130+
"SecRuleEngine On",
131+
"SecRequestBodyAccess On",
132+
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500008,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
133+
"SecRule XML \"@validateDTD test-cases/data/SoapEnvelope.dtd\" \"id:500007,phase:3,deny\""
134+
]
135+
},
136+
{
137+
"enabled":1,
138+
"version_min":300000,
139+
"title":"Testing XML request body parser - validateDTD (bad DTD)",
140+
"expected":{
141+
"parser_error": "Line: 4. Column: 12. XML: Failed to load DTD: test-cases/data/SoapEnvelope-bad.dtd"
142+
},
143+
"client":{
144+
"ip":"200.249.12.31",
145+
"port":123
146+
},
147+
"request":{
148+
"headers":{
149+
"Host":"localhost",
150+
"User-Agent":"curl/7.38.0",
151+
"Accept":"*/*",
152+
"Cookie": "PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120",
153+
"Content-Type": "text/xml"
154+
},
155+
"uri":"/?key=value&key=other_value",
156+
"method":"POST",
157+
"body": [
158+
"<?xml version=\"1.0\" encoding=\"utf-8\"?>",
159+
" <!DOCTYPE Envelope SYSTEM \"SoapEnvelope.dtd\">",
160+
" <Envelope>",
161+
" <Body>",
162+
" <getInput>",
163+
" <id type=\"string\">12123</id>",
164+
" </getInput>",
165+
" </Body>",
166+
" </Envelope>"
167+
]
168+
},
169+
"server":{
170+
"ip":"200.249.12.31",
171+
"port":80
172+
},
173+
"rules":[
174+
"SecRuleEngine On",
175+
"SecRequestBodyAccess On",
176+
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500008,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
177+
"SecRule XML \"@validateDTD test-cases/data/SoapEnvelope-bad.dtd\" \"id:500007,phase:3,deny\""
178+
]
179+
}
180+
]
181+

0 commit comments

Comments
 (0)