diff --git a/lib/core/common.py b/lib/core/common.py index 6992420a5..9e2a489c2 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -57,6 +57,7 @@ from lib.core.exception import sqlmapFilePathException from lib.core.exception import sqlmapNoneDataException from lib.core.exception import sqlmapMissingDependence from lib.core.exception import sqlmapSyntaxException +from lib.core.optiondict import optDict from lib.core.settings import DESCRIPTION from lib.core.settings import IS_WIN from lib.core.settings import PLATFORM @@ -417,7 +418,7 @@ def fileToStr(fileName): @rtype: C{str} """ - filePointer = codecs.open(fileName, "r", conf.dataEncoding) + filePointer = codecs.open(fileName, "rb", conf.dataEncoding) fileText = filePointer.read() return fileText.replace(" ", "").replace("\t", "").replace("\r", "").replace("\n", " ") @@ -1106,7 +1107,8 @@ def profile(profileOutputFile=None, dotOutputFile=None, imageOutputFile=None): import gtk import pydot except ImportError, e: - logger.error(e) + errMsg = "profiling requires third-party libraries (%s)" % str(e) + logger.error(errMsg) return if profileOutputFile is None: @@ -1209,6 +1211,9 @@ def initCommonOutputs(): for line in cfile.xreadlines(): line = line.strip() + if line.startswith('#'): + continue + if len(line) > 1: if line[0] == '[' and line[-1] == ']': key = line[1:-1] @@ -1220,20 +1225,27 @@ def initCommonOutputs(): cfile.close() -def getGoodSamaritanParameters(part, prevValue, originalCharset): +def goGoodSamaritan(part, prevValue, originalCharset): """ - Function for retrieving parameters needed for good samaritan (common outputs) feature. - Returns singleValue if there is a complete single match (in part of common-outputs.txt set by parameter 'part') - regarding parameter prevValue. If there is no single value match, but multiple, predictedCharset is returned - containing more probable characters (retrieved from matched items in common-outputs.txt) together with the - rest of charset as otherCharset + Function for retrieving parameters needed for common prediction (good + samaritan) feature. + + part is for instance Users, Databases, Tables and corresponds to the + header (e.g. [Users]) in txt/common-outputs.txt. + + prevValue: retrieved query output so far (e.g. 'i'). + + Returns singleValue if there is a complete single match (in part of + txt/common-outputs.txt under 'part') regarding parameter prevValue. If + there is no single value match, but multiple, commonCharset is + returned containing more probable characters (retrieved from matched + values in txt/common-outputs.txt) together with the rest of charset as + otherCharset. """ + if kb.commonOutputs is None: initCommonOutputs() - if not part or not prevValue: #is not None and != "" - return None, None, originalCharset - predictionSet = set() wildIndexes = [] singleValue = None @@ -1249,29 +1261,34 @@ def getGoodSamaritanParameters(part, prevValue, originalCharset): charIndex += 1 findIndex = prevValue.find('.', charIndex) + # If the header we are looking for has common outputs defined if part in kb.commonOutputs: for item in kb.commonOutputs[part]: + # Check if the common output (item) starts with prevValue if re.search('\A%s' % prevValue, item): singleValue = item + for index in wildIndexes: char = item[index] if char not in predictionSet: predictionSet.add(char) - predictedCharset = [] + commonCharset = [] otherCharset = [] + # Split the original charset into common chars (commonCharset) + # and other chars (otherCharset) for ordChar in originalCharset: if chr(ordChar) not in predictionSet: otherCharset.append(ordChar) else: - predictedCharset.append(ordChar) + commonCharset.append(ordChar) - predictedCharset.sort() + commonCharset.sort() - if len(predictedCharset) > 1: - return None, predictedCharset, otherCharset + if len(commonCharset) > 1: + return None, commonCharset, otherCharset else: return singleValue, None, originalCharset else: @@ -1279,8 +1296,12 @@ def getGoodSamaritanParameters(part, prevValue, originalCharset): def getCompiledRegex(regex, *args): """ - Returns compiled regular expression and stores it in cache for further usage + Returns compiled regular expression and stores it in cache for further + usage """ + + global __compiledRegularExpressions + if (regex, args) in __compiledRegularExpressions: return __compiledRegularExpressions[(regex, args)] else: @@ -1290,15 +1311,23 @@ def getCompiledRegex(regex, *args): def getPartRun(): """ - Goes through call stack and finds constructs matching conf.dmbsHandler.*. Returns it or it's alias used in common-outputs.txt + Goes through call stack and finds constructs matching conf.dmbsHandler.*. + Returns it or its alias used in txt/common-outputs.txt """ - commonPartsDict = { "getTables":"Tables", "getColumns":"Columns", "getUsers":"Users", "getBanner":"Banners", "getDbs":"Databases" } + retVal = None + commonPartsDict = optDict["Enumeration"] stack = [item[4][0] if isinstance(item[4], list) else '' for item in inspect.stack()] reobj = getCompiledRegex('conf\.dbmsHandler\.([^(]+)\(\)') + + # Goes backwards through the stack to find the conf.dbmsHandler method + # calling this function for i in xrange(len(stack) - 1, 0, -1): match = reobj.search(stack[i]) + if match: + # This is the calling conf.dbmsHandler method (e.g. 'getDbms') retVal = match.groups()[0] break - return commonPartsDict[retVal] if retVal in commonPartsDict else retVal + + return commonPartsDict[retVal][1] if retVal in commonPartsDict else retVal diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 7bd1e9871..3820c9fca 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -23,7 +23,10 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ optDict = { - # Family: { "parameter_name": "parameter_datatype" }, + # Format: + # Family: { "parameter name": "parameter datatype" }, + # Or: + # Family: { "parameter name": ("parameter datatype", "category name used for common outputs feature") }, "Target": { "direct": "string", "url": "string", @@ -84,17 +87,17 @@ optDict = { }, "Enumeration": { - "getBanner": "boolean", - "getCurrentUser": "boolean", - "getCurrentDb": "boolean", + "getBanner": ("boolean", "Banners"), + "getCurrentUser": ("boolean", "Users"), + "getCurrentDb": ("boolean", "Databases"), "isDba": "boolean", - "getUsers": "boolean", - "getPasswordHashes": "boolean", - "getPrivileges": "boolean", - "getRoles": "boolean", - "getDbs": "boolean", - "getTables": "boolean", - "getColumns": "boolean", + "getUsers": ("boolean", "Users"), + "getPasswordHashes": ("boolean", "Hashes"), + "getPrivileges": ("boolean", "Privileges"), + "getRoles": ("boolean", "Roles"), + "getDbs": ("boolean", "Databases"), + "getTables": ("boolean", "Tables"), + "getColumns": ("boolean", "Columns"), "dumpTable": "boolean", "dumpAll": "boolean", "search": "boolean", @@ -107,6 +110,8 @@ optDict = { "limitStop": "integer", "firstChar": "integer", "lastChar": "integer", + "getNumOfTables": "integer", + "getNumOfDBs": "integer", "query": "string", "sqlShell": "boolean" }, @@ -144,6 +149,7 @@ optDict = { }, "Miscellaneous": { + "xmlFile": "string", "sessionFile": "string", "flushSession": "boolean", "eta": "boolean", diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 696d1d4ab..5d20501ce 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -30,7 +30,7 @@ from lib.core.agent import agent from lib.core.common import dataToSessionFile from lib.core.common import dataToStdout from lib.core.common import getCharset -from lib.core.common import getGoodSamaritanParameters +from lib.core.common import goGoodSamaritan from lib.core.common import getPartRun from lib.core.common import replaceNewlineTabs from lib.core.common import safeStringFormat @@ -53,11 +53,12 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None """ partialValue = "" - finalValue = "" - + finalValue = "" asciiTbl = getCharset(charsetType) - kb.partRun = getPartRun() if conf.useCommonPrediction else None #set kb.partRun in case common-prediction used + # Set kb.partRun in case "common prediction" feature (a.k.a. "good + # samaritan") is used + kb.partRun = getPartRun() if conf.useCommonPrediction else None if "LENGTH(" in expression or "LEN(" in expression: firstChar = 0 @@ -116,9 +117,8 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None else: dataToStdout("[%s] [INFO] retrieved: " % time.strftime("%X")) - queriesCount = [0] # As list to deal with nested scoping rules - - hintlock = threading.Lock() + queriesCount = [0] # As list to deal with nested scoping rules + hintlock = threading.Lock() def tryHint(idx): hintlock.acquire() @@ -131,9 +131,9 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None else: posValue = ord(hintValue[idx-1]) - queriesCount[0] += 1 forgedPayload = safeStringFormat(payload.replace('%3E', '%3D'), (expressionUnescaped, idx, posValue)) - result = Request.queryPage(urlencode(forgedPayload)) + queriesCount[0] += 1 + result = Request.queryPage(urlencode(forgedPayload)) if result: return hintValue[idx-1] @@ -155,6 +155,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None if len(charTbl) == 1: forgedPayload = safeStringFormat(payload.replace('%3E', '%3D'), (expressionUnescaped, idx, charTbl[0])) + queriesCount[0] += 1 result = Request.queryPage(urlencode(forgedPayload)) if result: return chr(charTbl[0]) if charTbl[0] < 128 else unichr(charTbl[0]) @@ -165,9 +166,8 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None minValue = charTbl[0] while len(charTbl) != 1: - queriesCount[0] += 1 - position = (len(charTbl) >> 1) - posValue = charTbl[position] + position = (len(charTbl) >> 1) + posValue = charTbl[position] if kb.dbms == "SQLite": posValueOld = posValue @@ -181,7 +181,8 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None else: forgedPayload = safeStringFormat(payload.replace('%3E', 'NOT BETWEEN 0 AND'), (expressionUnescaped, idx, posValue)) - result = Request.queryPage(urlencode(forgedPayload)) + queriesCount[0] += 1 + result = Request.queryPage(urlencode(forgedPayload)) if kb.dbms == "SQLite": posValue = posValueOld @@ -249,7 +250,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None if conf.threadContinue: charStart = time.time() - val = getChar(curidx) + val = getChar(curidx) if val is None: raise sqlmapValueException, "failed to get character at index %d (expected %d total)" % (curidx, length) @@ -344,6 +345,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None raise infoMsg = None + # If we have got one single character not correctly fetched it # can mean that the connection to the target url was lost if None in value: @@ -369,32 +371,44 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None index = firstChar while True: - index += 1 + index += 1 charStart = time.time() - #common prediction (a.k.a. good samaritan) - if conf.useCommonPrediction: - singleValue, predictedCharset, otherCharset = getGoodSamaritanParameters(kb.partRun, finalValue, asciiTbl) + # Common prediction feature (a.k.a. "good samaritan") + # NOTE: to be used only when multi-threading is not set for + # the moment + if conf.useCommonPrediction and len(finalValue) > 0 and kb.partRun is not None: val = None + singleValue, commonCharset, otherCharset = goGoodSamaritan(kb.partRun, finalValue, asciiTbl) - #if there is no singleValue (single match from common-outputs.txt) use the returned predictedCharset - if singleValue is None: - val = getChar(index, predictedCharset, False) if predictedCharset else None - else: - #one shot query containing equals singleValue + # If there is no singleValue (single match from + # txt/common-outputs.txt) use the returned common + # charset only to retrieve the query output + if singleValue is not None: + # One-shot query containing equals singleValue query = agent.prefixQuery(" %s" % safeStringFormat('AND (%s) = %s', (expressionUnescaped, unescaper.unescape('\'%s\'' % singleValue)))) query = agent.postfixQuery(query) + queriesCount[0] += 1 result = Request.queryPage(urlencode(agent.payload(newValue=query))) - #did we have luck? + + # Did we have luck? if result: dataToSessionFile(replaceNewlineTabs(singleValue[index-1:])) + if showEta: - etaProgressUpdate(time.time() - charStart, lastChar + 1) + etaProgressUpdate(time.time() - charStart, len(singleValue)) elif conf.verbose >= 1: dataToStdout(singleValue[index-1:]) + finalValue = singleValue + break - #if we had no luck with singleValue and predictedCharset use the returned otherCharset + elif commonCharset: + # TODO: this part does not seem to work yet + val = getChar(index, commonCharset, False) + + # If we had no luck with singleValue and common charset, + # use the returned other charset if not val: val = getChar(index, otherCharset) else: