From 0ff3b1ce70722f9c66c3b044c13845fb843c9312 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 28 Oct 2020 16:48:11 +0100 Subject: [PATCH] Implemented FOR JSON AUTO in MsSQL --- data/xml/queries.xml | 4 +-- lib/core/option.py | 1 - lib/core/settings.py | 2 +- lib/techniques/union/use.py | 67 +++++++++++------------------------- plugins/generic/databases.py | 12 ------- 5 files changed, 23 insertions(+), 63 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 33267e870..0e60c3173 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -198,11 +198,11 @@ - + - + diff --git a/lib/core/option.py b/lib/core/option.py index 6b28901ca..222e2aa3c 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2113,7 +2113,6 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.responseTimeMode = None kb.responseTimePayload = None kb.resumeValues = True - kb.rowXmlMode = False kb.safeCharEncode = False kb.safeReq = AttribDict() kb.secondReq = None diff --git a/lib/core/settings.py b/lib/core/settings.py index e54302067..4db5996fc 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.4.10.25" +VERSION = "1.4.10.26" 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/techniques/union/use.py b/lib/techniques/union/use.py index ddb772901..241fcea7e 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -75,7 +75,7 @@ def _oneShotUnionUse(expression, unpack=True, limited=False): if retVal is None: vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector - if not any((kb.rowXmlMode, kb.jsonAggMode)): + if not kb.jsonAggMode: injExpression = unescaper.escape(agent.concatQuery(expression, unpack)) kb.unionDuplicates = vector[7] kb.forcePartialUnion = vector[8] @@ -100,49 +100,26 @@ def _oneShotUnionUse(expression, unpack=True, limited=False): incrementCounter(PAYLOAD.TECHNIQUE.UNION) - if kb.rowXmlMode: - output = extractRegexResult(r"(?P()+)", page or "") - if output: - try: - root = xml.etree.ElementTree.fromstring(safeStringFormat("%s", getBytes(output))) - retVal = "" - for column in kb.dumpColumns: - base64 = True - for child in root: - value = child.attrib.get(column, "").strip() - if value and not re.match(r"\A[a-zA-Z0-9+/]+={0,2}\Z", value): - base64 = False - break - - try: - decodeBase64(value) - except (binascii.Error, TypeError): - base64 = False - break - - if base64: - for child in root: - child.attrib[column] = decodeBase64(child.attrib.get(column, ""), binary=False) or NULL - - for child in root: - row = [] - for column in kb.dumpColumns: - row.append(child.attrib.get(column, NULL)) - retVal += "%s%s%s" % (kb.chars.start, kb.chars.delimiter.join(row), kb.chars.stop) - - except: - pass - else: - retVal = getUnicode(retVal) - elif kb.jsonAggMode: - if Backend.isDbms(DBMS.PGSQL): + if kb.jsonAggMode: + if Backend.isDbms(DBMS.MSSQL): + output = extractRegexResult(r"%s(?P.*)%s" % (kb.chars.start, kb.chars.stop), page or "") + if output: + try: + retVal = "" + fields = re.findall(r'"([^"]+)":', extractRegexResult(r"{(?P[^}]+)}", output)) + for row in json.loads(output): + retVal += "%s%s%s" % (kb.chars.start, kb.chars.delimiter.join(getUnicode(row[field] or NULL) for field in fields), kb.chars.stop) + except: + pass + else: + retVal = getUnicode(retVal) + elif Backend.isDbms(DBMS.PGSQL): output = extractRegexResult(r"(?P%s.*%s)" % (kb.chars.start, kb.chars.stop), page or "") if output: retVal = output else: - output = extractRegexResult(r"(?P%s.*?%s)" % (kb.chars.start, kb.chars.stop), page or "") + output = extractRegexResult(r"%s(?P.*?)%s" % (kb.chars.start, kb.chars.stop), page or "") if output: - output = output[len(kb.chars.start):-len(kb.chars.stop)] try: retVal = "" for row in json.loads(output): @@ -177,7 +154,7 @@ def _oneShotUnionUse(expression, unpack=True, limited=False): hashDBWrite("%s%s" % (conf.hexConvert or False, expression), retVal) - elif not any((kb.rowXmlMode, kb.jsonAggMode)): + elif not kb.jsonAggMode: trimmed = _("%s(?P.*?)<" % (kb.chars.start)) if trimmed: @@ -261,7 +238,7 @@ def unionUse(expression, unpack=True, dump=False): debugMsg += "it does not play well with UNION query SQL injection" singleTimeDebugMessage(debugMsg) - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ORACLE, DBMS.PGSQL) and expressionFields: + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ORACLE, DBMS.PGSQL, DBMS.MSSQL) and expressionFields: match = re.search(r"SELECT\s*(.+?)\bFROM", expression, re.I) if match and not (Backend.isDbms(DBMS.ORACLE) and FROM_DUMMY_TABLE[DBMS.ORACLE] in expression): kb.jsonAggMode = True @@ -271,15 +248,11 @@ def unionUse(expression, unpack=True, dump=False): query = expression.replace(expressionFields, "'%s'||JSON_ARRAYAGG(%s)||'%s'" % (kb.chars.start, ("||'%s'||" % kb.chars.delimiter).join(expressionFieldsList), kb.chars.stop), 1) elif Backend.isDbms(DBMS.PGSQL): # Note: ARRAY_AGG does CSV alike output, thus enclosing start/end inside each item query = expression.replace(expressionFields, "ARRAY_AGG('%s'||%s||'%s')::text" % (kb.chars.start, ("||'%s'||" % kb.chars.delimiter).join("COALESCE(%s::text,' ')" % field for field in expressionFieldsList), kb.chars.stop), 1) + elif Backend.isDbms(DBMS.MSSQL): + query = "'%s'+(%s FOR JSON AUTO, INCLUDE_NULL_VALUES)+'%s'" % (kb.chars.start, expression, kb.chars.stop) output = _oneShotUnionUse(query, False) value = parseUnionPage(output) kb.jsonAggMode = False - elif Backend.isDbms(DBMS.MSSQL) and kb.dumpColumns: - kb.rowXmlMode = True - _ = "(%s FOR XML RAW, BINARY BASE64)" % expression - output = _oneShotUnionUse(_, False) - value = parseUnionPage(output) - kb.rowXmlMode = False # We have to check if the SQL query might return multiple entries # if the technique is partial UNION query and in such case forge the diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py index 7a3dfe4dd..eb2061753 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -639,18 +639,6 @@ class Databases(object): logger.info(infoMsg) values = None - if Backend.isDbms(DBMS.MSSQL) and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION): - expression = query - kb.dumpColumns = [] - kb.rowXmlMode = True - - for column in (extractRegexResult(r"SELECT (?P.+?) FROM", query) or "").split(','): - kb.dumpColumns.append(randomStr().lower()) - expression = expression.replace(column, "%s AS %s" % (column, kb.dumpColumns[-1]), 1) - - values = unionUse(expression) - kb.rowXmlMode = False - kb.dumpColumns = None if values is None: values = inject.getValue(query, blind=False, time=False)