diff --git a/doc/THANKS b/doc/THANKS index 28e8e109c..4aa285642 100644 --- a/doc/THANKS +++ b/doc/THANKS @@ -532,6 +532,9 @@ Anthony Zboralski Thierry Zoller for reporting a couple of major bugs +Zhen Zhou + for suggesting a feature + -insane- for reporting a minor bug diff --git a/lib/core/option.py b/lib/core/option.py index 11e43af3d..e31c0cff4 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1424,6 +1424,7 @@ def __setKnowledgeBaseAttributes(flushAll=True): kb.nullConnection = None kb.pageTemplate = None kb.pageTemplates = dict() + kb.orderByColumns = None kb.originalPage = None # Back-end DBMS underlying operating system fingerprint via banner (-b) diff --git a/lib/core/settings.py b/lib/core/settings.py index ee48947a7..2482182a7 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -388,3 +388,6 @@ BIGARRAY_CHUNK_LENGTH = 4096 # Only console display last n table rows TRIM_STDOUT_DUMP_SIZE = 1024 + +# Step used in ORDER BY technique used for finding the right number of columns in UNION query injections +ORDER_BY_STEP = 10 diff --git a/lib/techniques/union/test.py b/lib/techniques/union/test.py index db454283f..72f9f38a8 100644 --- a/lib/techniques/union/test.py +++ b/lib/techniques/union/test.py @@ -21,8 +21,10 @@ from lib.core.common import getUnicode from lib.core.common import listToStrValue from lib.core.common import popValue from lib.core.common import pushValue +from lib.core.common import randomInt from lib.core.common import randomStr from lib.core.common import removeReflectiveValues +from lib.core.common import singleTimeLogMessage from lib.core.common import singleTimeWarnMessage from lib.core.common import stdev from lib.core.common import wasLastRequestDBMSError @@ -39,6 +41,7 @@ from lib.core.settings import MIN_RATIO from lib.core.settings import MAX_RATIO from lib.core.settings import MIN_STATISTICAL_RANGE from lib.core.settings import MIN_UNION_RESPONSES +from lib.core.settings import ORDER_BY_STEP from lib.core.unescaper import unescaper from lib.parse.html import htmlParser from lib.request.comparison import comparison @@ -50,11 +53,53 @@ def __findUnionCharCount(comment, place, parameter, value, prefix, suffix, where """ retVal = None + def __orderByTechnique(): + def __orderByTest(cols): + query = agent.prefixQuery("ORDER BY %d" % cols, prefix=prefix) + query = agent.suffixQuery(query, suffix=suffix, comment=comment) + payload = agent.payload(newValue=query, place=place, parameter=parameter, where=where) + page, _ = Request.queryPage(payload, place=place, content=True, raise404=False) + return not re.search(r"((warning|error)[^\n]*order)|(order by)", page or "", re.I) + + if __orderByTest(1) and not __orderByTest(randomInt()): + infoMsg = "ORDER BY technique seems to be usable. " + infoMsg += "this should dramatically reduce the " + infoMsg += "time needed to find the right number " + infoMsg += "of query columns. Automatically extending the " + infoMsg += "range for UNION query injection technique" + singleTimeLogMessage(infoMsg) + + lowCols, highCols = 1, ORDER_BY_STEP + found = None + while not found: + if __orderByTest(highCols): + lowCols = highCols + highCols += ORDER_BY_STEP + else: + while not found: + mid = highCols - (highCols - lowCols) / 2 + if __orderByTest(mid): + lowCols = mid + else: + highCols = mid + if (highCols - lowCols) < 2: + found = lowCols + + return found + pushValue(kb.errorIsNone) items, ratios = [], [] kb.errorIsNone = False lowerCount, upperCount = conf.uColsStart, conf.uColsStop + if lowerCount == 1: + found = kb.orderByColumns or __orderByTechnique() + if found: + kb.orderByColumns = found + infoMsg = "target url appears to have %d columns in query" % found + singleTimeLogMessage(infoMsg) + return found + if abs(upperCount - lowerCount) < MIN_UNION_RESPONSES: upperCount = lowerCount + MIN_UNION_RESPONSES diff --git a/plugins/generic/enumeration.py b/plugins/generic/enumeration.py index 31e37c764..e96e2cb50 100644 --- a/plugins/generic/enumeration.py +++ b/plugins/generic/enumeration.py @@ -2031,10 +2031,8 @@ class Enumeration: query += exclDbsQuery values = inject.getValue(query, blind=False) - if not isNoneValue(values): - if isinstance(values, basestring): - values = [ values ] - + if not any([isNoneValue(values), isinstance(values, basestring)]): + values = filter(lambda x: isinstance(x, (tuple, list, set)) and len(x) == 2, values) for foundDb, foundTbl in values: foundDb = safeSQLIdentificatorNaming(foundDb) foundTbl = safeSQLIdentificatorNaming(foundTbl, True)