diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 263c28276..e2df21bcf 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -31,6 +31,7 @@ from lib.core.common import getSafeExString from lib.core.common import hashDBRetrieve from lib.core.common import hashDBWrite from lib.core.common import intersect +from lib.core.common import isDigit from lib.core.common import isListLike from lib.core.common import parseTargetUrl from lib.core.common import popValue @@ -129,7 +130,7 @@ def _selectInjection(): message += "[q] Quit" choice = readInput(message, default='0').upper() - if choice.isdigit() and int(choice) < len(kb.injections) and int(choice) >= 0: + if isDigit(choice) and int(choice) < len(kb.injections) and int(choice) >= 0: index = int(choice) elif choice == 'Q': raise SqlmapUserQuitException diff --git a/lib/core/common.py b/lib/core/common.py index 45cdfea5f..e2ced30fd 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1245,6 +1245,22 @@ def isZipFile(filename): return openFile(filename, "rb", encoding=None).read(len(ZIP_HEADER)) == ZIP_HEADER +def isDigit(value): + """ + Checks if provided (string) value consists of digits (Note: Python's isdigit() is problematic) + + >>> u'\xb2'.isdigit() + True + >>> isDigit(u'\xb2') + False + >>> isDigit('123456') + True + >>> isDigit('3b3') + False + """ + + return re.search(r"\A[0-9]+\Z", value or "") is not None + def checkFile(filename, raiseOnError=True): """ Checks for file existence and readability diff --git a/lib/core/patch.py b/lib/core/patch.py index e0f19baf2..60ac0ef10 100644 --- a/lib/core/patch.py +++ b/lib/core/patch.py @@ -20,6 +20,7 @@ import thirdparty.chardet.universaldetector from lib.core.common import filterNone from lib.core.common import getSafeExString +from lib.core.common import isDigit from lib.core.common import isListLike from lib.core.common import readInput from lib.core.common import shellExec @@ -62,6 +63,7 @@ def resolveCrossReferences(): Place for cross-reference resolution """ + lib.core.threads.isDigit = isDigit lib.core.threads.readInput = readInput lib.core.common.getPageTemplate = getPageTemplate lib.core.convert.filterNone = filterNone diff --git a/lib/core/settings.py b/lib/core/settings.py index b648114a7..316073349 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -18,7 +18,7 @@ from lib.core.enums import OS from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.3.10.6" +VERSION = "1.3.10.7" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/lib/core/threads.py b/lib/core/threads.py index e37b3b3bd..7b860d185 100644 --- a/lib/core/threads.py +++ b/lib/core/threads.py @@ -73,6 +73,10 @@ def readInput(message, default=None, checkBatch=True, boolean=False): # It will be overwritten by original from lib.core.common pass +def isDigit(value): + # It will be overwritten by original from lib.core.common + pass + def getCurrentThreadData(): """ Returns current thread's local data @@ -125,10 +129,12 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio choice = readInput(message, default=str(numThreads)) if choice: skipThreadCheck = False + if choice.endswith('!'): choice = choice[:-1] skipThreadCheck = True - if choice.isdigit(): + + if isDigit(choice): if int(choice) > MAX_NUMBER_OF_THREADS and not skipThreadCheck: errMsg = "maximum number of used threads is %d avoiding potential connection issues" % MAX_NUMBER_OF_THREADS logger.critical(errMsg) diff --git a/lib/request/inject.py b/lib/request/inject.py index ffdeeb66a..a475eae9e 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -24,6 +24,7 @@ from lib.core.common import getTechniqueData from lib.core.common import hashDBRetrieve from lib.core.common import hashDBWrite from lib.core.common import initTechnique +from lib.core.common import isDigit from lib.core.common import isNoneValue from lib.core.common import isNumPosStrValue from lib.core.common import isTechniqueAvailable @@ -235,7 +236,7 @@ def _goInferenceProxy(expression, fromUser=False, batch=False, unpack=True, char elif choice == 'Q': raise SqlmapUserQuitException - elif choice.isdigit() and int(choice) > 0 and int(choice) <= count: + elif isDigit(choice) and int(choice) > 0 and int(choice) <= count: stopLimit = int(choice) infoMsg = "sqlmap is now going to retrieve the " @@ -246,7 +247,7 @@ def _goInferenceProxy(expression, fromUser=False, batch=False, unpack=True, char message = "how many? " stopLimit = readInput(message, default="10") - if not stopLimit.isdigit(): + if not isDigit(stopLimit): errMsg = "invalid choice" logger.error(errMsg) @@ -261,7 +262,7 @@ def _goInferenceProxy(expression, fromUser=False, batch=False, unpack=True, char return None - elif count and not count.isdigit(): + elif count and not isDigit(count): warnMsg = "it was not possible to count the number " warnMsg += "of entries for the SQL query provided. " warnMsg += "sqlmap will assume that it returns only " diff --git a/lib/takeover/metasploit.py b/lib/takeover/metasploit.py index 48fe9e305..18c7a5b84 100644 --- a/lib/takeover/metasploit.py +++ b/lib/takeover/metasploit.py @@ -23,6 +23,7 @@ from lib.core.common import dataToStdout from lib.core.common import Backend from lib.core.common import getLocalIP from lib.core.common import getRemoteIP +from lib.core.common import isDigit from lib.core.common import normalizePath from lib.core.common import ntToPosixSlashes from lib.core.common import pollProcess @@ -154,7 +155,7 @@ class Metasploit(object): choice = readInput(message, default="%d" % default) - if not choice or not choice.isdigit() or int(choice) > maxValue or int(choice) < 1: + if not choice or not isDigit(choice) or int(choice) > maxValue or int(choice) < 1: choice = default choice = int(choice) @@ -241,7 +242,7 @@ class Metasploit(object): elif Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008")): break - elif not choice.isdigit(): + elif not isDigit(choice): logger.warn("invalid value, only digits are allowed") elif int(choice) < 1 or int(choice) > 2: diff --git a/lib/takeover/udf.py b/lib/takeover/udf.py index c8fbf0faa..2848d67ff 100644 --- a/lib/takeover/udf.py +++ b/lib/takeover/udf.py @@ -11,6 +11,7 @@ from lib.core.agent import agent from lib.core.common import Backend from lib.core.common import checkFile from lib.core.common import dataToStdout +from lib.core.common import isDigit from lib.core.common import isStackingAvailable from lib.core.common import readInput from lib.core.common import unArrayizeValue @@ -339,11 +340,9 @@ class UDF(object): if choice == 'Q': break - elif hasattr(choice, "isdigit") and choice.isdigit() and int(choice) > 0 and int(choice) <= len(udfList): + elif isDigit(choice) and int(choice) > 0 and int(choice) <= len(udfList): choice = int(choice) break - elif isinstance(choice, int) and choice > 0 and choice <= len(udfList): - break else: warnMsg = "invalid value, only digits >= 1 and " warnMsg += "<= %d are allowed" % len(udfList) diff --git a/lib/takeover/web.py b/lib/takeover/web.py index 0a583b7f8..a459e2cc9 100644 --- a/lib/takeover/web.py +++ b/lib/takeover/web.py @@ -22,6 +22,7 @@ from lib.core.common import getPublicTypeMembers from lib.core.common import getSQLSnippet from lib.core.common import getTechnique from lib.core.common import getTechniqueData +from lib.core.common import isDigit from lib.core.common import isTechniqueAvailable from lib.core.common import isWindowsDriveLetterPath from lib.core.common import normalizePath @@ -200,7 +201,7 @@ class Web(object): while True: choice = readInput(message, default=str(default)) - if not choice.isdigit(): + if not isDigit(choice): logger.warn("invalid value, only digits are allowed") elif int(choice) < 1 or int(choice) > len(choices): diff --git a/plugins/generic/takeover.py b/plugins/generic/takeover.py index d1953923f..d3c32cbd4 100644 --- a/plugins/generic/takeover.py +++ b/plugins/generic/takeover.py @@ -9,6 +9,7 @@ import os from lib.core.common import Backend from lib.core.common import getSafeExString +from lib.core.common import isDigit from lib.core.common import isStackingAvailable from lib.core.common import openFile from lib.core.common import readInput @@ -101,7 +102,7 @@ class Takeover(Abstraction, Metasploit, ICMPsh, Registry): while True: tunnel = readInput(msg, default='1') - if tunnel.isdigit() and int(tunnel) in (1, 2): + if isDigit(tunnel) and int(tunnel) in (1, 2): tunnel = int(tunnel) break @@ -172,7 +173,7 @@ class Takeover(Abstraction, Metasploit, ICMPsh, Registry): while True: choice = readInput(msg, default='1') - if choice.isdigit() and int(choice) in (1, 2): + if isDigit(choice) and int(choice) in (1, 2): choice = int(choice) break