The modsecurity project recently created Python bindings
for their WAF, and despite the fact that naxsi is way better,
I wanted to give it a try. To save time to fellow naive adventurer that want
to do the same, this is the story of how far I went.
Please keep in mind that those bindings are more likely an experiment than a real project for now, since I found bugs in modsec's parser (read modesec bypass) and logic/API issues during this journey.
# install modsecurity
git clone https://github.com/SpiderLabs/ModSecurity.git
cd ModSecurity
./build.sh
./configure
make
make install
# install the bindings
git clone https://github.com/SpiderLabs/ModSecurity-Python-bindings.git
cd ModSecurity-Python-bindings
make # you may want to edit the Makefile and setup.py files
make install
And this is how to use it to load and process web requests:
import modsecurity
import os
import sys
req="""GET /docs/index.html?a=<script>alert(1)</script>&q=test HTTP/1.1
Host: 127.0.0.1
Accept-Language: en-uk
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
"""
resp="""HTTP/1.1 200 OK
Date: Sun, 16 Feb 2016 08:56:53 GMT
Server: nginx-2.2.14
Last-Modified: Sat, 20 Nov 2004 07:16:26 GMT
Accept-Ranges: bytes
Connection: close
Content-Type: text/html
<html><body><h1>It works!</h1>'<script>alert(1)</script>'</body></html>
"""
def parseRequest(req): # parse a web request
method = uri = version = ""
headers = list()
lines = req.split('\n')
try:
method, uri, version = lines.pop(0).split(' ')
except ValueError:
print 'Invalid request'
sys.exit(0)
while lines:
line = lines.pop(0)
if not line:
break
headers.append(line)
return method, uri, version, headers, '\n'.join(lines)
modsec = modsecurity.msc_init()
rules = modsecurity.Rules()
rules.load('''SecRuleEngine On
SecDebugLog /tmp/debug.log
SecDebugLogLevel 9
SecRule ARGS "@rx (script)" "id:1,phase:request,deny,t:none,status:403"
''')
ret = rules.getParserError()
if ret:
print 'Unable to parse rules: %s' % ret
transaction = modsecurity.msc_new_transaction(modsec, rules, None)
# Parse and process the request
method, uri, version, headers, data = parseRequest(req)
transaction.processURI(uri, method, version)
for header in headers:
transaction.addRequestHeader(*header.split(':', 2))
if data:
transaction.appendRequestBody(data, len(data))
# Parse and process the response
method, uri, version, headers, data = parseRequest(resp)
transaction.processURI(uri, method, version)
for header in headers:
transaction.addResponseHeader(*header.split(':', 1))
if data:
#transaction.appendResponseBody(data, len(data)) # the bindings are borken
transaction.m_responseBody = data # is this even working?
transaction.processRequestHeaders()
transaction.processRequestBody()
transaction.processResponseHeaders()
transaction.processResponseBody()
transaction.processLogging(200)
intervention = modsecurity.ModSecurityIntervention()
if transaction.intervention(intervention):
print 'Bad request!'
else:
print 'Good request!'
with open('/tmp/debug.log', 'r') as f:
print f.read()
os.remove('/tmp/debug.log')
And this is what you'll get:
$ python modsec.py
Bad request!
[4] Initialising transaction
[4] Starting phase URI. (SecRules 0 + 1/2)
[4] Adding request argument (QUERY_STRING): name "a", value "<script>alert(1)</script>"
[4] Adding request argument (QUERY_STRING): name "q", value "test"
[4] Starting phase URI. (SecRules 0 + 1/2)
[4] Starting phase REQUEST_HEADERS. (SecRules 1)
[9] This phase consists of 0 rule(s).
[4] Starting phase REQUEST_BODY. (SecRules 2)
[9] This phase consists of 1 rule(s).
[4] (Rule: 1) Executing operator "@rx " with param "(script)" against ARGS.
[9] Target value: "<script>alert(1)</script>" (Variable: ARGS:a)
[4] Operator completed in 0.000068 seconds
[4] Rule returned 1.
[4] Running (_non_ disruptive) action: status:403
[4] Running (disruptive) action: deny
[8] Running action deny
[9] Target value: "test" (Variable: ARGS:q)
[4] Operator completed in 0.000144 seconds
[4] Rule returned 0.
[4] Starting phase RESPONSE_HEADERS. (SecRules 3)
[9] This phase consists of 0 rule(s).
[4] Starting phase RESPONSE_BODY. (SecRules 4)
[9] This phase consists of 0 rule(s).
[4] Starting phase LOGGING. (SecRules 5)
[9] This phase consists of 0 rule(s).
[8] Checking if this request is suitable to be saved as an audit log.
Have fun parsing the logs!