From af9725214a0decb83ba15a7fe2b3239512ebefb2 Mon Sep 17 00:00:00 2001 From: Bernardo Damele Date: Wed, 12 Jan 2011 12:01:32 +0000 Subject: [PATCH] Properly deal with partial (single entry) UNION injections. Got rid of kb.union*, now it's all stored/used from kb.injection. Minor bug fix with where=2 detection phase. --- lib/controller/checks.py | 10 ++++++++- lib/core/option.py | 1 - lib/core/session.py | 4 ---- lib/request/inject.py | 5 ----- lib/techniques/inband/union/test.py | 35 +++++++++++------------------ lib/techniques/inband/union/use.py | 4 +++- 6 files changed, 25 insertions(+), 34 deletions(-) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index ba112bcff..f477cfd78 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -378,8 +378,12 @@ def checkSqlInjection(place, parameter, value): # In case of UNION query SQL injection elif method == PAYLOAD.METHOD.UNION: + # Test for UNION injection and set the sample + # payload as well as the vector. + # NOTE: vector is set to a tuple with 6 elements, + # used afterwards by Agent.forgeInbandQuery() + # method to forge the UNION query payload configUnion(test.request.char, test.request.columns) - dbmsToUnescape = dbms if dbms is not None else injection.dbms reqPayload, vector = unionTest(comment, place, parameter, value, prefix, suffix, dbmsToUnescape) @@ -389,6 +393,10 @@ def checkSqlInjection(place, parameter, value): injectable = True + # Overwrite 'where' because it can differ + # in unionTest()'s vector (1 or 2) + where = vector[5] + # If the injection test was successful feed the injection # object with the test's details if injectable is True: diff --git a/lib/core/option.py b/lib/core/option.py index c3e7be8dd..bbc52cbad 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1164,7 +1164,6 @@ def __setKnowledgeBaseAttributes(flushAll=True): kb.threadContinue = True kb.threadException = False kb.threadData = {} - kb.unionNegative = False if flushAll: kb.keywords = set(getFileItems(paths.SQL_KEYWORDS)) diff --git a/lib/core/session.py b/lib/core/session.py index eddc04cd4..0a9258c66 100644 --- a/lib/core/session.py +++ b/lib/core/session.py @@ -191,10 +191,6 @@ def setOs(): if condition: dataToSessionFile("[%s][%s][%s][OS][%s]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), safeFormatString(kb.os))) -def setUnion(negative=False): - if negative: - kb.unionNegative = True - def setRemoteTempPath(): condition = ( not kb.resumedQueries or ( kb.resumedQueries.has_key(conf.url) and diff --git a/lib/request/inject.py b/lib/request/inject.py index 5b84467f7..646920fbc 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -425,9 +425,6 @@ def getValue(expression, blind=True, inband=True, error=True, time=True, fromUse count += 1 found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE - oldUnionNegative = kb.unionNegative - kb.unionNegative = False - if error and isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) and not found: kb.technique = PAYLOAD.TECHNIQUE.ERROR @@ -461,8 +458,6 @@ def getValue(expression, blind=True, inband=True, error=True, time=True, fromUse else: value = __goInferenceProxy(query, fromUser, expected, batch, resumeValue, unpack, charsetType, firstChar, lastChar) - kb.unionNegative = oldUnionNegative - if value and isinstance(value, basestring): value = value.strip() else: diff --git a/lib/techniques/inband/union/test.py b/lib/techniques/inband/union/test.py index bf1ddd93b..bec353ea1 100644 --- a/lib/techniques/inband/union/test.py +++ b/lib/techniques/inband/union/test.py @@ -21,14 +21,13 @@ from lib.core.data import logger from lib.core.data import queries from lib.core.enums import DBMS from lib.core.enums import PAYLOAD -from lib.core.session import setUnion from lib.core.unescaper import unescaper from lib.parse.html import htmlParser from lib.request.connect import Connect as Request def __unionPosition(comment, place, parameter, value, prefix, suffix, dbms, count, where=1): validPayload = None - unionVector = None + vector = None # For each column of the table (# of NULL) perform a request using # the UNION ALL SELECT statement to test it the target url is @@ -48,7 +47,7 @@ def __unionPosition(comment, place, parameter, value, prefix, suffix, dbms, coun if resultPage and randQuery in resultPage and " UNION ALL SELECT " not in resultPage: validPayload = payload - unionVector = (exprPosition, count, comment, prefix, suffix) + vector = (exprPosition, count, comment, prefix, suffix, where) if where == 1: # Prepare expression with delimiters @@ -64,34 +63,26 @@ def __unionPosition(comment, place, parameter, value, prefix, suffix, dbms, coun resultPage, _ = Request.queryPage(payload, place=place, content=True) if resultPage and (randQuery not in resultPage or randQuery2 not in resultPage): - setUnion(negative=True) + vector = (exprPosition, count, comment, prefix, suffix, 2) break - return validPayload, unionVector + return validPayload, vector def __unionConfirm(comment, place, parameter, value, prefix, suffix, dbms, count): validPayload = None - unionVector = None + vector = None # Confirm the inband SQL injection and get the exact column # position which can be used to extract data - validPayload, unionVector = __unionPosition(comment, place, parameter, value, prefix, suffix, dbms, count) + validPayload, vector = __unionPosition(comment, place, parameter, value, prefix, suffix, dbms, count) # Assure that the above function found the exploitable full inband # SQL injection position if not validPayload: - validPayload, unionVector = __unionPosition(comment, place, parameter, value, prefix, suffix, dbms, count, where=2) + validPayload, vector = __unionPosition(comment, place, parameter, value, prefix, suffix, dbms, count, where=2) - # Assure that the above function found the exploitable partial - # (single entry) inband SQL injection position with negative - # parameter validPayload - if not validPayload: - return None, None - else: - setUnion(negative=True) - - return validPayload, unionVector + return validPayload, vector def __unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix, dbms): """ @@ -101,7 +92,7 @@ def __unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix """ validPayload = None - unionVector = None + vector = None query = agent.prefixQuery("UNION ALL SELECT %s" % conf.uChar) for count in range(conf.uColsStart, conf.uColsStop+1): @@ -118,14 +109,14 @@ def __unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix debugMsg = "testing number of columns: %s" % status logger.debug(debugMsg) - validPayload, unionVector = __unionConfirm(comment, place, parameter, value, prefix, suffix, dbms, count) + validPayload, vector = __unionConfirm(comment, place, parameter, value, prefix, suffix, dbms, count) if validPayload: break clearConsoleLine(True) - return validPayload, unionVector + return validPayload, vector def unionTest(comment, place, parameter, value, prefix, suffix, dbms): """ @@ -138,9 +129,9 @@ def unionTest(comment, place, parameter, value, prefix, suffix, dbms): oldTechnique = kb.technique kb.technique = PAYLOAD.TECHNIQUE.UNION - validPayload, unionVector = __unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix, dbms) + validPayload, vector = __unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix, dbms) if validPayload: validPayload = agent.removePayloadDelimiters(validPayload, False) - return validPayload, unionVector + return validPayload, vector diff --git a/lib/techniques/inband/union/use.py b/lib/techniques/inband/union/use.py index 33a9aac1e..f1862b6f9 100644 --- a/lib/techniques/inband/union/use.py +++ b/lib/techniques/inband/union/use.py @@ -56,7 +56,7 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, nullCh expression = agent.concatQuery(expression, unpack) expression = unescaper.unescape(expression) - if kb.unionNegative and not direct: + if kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == 2 and not direct: _, _, _, _, _, expressionFieldsList, expressionFields = agent.getFields(origExpr) # We have to check if the SQL query might return multiple entries @@ -194,6 +194,8 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, nullCh status = '%d/%d entries (%d%s)' % (count, length, round(100.0*count/length), '%') dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), status), True) + dataToStdout("\n") + except KeyboardInterrupt: print warnMsg = "Ctrl+C detected in dumping phase"