''' 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 ''' from core.data.fuzzer.fuzzer import * import core.controllers.outputManager as om import core.data.kb.vuln as vuln import core.data.kb.knowledgeBase as kb import core.data.parsers.urlParser as urlParser from core.controllers.threads.threadManager import threadManager as tm from core.controllers.w3afException import w3afException # importing this to have sendMutant and setUrlOpener from core.controllers.basePlugin.basePlugin import basePlugin import re class blindSqli(basePlugin): ''' This class tests for blind SQL injection bugs, the logic is here and not as an audit plugin cause this logic is also used in attack plugins. @author: Andres Riancho ( andres.riancho@gmail.com ) ''' def __init__(self): # User configured variables self._equalLimit = 0.8 self._equAlgorithm = 'stringEq' self._tm = tm() def setEqualLimit( self, _equalLimit ): self._equalLimit = _equalLimit def setEquAlgorithm( self, _equAlgorithm ): self._equAlgorithm = _equAlgorithm def verifyBlindSQL( self, freq, parameter ): ''' Verify the existance of an already found vuln. ''' dummy = ['',] parameterToTest = [ parameter, ] mutants = createMutants( freq , dummy, fuzzableParamList=parameterToTest ) for mutant in mutants: for statementTuple in self._getStatements(): vulns = self._findBsql( mutant, statementTuple ) if len( vulns ): return vulns return [] def findBlindSQL(self, fuzzableRequest, saveToKb=False ): ''' Tests an URL for blind Sql injection vulnerabilities. @param freq: A fuzzableRequest ''' dummy = ['',] mutants = createMutants( fuzzableRequest , dummy ) for mutant in mutants: for statementTuple in self._getStatements(): targs = (mutant,statementTuple, saveToKb) self._tm.startFunction( target=self._findBsqlAux, args=targs, ownerObj=self ) self._tm.join( self ) def _getStatements( self ): ''' Returns a list of statement tuples. ''' res = [] # Unquoted, integer values res.append( ('numeric','%i OR %i=%i ', '%i AND %i=%i ') ) # Single quotes res.append( ('stringsingle',"%i' OR '%i'='%i", "%i' AND '%i'='%i") ) # Double quotes res.append( ('stringdouble','%i" OR "%i"="%i', '%i" AND "%i"="%i') ) return res def _findBsqlAux( self, mutant, statementTuple, saveToKb ): ''' Auxiliar function that does almost nothing. ''' bsqlVulns = self._findBsql( mutant, statementTuple ) if saveToKb: for vuln in bsqlVulns: kb.kb.append( self, 'bsqli', vuln ) om.out.vulnerability( vuln.getDesc() ) def _findBsql( self, mutant, statementTuple ): ''' Is the main algorithm for finding blind sql injections. ''' res = [] statementType = statementTuple[0] trueStatement = statementTuple[1] falseStatement = statementTuple[2] rndNum = int( createRandNum( 2 ) ) rndNumPlusOne = rndNum +1 trueStm = trueStatement % (rndNum, rndNum, rndNum ) mutant.setModValue( trueStm ) trueResponse = self._sendMutant( mutant, analyze=False ) falseStm = falseStatement % (rndNum, rndNum, rndNumPlusOne) mutant.setModValue( falseStm ) falseResponse = self._sendMutant( mutant, analyze=False ) om.out.debug('Comparing trueResponse and falseResponse.') if not self._equal( trueResponse.getBody() , falseResponse.getBody() ): sintaxError = "d'z'0" mutant.setModValue( sintaxError ) seResponse = self._sendMutant( mutant, analyze=False ) om.out.debug('Comparing trueResponse and sintaxErrorResponse.') if not self._equal( trueResponse.getBody() , seResponse.getBody() ): rndNum +=2 rndNumPlusOne += 2 secondFalseStm = falseStatement % (rndNum, rndNum, rndNumPlusOne) mutant.setModValue( secondFalseStm ) secondFalseResponse = self._sendMutant( mutant, analyze=False ) secondTrueStm = trueStatement % (rndNum, rndNum, rndNum ) mutant.setModValue( secondTrueStm ) secondTrueResponse = self._sendMutant( mutant, analyze=False ) om.out.debug('Comparing secondTrueResponse and trueResponse.') if self._equal( secondTrueResponse.getBody(), trueResponse.getBody() ): om.out.debug('Comparing secondFalseResponse and falseResponse.') if self._equal( secondFalseResponse.getBody(), falseResponse.getBody() ): v = vuln.vuln( mutant ) v.setId( secondFalseResponse.id ) v.setDesc( 'Blind SQL injection was found at: ' + v.getURL() + ' . Using method: ' + v.getMethod() + '. The injectable parameter is: ' + mutant.getVar() ) v['type'] = statementType v['trueHtml'] = secondTrueResponse.getBody() v['falseHtml'] = secondFalseResponse.getBody() v['errorHtml'] = seResponse.getBody() res.append( v ) return res def _equal( self, body1, body2 ): ''' Determines if two pages are equal using some tricks. ''' if self._equAlgorithm == 'setIntersection': return self._setIntersection( body1, body2) elif self._equAlgorithm == 'stringEq': return self._stringEq( body1, body2) elif self._equAlgorithm == 'intelligentCut': return self._intelligentCut( body1, body2) raise w3afException('Unknown algorithm selected.') def _intelligentCut( self, body1, body2 ): ''' This is one of the equal algorithms. The idea is to remove the sections of the html that change from one call to another. ''' raise w3afException('_intelligentCut is not implemented yet.') def _stringEq( self, body1 , body2 ): ''' This is one of the equal algorithms. ''' if body1 == body2: om.out.debug('Pages are equal.') return True else: om.out.debug('Pages are NOT equal.') return False def _setIntersection( self, body1, body2 ): ''' This is one of the equal algorithms. ''' sb1 = re.findall('(\w+)',body1 ) sb2 = re.findall('(\w+)',body2 ) setb1 = set( sb1 ) setb2 = set( sb2 ) intersection = setb1.intersection( setb2 ) totalLen = float( len( setb1 ) + len( setb2 ) ) if totalLen == 0: om.out.error( 'The length of both pages are zero. Cant work with this.' ) return False equal = ( 2 * len(intersection) ) / totalLen if equal > self._equalLimit: om.out.debug('Pages are equal, match rate: ' + str(equal) ) return True else: om.out.debug('Pages are NOT equal, match rate: ' + str(equal) ) return False