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!  Initialising transaction  Starting phase URI. (SecRules 0 + 1/2)  Adding request argument (QUERY_STRING): name "a", value "<script>alert(1)</script>"  Adding request argument (QUERY_STRING): name "q", value "test"  Starting phase URI. (SecRules 0 + 1/2)  Starting phase REQUEST_HEADERS. (SecRules 1)  This phase consists of 0 rule(s).  Starting phase REQUEST_BODY. (SecRules 2)  This phase consists of 1 rule(s).  (Rule: 1) Executing operator "@rx " with param "(script)" against ARGS.  Target value: "<script>alert(1)</script>" (Variable: ARGS:a)  Operator completed in 0.000068 seconds  Rule returned 1.  Running (_non_ disruptive) action: status:403  Running (disruptive) action: deny  Running action deny  Target value: "test" (Variable: ARGS:q)  Operator completed in 0.000144 seconds  Rule returned 0.  Starting phase RESPONSE_HEADERS. (SecRules 3)  This phase consists of 0 rule(s).  Starting phase RESPONSE_BODY. (SecRules 4)  This phase consists of 0 rule(s).  Starting phase LOGGING. (SecRules 5)  This phase consists of 0 rule(s).  Checking if this request is suitable to be saved as an audit log.
Have fun parsing the logs!