lots of refactoring regarding removal of already obsolete session file mechanism

This commit is contained in:
Miroslav Stampar 2012-06-21 10:09:10 +00:00
parent 1e67b4f0b9
commit ec44e88db8
9 changed files with 135 additions and 398 deletions

View File

@ -738,13 +738,6 @@ def dataToStdout(data, forceOutput=False):
logging._releaseLock() logging._releaseLock()
setFormatterPrependFlag(len(data) == 1 and data not in ('\n', '\r') or len(data) > 2 and data[0] == '\r' and data[-1] != '\n') setFormatterPrependFlag(len(data) == 1 and data not in ('\n', '\r') or len(data) > 2 and data[0] == '\r' and data[-1] != '\n')
def dataToSessionFile(data):
if not conf.sessionFile or kb.suppressSession:
return
conf.sessionFP.write(data)
conf.sessionFP.flush()
def dataToTrafficFile(data): def dataToTrafficFile(data):
if not conf.trafficFile: if not conf.trafficFile:
return return

View File

@ -143,14 +143,16 @@ class EXPECTED:
INT = "int" INT = "int"
class HASHDB_KEYS: class HASHDB_KEYS:
KB_ABS_FILE_PATHS = "KB_ABS_FILE_PATHS" DBMS = "DBMS"
KB_CHARS = "KB_CHARS"
KB_BRUTE_TABLES = "KB_BRUTE_TABLES"
KB_BRUTE_COLUMNS = "KB_BRUTE_COLUMNS"
CONF_TMP_PATH = "CONF_TMP_PATH" CONF_TMP_PATH = "CONF_TMP_PATH"
KB_XP_CMDSHELL_AVAILABLE = "KB_XP_CMDSHELL_AVAILABLE" KB_ABS_FILE_PATHS = "KB_ABS_FILE_PATHS"
KB_INJECTIONS = "KB_INJECTIONS" KB_BRUTE_COLUMNS = "KB_BRUTE_COLUMNS"
KB_BRUTE_TABLES = "KB_BRUTE_TABLES"
KB_CHARS = "KB_CHARS"
KB_DYNAMIC_MARKINGS = "KB_DYNAMIC_MARKINGS" KB_DYNAMIC_MARKINGS = "KB_DYNAMIC_MARKINGS"
KB_INJECTIONS = "KB_INJECTIONS"
KB_XP_CMDSHELL_AVAILABLE = "KB_XP_CMDSHELL_AVAILABLE"
OS = "OS"
class REDIRECTION: class REDIRECTION:
YES = "Y" YES = "Y"

View File

@ -1497,13 +1497,11 @@ def __setKnowledgeBaseAttributes(flushAll=True):
kb.reflectiveMechanism = True kb.reflectiveMechanism = True
kb.reflectiveCounters = {REFLECTIVE_COUNTER.MISS:0, REFLECTIVE_COUNTER.HIT:0} kb.reflectiveCounters = {REFLECTIVE_COUNTER.MISS:0, REFLECTIVE_COUNTER.HIT:0}
kb.responseTimes = [] kb.responseTimes = []
kb.resumedQueries = {}
kb.resumeValues = True kb.resumeValues = True
kb.safeCharEncode = False kb.safeCharEncode = False
kb.singleLogFlags = set() kb.singleLogFlags = set()
kb.skipOthersDbms = None kb.skipOthersDbms = None
kb.stickyFlag = False kb.stickyFlag = False
kb.suppressSession = False
kb.suppressResumeInfo = False kb.suppressResumeInfo = False
kb.technique = None kb.technique = None
kb.testMode = False kb.testMode = False

View File

@ -11,7 +11,7 @@ import re
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.common import Format from lib.core.common import Format
from lib.core.common import dataToSessionFile from lib.core.common import hashDBWrite
from lib.core.common import intersect from lib.core.common import intersect
from lib.core.common import readInput from lib.core.common import readInput
from lib.core.common import singleTimeWarnMessage from lib.core.common import singleTimeWarnMessage
@ -20,42 +20,25 @@ from lib.core.convert import base64unpickle
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import kb from lib.core.data import kb
from lib.core.data import logger from lib.core.data import logger
from lib.core.enums import HASHDB_KEYS
from lib.core.enums import OS from lib.core.enums import OS
from lib.core.settings import SUPPORTED_DBMS from lib.core.settings import SUPPORTED_DBMS
from lib.core.settings import UNKNOWN_DBMS_VERSION from lib.core.settings import UNKNOWN_DBMS_VERSION
def safeFormatString(value):
retVal = value
if retVal:
retVal = retVal.replace("[", "__LEFT_SQUARE_BRACKET__").replace("]", "__RIGHT_SQUARE_BRACKET__")
return retVal
def unSafeFormatString(value):
retVal = value
if retVal:
retVal = retVal.replace("__LEFT_SQUARE_BRACKET__", "[").replace("__RIGHT_SQUARE_BRACKET__", "]")
return retVal
def setDbms(dbms): def setDbms(dbms):
""" """
@param dbms: database management system to be set into the knowledge @param dbms: database management system to be set into the knowledge
base as fingerprint. base as fingerprint.
@type dbms: C{str} @type dbms: C{str}
""" """
condition = (
not kb.resumedQueries
or ( kb.resumedQueries.has_key(conf.url) and
not kb.resumedQueries[conf.url].has_key("DBMS") )
)
if condition: hashDBWrite(HASHDB_KEYS.DBMS, dbms)
dataToSessionFile("[%s][%s][%s][DBMS][%s]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), safeFormatString(dbms)))
firstRegExp = "(%s)" % ("|".join([alias for alias in SUPPORTED_DBMS])) _ = "(%s)" % ("|".join([alias for alias in SUPPORTED_DBMS]))
dbmsRegExp = re.search("^%s" % firstRegExp, dbms, re.I) _ = re.search("^%s" % _, dbms, re.I)
if dbmsRegExp: if _:
dbms = dbmsRegExp.group(1) dbms = _.group(1)
Backend.setDbms(dbms) Backend.setDbms(dbms)
@ -76,11 +59,6 @@ def setOs():
""" """
infoMsg = "" infoMsg = ""
condition = (
not kb.resumedQueries
or ( kb.resumedQueries.has_key(conf.url) and
not kb.resumedQueries[conf.url].has_key("OS") )
)
if not kb.bannerFp: if not kb.bannerFp:
return return
@ -105,82 +83,4 @@ def setOs():
if infoMsg: if infoMsg:
logger.info(infoMsg) logger.info(infoMsg)
if condition: hashDBWrite(HASHDB_KEYS.OS, Backend.getOs())
dataToSessionFile("[%s][%s][%s][OS][%s]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), Backend.getOs()))
def resumeConfKb(expression, url, value):
if expression == "Dynamic markings" and url == conf.url:
kb.dynamicMarkings = base64unpickle(value[:-1])
infoMsg = "resuming dynamic markings from session file"
logger.info(infoMsg)
elif expression == "DBMS" and url == conf.url:
dbms = unSafeFormatString(value[:-1])
dbms = dbms.lower()
dbmsVersion = [UNKNOWN_DBMS_VERSION]
firstRegExp = "(%s)" % ("|".join([alias for alias in SUPPORTED_DBMS]))
dbmsRegExp = re.search("%s ([\d\.]+)" % firstRegExp, dbms)
if dbmsRegExp:
dbms = dbmsRegExp.group(1)
dbmsVersion = [ dbmsRegExp.group(2) ]
if conf.dbms and conf.dbms.lower() != dbms:
message = "you provided '%s' as back-end DBMS, " % conf.dbms
message += "but from a past scan information on the target URL "
message += "sqlmap assumes the back-end DBMS is %s. " % dbms
message += "Do you really want to force the back-end "
message += "DBMS value? [y/N] "
test = readInput(message, default="N")
if not test or test[0] in ("n", "N"):
conf.dbms = None
Backend.setDbms(dbms)
Backend.setVersionList(dbmsVersion)
else:
infoMsg = "resuming back-end DBMS '%s' " % dbms
infoMsg += "from session file"
logger.info(infoMsg)
Backend.setDbms(dbms)
Backend.setVersionList(dbmsVersion)
elif expression == "OS" and url == conf.url:
os = unSafeFormatString(value[:-1])
if os and os != 'None':
infoMsg = "resuming back-end DBMS operating system '%s' " % os
infoMsg += "from session file"
logger.info(infoMsg)
if conf.os and conf.os.lower() != os.lower():
message = "you provided '%s' as back-end DBMS operating " % conf.os
message += "system, but from a past scan information on the "
message += "target URL sqlmap assumes the back-end DBMS "
message += "operating system is %s. " % os
message += "Do you really want to force the back-end DBMS "
message += "OS value? [y/N] "
test = readInput(message, default="N")
if not test or test[0] in ("n", "N"):
conf.os = os
else:
conf.os = os
Backend.setOs(conf.os)
elif expression == "Remote temp path" and url == conf.url and conf.tmpPath is None:
conf.tmpPath = unSafeFormatString(value[:-1])
infoMsg = "resuming remote absolute path of temporary "
infoMsg += "files directory '%s' from session file" % conf.tmpPath
logger.info(infoMsg)
elif conf.freshQueries:
pass
elif expression == "xp_cmdshell availability" and url == conf.url:
kb.xpCmdshellAvailable = True if unSafeFormatString(value[:-1]).lower() == "true" else False
infoMsg = "resuming xp_cmdshell availability"
logger.info(infoMsg)

View File

@ -14,7 +14,7 @@ import re
import tempfile import tempfile
import time import time
from lib.core.common import dataToSessionFile from lib.core.common import Backend
from lib.core.common import hashDBRetrieve from lib.core.common import hashDBRetrieve
from lib.core.common import intersect from lib.core.common import intersect
from lib.core.common import paramToDict from lib.core.common import paramToDict
@ -37,14 +37,15 @@ from lib.core.exception import sqlmapUserQuitException
from lib.core.option import authHandler from lib.core.option import authHandler
from lib.core.option import __setDBMS from lib.core.option import __setDBMS
from lib.core.option import __setKnowledgeBaseAttributes from lib.core.option import __setKnowledgeBaseAttributes
from lib.core.session import resumeConfKb
from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR
from lib.core.settings import HOST_ALIASES from lib.core.settings import HOST_ALIASES
from lib.core.settings import REFERER_ALIASES from lib.core.settings import REFERER_ALIASES
from lib.core.settings import RESULTS_FILE_FORMAT from lib.core.settings import RESULTS_FILE_FORMAT
from lib.core.settings import SOAP_REGEX from lib.core.settings import SOAP_REGEX
from lib.core.settings import SUPPORTED_DBMS
from lib.core.settings import UNENCODED_ORIGINAL_VALUE from lib.core.settings import UNENCODED_ORIGINAL_VALUE
from lib.core.settings import UNICODE_ENCODING from lib.core.settings import UNICODE_ENCODING
from lib.core.settings import UNKNOWN_DBMS_VERSION
from lib.core.settings import URI_INJECTABLE_REGEX from lib.core.settings import URI_INJECTABLE_REGEX
from lib.core.settings import USER_AGENT_ALIASES from lib.core.settings import USER_AGENT_ALIASES
from lib.utils.hashdb import HashDB from lib.utils.hashdb import HashDB
@ -243,78 +244,79 @@ def __resumeHashDBValues():
if injection not in kb.injections: if injection not in kb.injections:
kb.injections.append(injection) kb.injections.append(injection)
def __setOutputResume(): __resumeDBMS()
__resumeOS()
def __resumeDBMS():
""" """
Check and set the output text file and the resume functionality. Resume stored DBMS information from HashDB
""" """
if not conf.sessionFile: value = hashDBRetrieve(HASHDB_KEYS.DBMS)
conf.sessionFile = "%s%ssession" % (conf.outputPath, os.sep)
logger.info("using '%s' as a session file" % conf.sessionFile) if not value:
return
if os.path.exists(conf.sessionFile): dbms = value.lower()
if not conf.flushSession: dbmsVersion = [UNKNOWN_DBMS_VERSION]
try: _ = "(%s)" % ("|".join([alias for alias in SUPPORTED_DBMS]))
readSessionFP = codecs.open(conf.sessionFile, "r", UNICODE_ENCODING, 'replace') _ = re.search("%s ([\d\.]+)" % _, dbms, re.I)
__url_cache = set()
__expression_cache = {}
for line in readSessionFP.readlines(): # xreadlines doesn't return unicode strings when codec.open() is used if _:
if line.count("][") == 4: dbms = _.group(1).lower()
line = line.split("][") dbmsVersion = [_.group(2)]
if len(line) != 5: if conf.dbms:
continue if conf.dbms.lower() != dbms:
message = "you provided '%s' as back-end DBMS, " % conf.dbms
message += "but from a past scan information on the target URL "
message += "sqlmap assumes the back-end DBMS is %s. " % dbms
message += "Do you really want to force the back-end "
message += "DBMS value? [y/N] "
test = readInput(message, default="N")
url, _, _, expression, value = line if not test or test[0] in ("n", "N"):
conf.dbms = None
Backend.setDbms(dbms)
Backend.setVersionList(dbmsVersion)
else:
infoMsg = "resuming back-end DBMS '%s' " % dbms
logger.info(infoMsg)
if not value: Backend.setDbms(dbms)
continue Backend.setVersionList(dbmsVersion)
if url[0] == "[": def __resumeOS():
url = url[1:] """
Resume stored OS information from HashDB
"""
value = value.rstrip('\r\n') # Strips both chars independently value = hashDBRetrieve(HASHDB_KEYS.OS)
if url not in ( conf.url, conf.hostname ): if not value:
continue return
if url not in __url_cache: os = value
kb.resumedQueries[url] = {}
kb.resumedQueries[url][expression] = value
__url_cache.add(url)
__expression_cache[url] = set(expression)
resumeConfKb(expression, url, value) if os and os != 'None':
infoMsg = "resuming back-end DBMS operating system '%s' " % os
logger.info(infoMsg)
if expression not in __expression_cache[url]: if conf.os and conf.os.lower() != os.lower():
kb.resumedQueries[url][expression] = value message = "you provided '%s' as back-end DBMS operating " % conf.os
__expression_cache[url].add(value) message += "system, but from a past scan information on the "
elif len(value) >= len(kb.resumedQueries[url][expression]): message += "target URL sqlmap assumes the back-end DBMS "
kb.resumedQueries[url][expression] = value message += "operating system is %s. " % os
message += "Do you really want to force the back-end DBMS "
message += "OS value? [y/N] "
test = readInput(message, default="N")
if kb.injection.place is not None and kb.injection.parameter is not None: if not test or test[0] in ("n", "N"):
kb.injections.append(kb.injection) conf.os = os
except IOError, msg:
errMsg = "unable to properly open the session file (%s)" % msg
raise sqlmapFilePathException, errMsg
else:
readSessionFP.close()
else: else:
try: conf.os = os
os.remove(conf.sessionFile)
logger.info("flushing session file")
except OSError, msg:
errMsg = "unable to flush the session file (%s)" % msg
raise sqlmapFilePathException, errMsg
try: Backend.setOs(conf.os)
conf.sessionFP = codecs.open(conf.sessionFile, "a", UNICODE_ENCODING)
dataToSessionFile("\n[%s]\n" % time.strftime("%X %x"))
except IOError:
errMsg = "unable to write on the session file specified"
raise sqlmapFilePathException, errMsg
def __setResultsFile(): def __setResultsFile():
""" """
@ -435,7 +437,6 @@ def initTargetEnv():
conf.paramDict = {} conf.paramDict = {}
conf.parameters = {} conf.parameters = {}
conf.sessionFile = None
conf.hashDBFile = None conf.hashDBFile = None
__setKnowledgeBaseAttributes(False) __setKnowledgeBaseAttributes(False)
@ -445,7 +446,6 @@ def initTargetEnv():
def setupTargetEnv(): def setupTargetEnv():
__createTargetDirs() __createTargetDirs()
__setRequestParams() __setRequestParams()
__setOutputResume()
__setHashDB() __setHashDB()
__resumeHashDBValues() __resumeHashDBValues()
__setResultsFile() __setResultsFile()

View File

@ -126,7 +126,6 @@ def liveTest():
name = None name = None
log = [] log = []
session = []
switches = dict(global_) switches = dict(global_)
if case.hasAttribute("name"): if case.hasAttribute("name"):
@ -143,14 +142,9 @@ def liveTest():
if item.hasAttribute("value"): if item.hasAttribute("value"):
log.append(replaceVars(item.getAttribute("value"), vars_)) log.append(replaceVars(item.getAttribute("value"), vars_))
if case.getElementsByTagName("session"):
for item in case.getElementsByTagName("session")[0].getElementsByTagName("item"):
if item.hasAttribute("value"):
session.append(replaceVars(item.getAttribute("value"), vars_))
msg = "running live test case '%s' (%d/%d)" % (name, count, length) msg = "running live test case '%s' (%d/%d)" % (name, count, length)
logger.info(msg) logger.info(msg)
result = runCase(switches, log, session) result = runCase(switches, log)
if result: if result:
logger.info("test passed") logger.info("test passed")
else: else:
@ -178,7 +172,6 @@ def initCase(switches=None):
if key in cmdLineOptions.__dict__: if key in cmdLineOptions.__dict__:
cmdLineOptions.__dict__[key] = value cmdLineOptions.__dict__[key] = value
conf.sessionFile = None
init(cmdLineOptions, True) init(cmdLineOptions, True)
__setVerbosity() __setVerbosity()
@ -190,7 +183,7 @@ def cleanCase():
conf.verbose = 1 conf.verbose = 1
__setVerbosity() __setVerbosity()
def runCase(switches=None, log=None, session=None): def runCase(switches=None, log=None):
retVal = True retVal = True
initCase(switches) initCase(switches)
@ -198,19 +191,6 @@ def runCase(switches=None, log=None, session=None):
if result == False: #if None ignore if result == False: #if None ignore
retVal = False retVal = False
if session and retVal:
ifile = open(conf.sessionFile, 'r')
content = ifile.read()
ifile.close()
for item in session:
if item.startswith("r'") and item.endswith("'"):
if not re.search(item[2:-1], content, re.DOTALL):
retVal = False
break
elif content.find(item) < 0:
retVal = False
break
if log and retVal: if log and retVal:
ifile = open(conf.dumper.getOutputFile(), 'r') ifile = open(conf.dumper.getOutputFile(), 'r')
content = ifile.read() content = ifile.read()

View File

@ -50,12 +50,11 @@ from lib.core.unescaper import unescaper
from lib.request.connect import Connect as Request from lib.request.connect import Connect as Request
from lib.request.direct import direct from lib.request.direct import direct
from lib.techniques.blind.inference import bisection from lib.techniques.blind.inference import bisection
from lib.techniques.blind.inference import queryOutputLength
from lib.techniques.dns.test import dnsTest from lib.techniques.dns.test import dnsTest
from lib.techniques.dns.use import dnsUse from lib.techniques.dns.use import dnsUse
from lib.techniques.error.use import errorUse from lib.techniques.error.use import errorUse
from lib.techniques.union.use import unionUse from lib.techniques.union.use import unionUse
from lib.utils.resume import queryOutputLength
from lib.utils.resume import resume
def __goInference(payload, expression, charsetType=None, firstChar=None, lastChar=None, dump=False): def __goInference(payload, expression, charsetType=None, firstChar=None, lastChar=None, dump=False):
start = time.time() start = time.time()
@ -488,8 +487,4 @@ def goStacked(expression, silent=False):
Request.queryPage(payload, content=False, silent=silent, noteResponseTime=False, timeBasedCompare=True) Request.queryPage(payload, content=False, silent=silent, noteResponseTime=False, timeBasedCompare=True)
def checkBooleanExpression(expression, expectingNone=True): def checkBooleanExpression(expression, expectingNone=True):
kb.suppressSession = True return getValue(unescaper.unescape(expression), expected=EXPECTED.BOOL, suppressOutput=True, expectingNone=expectingNone)
value = getValue(unescaper.unescape(expression), expected=EXPECTED.BOOL, suppressOutput=True, expectingNone=expectingNone)
kb.suppressSession = False
return value

View File

@ -7,12 +7,14 @@ Copyright (c) 2006-2012 sqlmap developers (http://www.sqlmap.org/)
See the file 'doc/COPYING' for copying permission See the file 'doc/COPYING' for copying permission
""" """
import re
import threading import threading
import time import time
from extra.safe2bin.safe2bin import safecharencode from extra.safe2bin.safe2bin import safecharencode
from lib.core.agent import agent from lib.core.agent import agent
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.common import calculateDeltaSeconds
from lib.core.common import dataToStdout from lib.core.common import dataToStdout
from lib.core.common import decodeHexValue from lib.core.common import decodeHexValue
from lib.core.common import decodeIntToUnicode from lib.core.common import decodeIntToUnicode
@ -24,6 +26,7 @@ from lib.core.common import getPartRun
from lib.core.common import hashDBRetrieve from lib.core.common import hashDBRetrieve
from lib.core.common import hashDBWrite from lib.core.common import hashDBWrite
from lib.core.common import incrementCounter from lib.core.common import incrementCounter
from lib.core.common import randomStr
from lib.core.common import safeStringFormat from lib.core.common import safeStringFormat
from lib.core.common import setFormatterPrependFlag from lib.core.common import setFormatterPrependFlag
from lib.core.common import singleTimeWarnMessage from lib.core.common import singleTimeWarnMessage
@ -31,6 +34,7 @@ from lib.core.data import conf
from lib.core.data import kb from lib.core.data import kb
from lib.core.data import logger from lib.core.data import logger
from lib.core.data import queries from lib.core.data import queries
from lib.core.enums import CHARSET_TYPE
from lib.core.enums import DBMS from lib.core.enums import DBMS
from lib.core.enums import PAYLOAD from lib.core.enums import PAYLOAD
from lib.core.exception import sqlmapThreadException from lib.core.exception import sqlmapThreadException
@ -546,3 +550,56 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
_ = finalValue or partialValue _ = finalValue or partialValue
return getCounter(kb.technique), safecharencode(_) if kb.safeCharEncode else _ return getCounter(kb.technique), safecharencode(_) if kb.safeCharEncode else _
def queryOutputLength(expression, payload):
"""
Returns the query output length.
"""
lengthQuery = queries[Backend.getIdentifiedDbms()].length.query
select = re.search("\ASELECT\s+", expression, re.I)
selectTopExpr = re.search("\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", expression, re.I)
selectDistinctExpr = re.search("\ASELECT\s+DISTINCT\((.+?)\)\s+FROM", expression, re.I)
selectFromExpr = re.search("\ASELECT\s+(.+?)\s+FROM", expression, re.I)
selectExpr = re.search("\ASELECT\s+(.+)$", expression, re.I)
miscExpr = re.search("\A(.+)", expression, re.I)
if selectTopExpr or selectDistinctExpr or selectFromExpr or selectExpr:
if selectTopExpr:
regExpr = selectTopExpr.groups()[0]
elif selectDistinctExpr:
regExpr = selectDistinctExpr.groups()[0]
elif selectFromExpr:
regExpr = selectFromExpr.groups()[0]
elif selectExpr:
regExpr = selectExpr.groups()[0]
elif miscExpr:
regExpr = miscExpr.groups()[0]
if ( select and re.search("\A(COUNT|LTRIM)\(", regExpr, re.I) ) or len(regExpr) <= 1:
return None, None, None
if selectDistinctExpr:
lengthExpr = "SELECT %s FROM (%s)" % (lengthQuery % regExpr, expression)
if Backend.getIdentifiedDbms() in ( DBMS.MYSQL, DBMS.PGSQL ):
lengthExpr += " AS %s" % randomStr(lowercase=True)
elif select:
lengthExpr = expression.replace(regExpr, lengthQuery % regExpr, 1)
else:
lengthExpr = lengthQuery % expression
infoMsg = "retrieving the length of query output"
logger.info(infoMsg)
start = time.time()
lengthExprUnescaped = unescaper.unescape(lengthExpr)
count, length = bisection(payload, lengthExprUnescaped, charsetType=CHARSET_TYPE.DIGITS)
debugMsg = "performed %d queries in %d seconds" % (count, calculateDeltaSeconds(start))
logger.debug(debugMsg)
if length == " ":
length = 0
return count, length, regExpr

View File

@ -1,188 +0,0 @@
#!/usr/bin/env python
"""
$Id$
Copyright (c) 2006-2012 sqlmap developers (http://www.sqlmap.org/)
See the file 'doc/COPYING' for copying permission
"""
import re
import time
from lib.core.common import calculateDeltaSeconds
from lib.core.common import dataToSessionFile
from lib.core.common import dataToStdout
from lib.core.common import Backend
from lib.core.common import safeStringFormat
from lib.core.common import randomStr
from lib.core.common import replaceNewlineTabs
from lib.core.common import restoreDumpMarkedChars
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.data import queries
from lib.core.enums import DBMS
from lib.core.enums import CHARSET_TYPE
from lib.core.unescaper import unescaper
from lib.techniques.blind.inference import bisection
def queryOutputLength(expression, payload):
"""
Returns the query output length.
"""
lengthQuery = queries[Backend.getIdentifiedDbms()].length.query
select = re.search("\ASELECT\s+", expression, re.I)
selectTopExpr = re.search("\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", expression, re.I)
selectDistinctExpr = re.search("\ASELECT\s+DISTINCT\((.+?)\)\s+FROM", expression, re.I)
selectFromExpr = re.search("\ASELECT\s+(.+?)\s+FROM", expression, re.I)
selectExpr = re.search("\ASELECT\s+(.+)$", expression, re.I)
miscExpr = re.search("\A(.+)", expression, re.I)
if selectTopExpr or selectDistinctExpr or selectFromExpr or selectExpr:
if selectTopExpr:
regExpr = selectTopExpr.groups()[0]
elif selectDistinctExpr:
regExpr = selectDistinctExpr.groups()[0]
elif selectFromExpr:
regExpr = selectFromExpr.groups()[0]
elif selectExpr:
regExpr = selectExpr.groups()[0]
elif miscExpr:
regExpr = miscExpr.groups()[0]
if ( select and re.search("\A(COUNT|LTRIM)\(", regExpr, re.I) ) or len(regExpr) <= 1:
return None, None, None
if selectDistinctExpr:
lengthExpr = "SELECT %s FROM (%s)" % (lengthQuery % regExpr, expression)
if Backend.getIdentifiedDbms() in ( DBMS.MYSQL, DBMS.PGSQL ):
lengthExpr += " AS %s" % randomStr(lowercase=True)
elif select:
lengthExpr = expression.replace(regExpr, lengthQuery % regExpr, 1)
else:
lengthExpr = lengthQuery % expression
infoMsg = "retrieving the length of query output"
logger.info(infoMsg)
start = time.time()
lengthExprUnescaped = unescaper.unescape(lengthExpr)
count, length = bisection(payload, lengthExprUnescaped, charsetType=CHARSET_TYPE.DIGITS)
debugMsg = "performed %d queries in %d seconds" % (count, calculateDeltaSeconds(start))
logger.debug(debugMsg)
if length == " ":
length = 0
return count, length, regExpr
def resume(expression, payload):
"""
This function can be called to resume part or entire output of a
SQL injection query output.
"""
try:
if "sqlmapfile" in expression or "sqlmapoutput" in expression or conf.freshQueries:
return None
condition = (
kb.resumedQueries and conf.url in kb.resumedQueries
and expression in kb.resumedQueries[conf.url]
)
if not condition:
return None
resumedValue = kb.resumedQueries[conf.url][expression]
if not resumedValue:
return None
resumedValue = restoreDumpMarkedChars(resumedValue, True)
if resumedValue[-1] == "]":
resumedValue = resumedValue[:-1]
infoMsg = "read from file '%s': " % conf.sessionFile
if "\n" in resumedValue:
infoMsg += "%s..." % resumedValue.split("\n")[0]
else:
infoMsg += resumedValue
if not kb.suppressResumeInfo:
dataToStdout("[%s] [INFO] %s\n" % (time.strftime("%X"), infoMsg))
return resumedValue
# If we called this function without providing a payload it means
# that we have called it from lib/request/inject __goInband() or
# from __goError() function so we return to the calling function
# so that the query output will be retrieved taking advantage
# of either error-based or inband SQL injection vulnerability.
if not payload:
return None
if not Backend.getIdentifiedDbms():
return None
substringQuery = queries[Backend.getIdentifiedDbms()].substring.query
select = re.search("\ASELECT ", expression, re.I)
_, length, regExpr = queryOutputLength(expression, payload)
if not length:
return None
if len(resumedValue) == int(length):
infoMsg = "read from file '%s': " % conf.sessionFile
infoMsg += "%s" % resumedValue.split("\n")[0]
logger.info(infoMsg)
dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injection.place, conf.parameters[kb.injection.place], expression, replaceNewlineTabs(resumedValue)))
return resumedValue
elif len(resumedValue) < int(length):
infoMsg = "resumed from file '%s': " % conf.sessionFile
infoMsg += "%s..." % resumedValue.split("\n")[0]
logger.info(infoMsg)
dataToSessionFile("[%s][%s][%s][%s][%s" % (conf.url, kb.injection.place, conf.parameters[kb.injection.place], expression, replaceNewlineTabs(resumedValue)))
if select:
newExpr = expression.replace(regExpr, safeStringFormat(substringQuery, (regExpr, len(resumedValue) + 1, int(length))), 1)
else:
newExpr = safeStringFormat(substringQuery, (expression, len(resumedValue) + 1, int(length)))
missingCharsLength = int(length) - len(resumedValue)
infoMsg = "retrieving pending %d query " % missingCharsLength
infoMsg += "output characters"
logger.info(infoMsg)
start = time.time()
count, finalValue = bisection(payload, newExpr, length=missingCharsLength)
debugMsg = "performed %d queries in %d seconds" % (count, calculateDeltaSeconds(start))
logger.debug(debugMsg)
if len(finalValue) != ( int(length) - len(resumedValue) ):
warnMsg = "the total length of the query is not "
warnMsg += "right, sqlmap is going to retrieve the "
warnMsg += "query value from the beginning now"
logger.warn(warnMsg)
return None
return "%s%s" % (resumedValue, finalValue)
return None
except ValueError:
errMsg = "invalid resume value for expression: '%s'" % expression
logger.error(errMsg)
return None