diff --git a/lib/controller/checks.py b/lib/controller/checks.py index b049afca7..946bd13b0 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -898,7 +898,7 @@ def checkNullConnection(): logger.info(infoMsg) try: - page, headers = Request.getPage(method=HTTPMETHOD.HEAD) + page, headers, _ = Request.getPage(method=HTTPMETHOD.HEAD) if not page and HTTPHEADER.CONTENT_LENGTH in headers: kb.nullConnection = NULLCONNECTION.HEAD @@ -906,7 +906,7 @@ def checkNullConnection(): infoMsg = "NULL connection is supported with HEAD header" logger.info(infoMsg) else: - page, headers = Request.getPage(auxHeaders={HTTPHEADER.RANGE: "bytes=-1"}) + page, headers, _ = Request.getPage(auxHeaders={HTTPHEADER.RANGE: "bytes=-1"}) if page and len(page) == 1 and HTTPHEADER.CONTENT_RANGE in headers: kb.nullConnection = NULLCONNECTION.RANGE diff --git a/lib/core/option.py b/lib/core/option.py index 0ef7f5303..0ad69a7f7 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1330,6 +1330,9 @@ def __cleanupOptions(): else: kb.adjustTimeDelay = False + if conf.code: + conf.code = int(conf.code) + def __setConfAttributes(): """ This function set some needed attributes into the configuration diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index d3e5f3a89..052d4a672 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -68,6 +68,7 @@ optDict = { "risk": "integer", "string": "string", "regexp": "string", + "code": "string", "textOnly": "boolean", "titles": "boolean" }, diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index ecb474774..c612c2054 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -200,13 +200,16 @@ def cmdLineParser(): "default %d)" % defaults.level) detection.add_option("--string", dest="string", - help="String to match in page when the " + help="String to match in the response when " "query is valid") detection.add_option("--regexp", dest="regexp", - help="Regexp to match in page when the " + help="Regexp to match in the response when " "query is valid") + detection.add_option("--code", dest="code", type="int", + help="HTTP response code to match when the query is valid") + detection.add_option("--text-only", dest="textOnly", action="store_true", help="Compare pages based only on the textual content") diff --git a/lib/request/comparison.py b/lib/request/comparison.py index f31ffbf92..a39cef43a 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -28,7 +28,7 @@ from lib.core.settings import LOWER_RATIO_BOUND from lib.core.settings import UPPER_RATIO_BOUND from lib.core.threads import getCurrentThreadData -def comparison(page, headers, getRatioValue=False, pageLength=None): +def comparison(page, headers, code=None, getRatioValue=False, pageLength=None): if page is None and pageLength is None: return None @@ -50,6 +50,9 @@ def comparison(page, headers, getRatioValue=False, pageLength=None): condition = re.search(conf.regexp, rawResponse, re.I | re.M) is not None return condition if not getRatioValue else (MAX_RATIO if condition else MIN_RATIO) + if isinstance(code, int) and conf.code: + return code == conf.code + if page: # In case of an DBMS error page return None if kb.errorIsNone and (wasLastRequestDBMSError() or wasLastRequestHTTPError()): diff --git a/lib/request/connect.py b/lib/request/connect.py index 49424cf14..af1e719c0 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -306,7 +306,7 @@ class Connect: # Return response object if response: - return conn, None + return conn, None, None # Get HTTP response page = conn.read() @@ -369,7 +369,7 @@ class Connect: warnMsg = "connection timed out while trying " warnMsg += "to get error page information (%d)" % e.code logger.warn(warnMsg) - return None, None + return None, None, None except: pass @@ -409,7 +409,7 @@ class Connect: processResponse(page, responseHeaders) elif e.code == 504: if ignoreTimeout: - return None, None + return None, None, None else: warnMsg = "unable to connect to the target url (%d - %s)" % (e.code, httplib.responses[e.code]) if threadData.retriesCount < conf.retries and not kb.threadException and not conf.realTest: @@ -418,14 +418,14 @@ class Connect: return Connect.__retryProxy(**kwargs) elif kb.testMode: logger.critical(warnMsg) - return None, None + return None, None, None else: raise sqlmapConnectionException, warnMsg else: debugMsg = "got HTTP error code: %d (%s)" % (code, status) logger.debug(debugMsg) processResponse(page, responseHeaders) - return page, responseHeaders + return page, responseHeaders, code except (urllib2.URLError, socket.error, socket.timeout, httplib.BadStatusLine, httplib.IncompleteRead), e: tbMsg = traceback.format_exc() @@ -454,16 +454,16 @@ class Connect: if "forcibly closed" in tbMsg: logger.critical(warnMsg) - return None, None + return None, None, None elif silent or (ignoreTimeout and any(map(lambda x: x in tbMsg, ["timed out", "IncompleteRead"]))): - return None, None + return None, None, None elif threadData.retriesCount < conf.retries and not kb.threadException and not conf.realTest: warnMsg += ", sqlmap is going to retry the request" logger.critical(warnMsg) return Connect.__retryProxy(**kwargs) elif kb.testMode: logger.critical(warnMsg) - return None, None + return None, None, None else: raise sqlmapConnectionException, warnMsg @@ -485,7 +485,7 @@ class Connect: logger.log(7, responseMsg) - return page, responseHeaders + return page, responseHeaders, code @staticmethod def queryPage(value=None, place=None, content=False, getRatioValue=False, silent=False, method=None, timeBasedCompare=False, noteResponseTime=True, auxHeaders=None, response=False, raise404=None): @@ -613,7 +613,7 @@ class Connect: auxHeaders[HTTPHEADER.RANGE] = "bytes=-1" - _, headers = 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, silent=silent, method=method, auxHeaders=auxHeaders, raise404=raise404) if headers: if kb.nullConnection == NULLCONNECTION.HEAD and HTTPHEADER.CONTENT_LENGTH in headers: @@ -622,7 +622,7 @@ class Connect: pageLength = int(headers[HTTPHEADER.CONTENT_RANGE][headers[HTTPHEADER.CONTENT_RANGE].find('/') + 1:]) if not pageLength: - page, headers = 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, silent=silent, method=method, auxHeaders=auxHeaders, response=response, raise404=raise404, ignoreTimeout=timeBasedCompare) threadData.lastQueryDuration = calculateDeltaSeconds(start) @@ -643,8 +643,8 @@ class Connect: page = removeReflectiveValues(page, payload) if getRatioValue: - return comparison(page, headers, getRatioValue=False, pageLength=pageLength), comparison(page, headers, getRatioValue=True, pageLength=pageLength) + return comparison(page, headers, code, getRatioValue=False, pageLength=pageLength), comparison(page, headers, code, getRatioValue=True, pageLength=pageLength) elif pageLength or page: - return comparison(page, headers, getRatioValue, pageLength) + return comparison(page, headers, code, getRatioValue, pageLength) else: return False diff --git a/lib/takeover/web.py b/lib/takeover/web.py index a76a25ec1..58822d2fa 100644 --- a/lib/takeover/web.py +++ b/lib/takeover/web.py @@ -62,7 +62,7 @@ class Web: cmd = conf.osCmd cmdUrl = "%s?cmd=%s" % (self.webBackdoorUrl, cmd) - page, _ = Request.getPage(url=cmdUrl, direct=True, silent=True) + page, _, _ = Request.getPage(url=cmdUrl, direct=True, silent=True) if page is not None: output = re.search("
(.+?)", page, re.I | re.S) @@ -237,7 +237,7 @@ class Web: self.webBaseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, uriPath) self.webStagerUrl = "%s/%s" % (self.webBaseUrl, stagerName) - uplPage, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False) + uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False) if "sqlmap file uploader" not in uplPage: if localPath not in warned: diff --git a/lib/techniques/union/test.py b/lib/techniques/union/test.py index 22bbb261d..863a14221 100644 --- a/lib/techniques/union/test.py +++ b/lib/techniques/union/test.py @@ -109,7 +109,7 @@ def __findUnionCharCount(comment, place, parameter, value, prefix, suffix, where query = agent.forgeInbandQuery('', -1, count, comment, prefix, suffix, kb.uChar) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) - ratio = comparison(page, headers, True) or MIN_RATIO + ratio = comparison(page, headers, getRatioValue=True) or MIN_RATIO ratios.append(ratio) min_, max_ = min(min_, ratio), max(max_, ratio) items.append((count, ratio)) diff --git a/sqlmap.conf b/sqlmap.conf index 152a10d3d..77d097d1e 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -204,12 +204,12 @@ level = 1 # Default: 1 risk = 1 -# String to match within the page content when the query is valid, only +# String to match within the raw response when the query is valid, only # needed if the page content dynamically changes at each refresh. # Refer to the user's manual for further details. string = -# Regular expression to match within the page content when the query is +# Regular expression to match within the raw response when the query is # valid, only needed if the needed if the page content dynamically changes # at each refresh. # Refer to the user's manual for further details. @@ -217,6 +217,12 @@ string = # (http://www.python.org/doc/2.5.2/lib/re-syntax.html) regexp = +# HTTP response code to match when the query is valid +# Valid: True or False +# Example: 200 (assuming any False statement returns a different response +# code) +code = + # Compare pages based only on the textual content # Valid: True or False textOnly = False