From 95cd9e2af3703fc63325bdfa2d0ece960565214a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 20 Dec 2011 12:52:41 +0000 Subject: [PATCH] adding support for scanning Host header values (-p host) --- lib/controller/checks.py | 2 +- lib/controller/controller.py | 6 ++++++ lib/core/agent.py | 2 +- lib/core/common.py | 20 +++++++++++++++----- lib/core/enums.py | 1 + lib/core/optiondict.py | 1 + lib/core/settings.py | 3 ++- lib/core/target.py | 15 +++++++++++++-- lib/parse/cmdline.py | 3 +++ lib/request/connect.py | 15 ++++++++++----- sqlmap.conf | 8 +++++--- 11 files changed, 58 insertions(+), 18 deletions(-) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 26bd4df93..8614f77f3 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -437,7 +437,7 @@ def checkSqlInjection(place, parameter, value): # Feed with the boundaries details only the first time a # test has been successful if injection.place is None or injection.parameter is None: - if place in (PLACE.UA, PLACE.REFERER): + if place in (PLACE.UA, PLACE.REFERER, PLACE.HOST): injection.parameter = place else: injection.parameter = parameter diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 6f0740d63..29fdf3d9c 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -54,6 +54,7 @@ from lib.core.settings import DEFAULT_GET_POST_DELIMITER from lib.core.settings import EMPTY_FORM_FIELDS_REGEX from lib.core.settings import IGNORE_PARAMETERS from lib.core.settings import LOW_TEXT_PERCENT +from lib.core.settings import HOST_ALIASES from lib.core.settings import REFERER_ALIASES from lib.core.settings import USER_AGENT_ALIASES from lib.core.target import initTargetEnv @@ -395,6 +396,10 @@ def start(): skip = (place == PLACE.UA and conf.level < 3) skip |= (place == PLACE.REFERER and conf.level < 3) + # Test Host header only if + # --level >= 5 + skip |= (place == PLACE.HOST and conf.level < 5) + # Test Cookie header only if --level >= 2 skip |= (place == PLACE.COOKIE and conf.level < 2) @@ -404,6 +409,7 @@ def start(): skip &= not (place == PLACE.UA and intersect(USER_AGENT_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.REFERER and intersect(REFERER_ALIASES, conf.testParameter, True)) + skip &= not (place == PLACE.HOST and intersect(HOST_ALIASES, conf.testParameter, True)) if skip: continue diff --git a/lib/core/agent.py b/lib/core/agent.py index 78e8d3b28..3dcdb440b 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -116,7 +116,7 @@ class Agent: retValue = ET.tostring(root) elif place == PLACE.URI: retValue = paramString.replace("%s%s" % (origValue, URI_INJECTION_MARK_CHAR), self.addPayloadDelimiters(newValue)) - elif place in (PLACE.UA, PLACE.REFERER): + elif place in (PLACE.UA, PLACE.REFERER, PLACE.HOST): retValue = paramString.replace(origValue, self.addPayloadDelimiters(newValue)) else: retValue = paramString.replace("%s=%s" % (parameter, origValue), diff --git a/lib/core/common.py b/lib/core/common.py index 70540dc16..9ca62a193 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -88,6 +88,9 @@ from lib.core.settings import VERSION from lib.core.settings import REVISION from lib.core.settings import VERSION_STRING from lib.core.settings import SITE +from lib.core.settings import HOST_ALIASES +from lib.core.settings import REFERER_ALIASES +from lib.core.settings import USER_AGENT_ALIASES from lib.core.settings import ERROR_PARSING_REGEXES from lib.core.settings import PRINTABLE_CHAR_REGEX from lib.core.settings import SQL_STATEMENTS @@ -706,13 +709,14 @@ def paramToDict(place, parameters=None): if len(conf.testParameter) > 1: warnMsg = "provided parameters '%s' " % paramStr warnMsg += "are not inside the %s" % place + logger.warn(warnMsg) else: parameter = conf.testParameter[0] - warnMsg = "provided parameter '%s' " % paramStr - warnMsg += "is not inside the %s" % place - - logger.warn(warnMsg) + if not intersect(USER_AGENT_ALIASES + REFERER_ALIASES + HOST_ALIASES, parameter, True): + warnMsg = "provided parameter '%s' " % paramStr + warnMsg += "is not inside the %s" % place + logger.warn(warnMsg) elif len(conf.testParameter) != len(testableParameters.keys()): for parameter in conf.testParameter: @@ -1277,12 +1281,18 @@ def parseTargetUrl(): conf.url = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, conf.path) conf.url = conf.url.replace(URI_QUESTION_MARKER, '?') - if not conf.referer and conf.level >= 3: + if not conf.referer and (conf.level >= 3 or intersect(REFERER_ALIASES, conf.testParameter, True)): debugMsg = "setting the HTTP Referer header to the target url" logger.debug(debugMsg) conf.httpHeaders = filter(lambda (key, value): key != HTTPHEADER.REFERER, conf.httpHeaders) conf.httpHeaders.append((HTTPHEADER.REFERER, conf.url)) + if not conf.host and (conf.level >= 5 or intersect(HOST_ALIASES, conf.testParameter, True)): + debugMsg = "setting the HTTP Host header to the target url" + logger.debug(debugMsg) + conf.httpHeaders = filter(lambda (key, value): key != HTTPHEADER.HOST, conf.httpHeaders) + conf.httpHeaders.append((HTTPHEADER.HOST, getHostHeader(conf.url))) + def expandAsteriskForColumns(expression): # If the user provided an asterisk rather than the column(s) # name, sqlmap will retrieve the columns itself and reprocess diff --git a/lib/core/enums.py b/lib/core/enums.py index c65cca404..a1bbe982e 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -48,6 +48,7 @@ class PLACE: COOKIE = "Cookie" UA = "User-Agent" REFERER = "Referer" + HOST = "Host" class HTTPMETHOD: GET = "GET" diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 34dd87397..1928f5d2b 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -31,6 +31,7 @@ optDict = { "agent": "string", "randomAgent": "boolean", "rParam": "string", + "host": "string", "referer": "string", "headers": "string", "aType": "string", diff --git a/lib/core/settings.py b/lib/core/settings.py index e766c8217..8a6b6a6c0 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -178,8 +178,9 @@ DBMS_DICT = { DBMS.MSSQL: (MSSQL_ALIASES, "python-pymssql", "http://pymssql.sour DBMS.DB2: (DB2_ALIASES, "python ibm-db", "http://code.google.com/p/ibm-db/") } -REFERER_ALIASES = ( "ref", "referer", "referrer" ) USER_AGENT_ALIASES = ( "ua", "useragent", "user-agent" ) +REFERER_ALIASES = ( "ref", "referer", "referrer" ) +HOST_ALIASES = ( "host", ) FROM_TABLE = { DBMS.ORACLE: " FROM DUAL", diff --git a/lib/core/target.py b/lib/core/target.py index f77d15b84..bae9de675 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -34,6 +34,7 @@ from lib.core.exception import sqlmapUserQuitException from lib.core.option import __setDBMS from lib.core.option import __setKnowledgeBaseAttributes from lib.core.session import resumeConfKb +from lib.core.settings import HOST_ALIASES from lib.core.settings import REFERER_ALIASES from lib.core.settings import RESULTS_FILE_FORMAT from lib.core.settings import SOAP_REGEX @@ -141,7 +142,7 @@ def __setRequestParams(): conf.paramDict[PLACE.COOKIE] = __paramDict __testableParameters = True - # Perform checks on User-Agent header value + # Perform checks on header values if conf.httpHeaders: for httpHeader, headerValue in conf.httpHeaders: if httpHeader == PLACE.UA: @@ -164,9 +165,19 @@ def __setRequestParams(): conf.paramDict[PLACE.REFERER] = { PLACE.REFERER: headerValue } __testableParameters = True + elif httpHeader == PLACE.HOST: + # No need for url encoding/decoding the host + conf.parameters[PLACE.HOST] = urldecode(headerValue) + + condition = any((not conf.testParameter, intersect(conf.testParameter, HOST_ALIASES))) + + if condition: + conf.paramDict[PLACE.HOST] = { PLACE.HOST: headerValue } + __testableParameters = True + if not conf.parameters: errMsg = "you did not provide any GET, POST and Cookie " - errMsg += "parameter, neither an User-Agent or Referer header" + errMsg += "parameter, neither an User-Agent, Referer or Host header value" raise sqlmapGenericException, errMsg elif not __testableParameters: diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 875404e26..3400a4146 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -92,6 +92,9 @@ def cmdLineParser(): request.add_option("--randomize", dest="rParam", help="Randomly change value for given parameter(s)") + request.add_option("--host", dest="host", + help="HTTP Host header") + request.add_option("--referer", dest="referer", help="HTTP Referer header") diff --git a/lib/request/connect.py b/lib/request/connect.py index 75af5668f..3a2ac424f 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -142,6 +142,7 @@ class Connect: cookie = kwargs.get('cookie', None) ua = kwargs.get('ua', None) referer = kwargs.get('referer', None) + host = kwargs.get('host', conf.host) direct = kwargs.get('direct', False) multipart = kwargs.get('multipart', False) silent = kwargs.get('silent', False) @@ -237,7 +238,7 @@ class Connect: requestMsg += " %s" % httplib.HTTPConnection._http_vsn_str - # Perform HTTP request + # Prepare HTTP headers headers = forgeHeaders(cookie, ua, referer) if conf.realTest: @@ -251,7 +252,7 @@ class Connect: headers[HTTPHEADER.ACCEPT] = HTTP_ACCEPT_HEADER_VALUE - headers[HTTPHEADER.HOST] = getHostHeader(url) + headers[HTTPHEADER.HOST] = host or getHostHeader(url) if auxHeaders: for key, item in auxHeaders.items(): @@ -533,6 +534,7 @@ class Connect: cookie = None ua = None referer = None + host = None page = None pageLength = None uri = None @@ -596,6 +598,9 @@ class Connect: if PLACE.REFERER in conf.parameters: referer = conf.parameters[PLACE.REFERER] if place != PLACE.REFERER or not value else value + if PLACE.HOST in conf.parameters: + host = conf.parameters[PLACE.HOST] if place != PLACE.HOST or not value else value + if PLACE.URI in conf.parameters: uri = conf.url if place != PLACE.URI or not value else value else: @@ -688,7 +693,7 @@ class Connect: if conf.safUrl and conf.saFreq > 0: kb.queryCounter += 1 if kb.queryCounter % conf.saFreq == 0: - Connect.getPage(url=conf.safUrl, cookie=cookie, direct=True, silent=True, ua=ua, referer=referer) + Connect.getPage(url=conf.safUrl, cookie=cookie, direct=True, silent=True, ua=ua, referer=referer, host=host) start = time.time() @@ -701,7 +706,7 @@ class Connect: auxHeaders[HTTPHEADER.RANGE] = "bytes=-1" - _, headers, code = Connect.getPage(url=uri, get=get, post=post, cookie=cookie, ua=ua, referer=referer, silent=silent, method=method, auxHeaders=auxHeaders, raise404=raise404) + _, headers, code = Connect.getPage(url=uri, get=get, post=post, cookie=cookie, ua=ua, referer=referer, host=host, silent=silent, method=method, auxHeaders=auxHeaders, raise404=raise404) if headers: if kb.nullConnection == NULLCONNECTION.HEAD and HTTPHEADER.CONTENT_LENGTH in headers: @@ -710,7 +715,7 @@ class Connect: pageLength = int(headers[HTTPHEADER.CONTENT_RANGE][headers[HTTPHEADER.CONTENT_RANGE].find('/') + 1:]) if not pageLength: - page, headers, code = Connect.getPage(url=uri, get=get, post=post, cookie=cookie, ua=ua, referer=referer, silent=silent, method=method, auxHeaders=auxHeaders, response=response, raise404=raise404, ignoreTimeout=timeBasedCompare) + page, headers, code = Connect.getPage(url=uri, get=get, post=post, cookie=cookie, ua=ua, referer=referer, host=host, silent=silent, method=method, auxHeaders=auxHeaders, response=response, raise404=raise404, ignoreTimeout=timeBasedCompare) threadData.lastQueryDuration = calculateDeltaSeconds(start) diff --git a/sqlmap.conf b/sqlmap.conf index fe819e054..6bd57c12f 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -59,14 +59,16 @@ agent = # Valid: True or False randomAgent = False -# Randomly change value for the given parameter -rParam = - +# HTTP Host header. +host = # HTTP Referer header. Useful to fake the HTTP Referer header value at # each HTTP request. referer = +# Randomly change value for the given parameter +rParam = + # Extra HTTP headers # Note: There must be a space at the beginning of each header line. headers = Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5