Table of Contents
Using ModSecurity for filtering application level requests is great. Let suggest you have been successfully using ModSecurity for filtering, attack detection/prevention and all kind of weird stuff.
Then you suddenly come to the moment, when you need to parse TEXT/XML request bodies….well here comes the HELL.
In order to make anything clear I will post the following steps:
- Given test/example scenario where you need to match upon text/xml request body
- Simple rules which comes on every normal person’s mind , which won’t work as expected .
- Another simple rules, which seems to be the only way for parsing/matching upon the request
1| Our pretty simple test scenario
Let say we have some index.php which accepts POST requests containing XML data and based upon these data we are doing some processing. I will call this “url to protect”. Then we want to block certain requests to our “url to protect” which are trying to pass us BAD THINGS inside the XML.
- URL to protect : http://one-of-our-sites.com/index.php
- What to look for: “bad_bad_string”
- What to do then: Block the request
Okay, we have defined what we have, what we wan’t to protect from and how to do it by simply blocking the request.
Also let assume, that our BAD REQUEST looks something like this:
curl -vd "
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<value>
<struct>
<member>
<name>bad_bad_string</name>
<value>bad_bad_string</value>
</member>
</struct>
</value>
</methodCall>
" -H "Content-Type:text/xml" http://one-of-our-sites.com/index.php
The request above is considered harmfull and bad.
2| Creating our defender rules
When it comes to the rule creation, you can choose one of these roads:
2.1| Trying to match the “bad_bad_string” by matching upon the ARGS/REQUEST_BODY in plain text
2.2| Trying to use the internal XML body processing engine (which cause some additional cpu overhead)
If you decide to go with road “2.1”, you will realize that it leads to dead end.
The following rules wont work to match “bad_bad_string” when you have “Content-Type:text/xml” on your incoming requesst:
Case 1, matching upon REQUEST_BODY
RequestBodyAccess On
SecRule REQUEST_BODY "bad_bad_string" "phase:2, deny, id: 111"
Case 2, matching upon FULL_REQUEST
RequestBodyAccess On
SecRule FULL_REQUEST "bad_bad_string" "phase:2, deny, id: 111"
Case 3, matching upon ARGS
RequestBodyAccess On
SecRule ARGS "bad_bad_string" "phase:2, deny, id: 111"
Case 4, try to use multipart or urlencoded parsing engines
SecRule REQUEST_HEADERS:Content-Type "text/xml" "phase:2, id:1111, ctl:requestBodyProcessor=URLENCODED"
Or
SecRule REQUEST_HEADERS:Content-Type "text/xml" "phase:2, id:1111, ctl:requestBodyProcessor=URLENCODED"
Case 5, try to use forceRequestBodyVariable and force REQUEST_BODY
SecRule REQUEST_HEADERS:Content-Type "text/xml" "phase:2, id:1111, ctl:forceRequestBodyVariable=On"
Enough USELESS cases !
Even if some of the cases above seems to work in earlier mod security versions, they don’t in the current stable: 2.9.0.
3| The right way to match upon XML content-type when using mod security 2.9.0
The right way to match upon the XML is to use the integrated XML engine.
Here is the working example:
WORKING FIX 1
RequestBodyAccess On
SecRule REQUEST_HEADERS:Content-Type "text/xml" "phase:1, deny, status:500 , ctl:requestBodyProcessor=XML"
SecRule XML:/methodCall/value/struct/member/name "bad_bad_string" "phase:2, id:123123, deny, status:500, chain"
What we are doing:
- Tell ModSecurity, to process Content-Type “text/xml” by using the XML engine
- Use the XML engine syntax to match upon our wanted bad value
UPDATE
Thanks to Hermann Schwaerzler from ModSecurity user mailing list, I was able to workout example, which successfully matches a plaintext string in XML, without using the internal XML engine.
Here are the rules:
WORKING FIX 2
SecRequestBodyAccess On
# In phase:1 , change the requestBodyProcessor to “URLENCODED”
SecRule REQUEST_HEADERS:Content-Type "text/xml" "phase:1, nolog,pass,ctl:requestBodyProcessor=URLENCODED, id:2222"
# Now in phase:2, match the plaintext value “testvalue2” inside the REQUEST_BODY
# then block the request with error 500
SecRule REQUEST_BODY "testvalue2" "phase:2, t:none, deny, status:500, id:3333"
If you need to parse response body, keep in mind these pitfalls.