From a8c9a470923cea567944d18ff92986b8b04bd0ee Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 15 Mar 2012 11:10:58 +0000 Subject: [PATCH] redirect logic rewritten from scratch --- lib/core/settings.py | 6 +++ lib/request/connect.py | 40 +++------------ lib/request/redirecthandler.py | 93 +++++++++++++++++++++------------- 3 files changed, 71 insertions(+), 68 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index f1ca4fa75..7b26ca842 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -463,3 +463,9 @@ HASH_RECOGNITION_QUIT_THRESHOLD = 10000 # Size of a buffer used for log file output BUFFERED_LOG_SIZE = 10000 + +# Maximum number of redirections to any single URL - this is needed because of the state that cookies introduce +MAX_SINGLE_URL_REDIRECTIONS = 4 + +# Maximum total number of redirections (regardless of URL) - before assuming we're in a loop +MAX_TOTAL_REDIRECTIONS = 10 diff --git a/lib/request/connect.py b/lib/request/connect.py index 46157b763..56a4fc50d 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -149,7 +149,6 @@ class Connect: ignoreTimeout = kwargs.get('ignoreTimeout', kb.ignoreTimeout) refreshing = kwargs.get('refreshing', False) retrying = kwargs.get('retrying', False) - redirecting = kwargs.get('redirecting', None) crawling = kwargs.get('crawling', False) if not urlparse.urlsplit(url).netloc: @@ -299,41 +298,20 @@ class Connect: if not kb.proxyAuthHeader and req.has_header(HTTPHEADER.PROXY_AUTHORIZATION): kb.proxyAuthHeader = req.get_header(HTTPHEADER.PROXY_AUTHORIZATION) - if hasattr(conn, "setcookie"): - kb.redirectSetCookie = conn.setcookie - - if hasattr(conn, "redurl") and hasattr(conn, "redcode") and target\ - and not redirecting and not conf.realTest: - - if kb.redirectChoice is None: - msg = "sqlmap got a %d redirect to " % conn.redcode - msg += "'%s'. What do you want to do? " % conn.redurl - msg += "\n[1] Follow the redirection (default)" - msg += "\n[2] Stay on the original page" - msg += "\n[3] Ignore" - choice = readInput(msg, default="1") - - kb.redirectChoice = choice - - if kb.redirectChoice == REDIRECTION.IGNORE: - redirecting = conn.redcode - page = threadData.lastRedirectMsg[1] - skipLogTraffic = True - else: - kb.queryCounter += 1 - kwargs['url'] = conf.url if kb.redirectChoice == REDIRECTION.ORIGINAL else conn.redurl - kwargs['redirecting'] = conn.redcode - return Connect.__getPageProxy(**kwargs) - # Return response object if response: return conn, None, None # Get HTTP response - if page is None: + if hasattr(conn, 'redurl'): + page = threadData.lastRedirectMsg[1] if kb.redirectChoice == REDIRECTION.IGNORE\ + else kb.originalPage if kb.redirectChoice == REDIRECTION.ORIGINAL\ + else conn.read() + skipLogTraffic = True + else: page = conn.read() - code = redirecting or conn.code + code = conn.code responseHeaders = conn.info() responseHeaders[URI_HTTP_HEADER] = conn.geturl() page = decodePage(page, responseHeaders.get(HTTPHEADER.CONTENT_ENCODING), responseHeaders.get(HTTPHEADER.CONTENT_TYPE)) @@ -404,9 +382,7 @@ class Connect: code = e.code threadData.lastHTTPError = (threadData.lastRequestUID, code) - if code not in kb.httpErrorCodes: - kb.httpErrorCodes[code] = 0 - kb.httpErrorCodes[code] += 1 + kb.httpErrorCodes[code] = kb.httpErrorCodes.get(code, 0) + 1 status = getUnicode(e.msg) responseMsg += "[#%d] (%d %s):\n" % (threadData.lastRequestUID, code, status) diff --git a/lib/request/redirecthandler.py b/lib/request/redirecthandler.py index f6816fca8..aa03534e4 100644 --- a/lib/request/redirecthandler.py +++ b/lib/request/redirecthandler.py @@ -11,35 +11,44 @@ import urllib2 import urlparse from lib.core.data import conf +from lib.core.data import kb from lib.core.data import logger from lib.core.common import getHostHeader from lib.core.common import getUnicode from lib.core.common import logHTTPTraffic +from lib.core.common import readInput from lib.core.enums import HTTPHEADER +from lib.core.enums import REDIRECTION from lib.core.exception import sqlmapConnectionException +from lib.core.settings import MAX_SINGLE_URL_REDIRECTIONS +from lib.core.settings import MAX_TOTAL_REDIRECTIONS from lib.core.threads import getCurrentThreadData from lib.request.basic import decodePage class SmartRedirectHandler(urllib2.HTTPRedirectHandler): - # maximum number of redirections to any single URL - # this is needed because of the state that cookies introduce - max_repeats = 4 - - # maximum total number of redirections (regardless of URL) before - # assuming we're in a loop - max_redirections = 10 - def _get_header_redirect(self, headers): retVal = None - if "location" in headers: - retVal = headers.getheaders("location")[0].split("?")[0] - elif "uri" in headers: - retVal = headers.getheaders("uri")[0].split("?")[0] + if headers: + if "location" in headers: + retVal = headers.getheaders("location")[0].split("?")[0] + elif "uri" in headers: + retVal = headers.getheaders("uri")[0].split("?")[0] return retVal - def common_http_redirect(self, result, headers, code, content, msg): + def _ask_redirect_choice(self, redcode, redurl): + if kb.redirectChoice is None: + msg = "sqlmap got a %d redirect to " % redcode + msg += "'%s'. What do you want to do? " % redurl + msg += "\n[1] Follow the redirection (default)" + msg += "\n[2] Stay on the original page" + msg += "\n[3] Ignore" + choice = readInput(msg, default="1") + + kb.redirectChoice = choice + + def _process_http_redirect(self, result, headers, code, content, msg): content = decodePage(content, headers.get(HTTPHEADER.CONTENT_ENCODING), headers.get(HTTPHEADER.CONTENT_TYPE)) threadData = getCurrentThreadData() @@ -59,25 +68,26 @@ class SmartRedirectHandler(urllib2.HTTPRedirectHandler): logger.log(7, responseMsg) - if result: - if self._get_header_redirect(headers): - result.redurl = self._get_header_redirect(headers) + if self._get_header_redirect(headers): + result.redurl = self._get_header_redirect(headers) - if hasattr(result, 'redurl'): - if not urlparse.urlsplit(result.redurl).netloc: - result.redurl = urlparse.urljoin(conf.url, result.redurl) + if not urlparse.urlsplit(result.redurl).netloc: + result.redurl = urlparse.urljoin(conf.url, result.redurl) - if "set-cookie" in headers: - result.setcookie = headers["set-cookie"].split("; path")[0] + if "set-cookie" in headers: + kb.redirectSetCookie = headers["set-cookie"].split("; path")[0] - result.redcode = code + result.redcode = code return result def http_error_301(self, req, fp, code, msg, headers): - self.infinite_loop_check(req) + content = None, None + redurl = self._get_header_redirect(headers) + + self._infinite_loop_check(req) + self._ask_redirect_choice(code, redurl) - content = None try: content = fp.read() except Exception, msg: @@ -85,16 +95,23 @@ class SmartRedirectHandler(urllib2.HTTPRedirectHandler): dbgMsg += "redirect response content (%s)" % msg logger.debug(dbgMsg) - if self._get_header_redirect(headers): - req.headers[HTTPHEADER.HOST] = getHostHeader(self._get_header_redirect(headers)) + if redurl: + req.headers[HTTPHEADER.HOST] = getHostHeader(redurl) - result = urllib2.HTTPRedirectHandler.http_error_301(self, req, fp, code, msg, headers) - return self.common_http_redirect(result, headers, code, content, msg) + if kb.redirectChoice == REDIRECTION.FOLLOW: + result = urllib2.HTTPRedirectHandler.http_error_301(self, req, fp, code, msg, headers) + else: + result = fp + + return self._process_http_redirect(result, headers, code, content, msg) def http_error_302(self, req, fp, code, msg, headers): - self.infinite_loop_check(req) + content = None, None + redurl = self._get_header_redirect(headers) + + self._infinite_loop_check(req) + self._ask_redirect_choice(code, redurl) - content = None try: content = fp.read() except Exception, msg: @@ -102,14 +119,18 @@ class SmartRedirectHandler(urllib2.HTTPRedirectHandler): dbgMsg += "redirect response content (%s)" % msg logger.debug(dbgMsg) - if self._get_header_redirect(headers): - req.headers[HTTPHEADER.HOST] = getHostHeader(self._get_header_redirect(headers)) + if redurl: + req.headers[HTTPHEADER.HOST] = getHostHeader(redurl) - result = urllib2.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers) - return self.common_http_redirect(result, headers, code, content, msg) + if kb.redirectChoice == REDIRECTION.FOLLOW: + result = urllib2.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers) + else: + result = fp - def infinite_loop_check(self, req): - if hasattr(req, 'redirect_dict') and (req.redirect_dict.get(req.get_full_url(), 0) >= self.max_repeats or len(req.redirect_dict) >= self.max_redirections): + return self._process_http_redirect(result, headers, code, content, msg) + + def _infinite_loop_check(self, req): + if hasattr(req, 'redirect_dict') and (req.redirect_dict.get(req.get_full_url(), 0) >= MAX_SINGLE_URL_REDIRECTIONS or len(req.redirect_dict) >= MAX_TOTAL_REDIRECTIONS): errMsg = "infinite redirect loop detected (%s). " % ", ".join(item for item in req.redirect_dict.keys()) errMsg += "please check all provided parameters and/or provide missing ones." raise sqlmapConnectionException, errMsg