diff --git a/lib/core/defaults.py b/lib/core/defaults.py index 0dcdd076c..81558bd86 100644 --- a/lib/core/defaults.py +++ b/lib/core/defaults.py @@ -15,6 +15,7 @@ _defaults = { "delay": 0, "timeout": 30, "retries": 3, + "csrfRetries": 0, "saFreq": 0, "threads": 1, "level": 1, diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index d546469c5..434754d93 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -61,6 +61,7 @@ optDict = { "csrfToken": "string", "csrfUrl": "string", "csrfMethod": "string", + "csrfRetries": "integer", "forceSSL": "boolean", "chunked": "boolean", "hpp": "boolean", diff --git a/lib/core/settings.py b/lib/core/settings.py index 5a856ee16..6538697eb 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -18,7 +18,7 @@ from lib.core.enums import OS from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.4.6.4" +VERSION = "1.4.6.5" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index b68936082..263c2738e 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -267,6 +267,9 @@ def cmdLineParser(argv=None): request.add_argument("--csrf-method", dest="csrfMethod", help="HTTP method to use during anti-CSRF token page visit") + request.add_argument("--csrf-retries", dest="csrfRetries", type=int, + help="Retries for anti-CSRF token retrieval (default %d)" % defaults.csrfRetries) + request.add_argument("--force-ssl", dest="forceSSL", action="store_true", help="Force usage of SSL/HTTPS") diff --git a/lib/request/connect.py b/lib/request/connect.py index be42b7e03..d61cab0bf 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -1045,6 +1045,8 @@ class Connect(object): auxHeaders[value.split(',')[0]] = value.split(',', 1)[-1] if conf.csrfToken: + token = AttribDict() + def _adjustParameter(paramString, parameter, newValue): retVal = paramString @@ -1061,56 +1063,64 @@ class Connect(object): return retVal - token = AttribDict() - page, headers, code = Connect.getPage(url=conf.csrfUrl or conf.url, data=conf.data if conf.csrfUrl == conf.url else None, method=conf.csrfMethod or (conf.method if conf.csrfUrl == conf.url else None), cookie=conf.parameters.get(PLACE.COOKIE), direct=True, silent=True, ua=conf.parameters.get(PLACE.USER_AGENT), referer=conf.parameters.get(PLACE.REFERER), host=conf.parameters.get(PLACE.HOST)) - page = urldecode(page) # for anti-CSRF tokens with special characters in their name (e.g. 'foo:bar=...') + for attempt in xrange(conf.csrfRetries + 1): + if token: + break - match = re.search(r"(?i)]+\bname=[\"']?(?P%s)\b[^>]*\bvalue=[\"']?(?P[^>'\"]*)" % conf.csrfToken, page or "", re.I) + if attempt > 0: + warnMsg = "unable to find anti-CSRF token '%s' at '%s'" % (conf.csrfToken._original, conf.csrfUrl or conf.url) + warnMsg += ". sqlmap is going to retry the request" + logger.warn(warnMsg) - if not match: - match = re.search(r"(?i)]+\bvalue=[\"']?(?P[^>'\"]*)[\"']?[^>]*\bname=[\"']?(?P%s)\b" % conf.csrfToken, page or "", re.I) + page, headers, code = Connect.getPage(url=conf.csrfUrl or conf.url, data=conf.data if conf.csrfUrl == conf.url else None, method=conf.csrfMethod or (conf.method if conf.csrfUrl == conf.url else None), cookie=conf.parameters.get(PLACE.COOKIE), direct=True, silent=True, ua=conf.parameters.get(PLACE.USER_AGENT), referer=conf.parameters.get(PLACE.REFERER), host=conf.parameters.get(PLACE.HOST)) + page = urldecode(page) # for anti-CSRF tokens with special characters in their name (e.g. 'foo:bar=...') + + match = re.search(r"(?i)]+\bname=[\"']?(?P%s)\b[^>]*\bvalue=[\"']?(?P[^>'\"]*)" % conf.csrfToken, page or "", re.I) if not match: - match = re.search(r"(?P%s)[\"']:[\"'](?P[^\"']+)" % conf.csrfToken, page or "", re.I) + match = re.search(r"(?i)]+\bvalue=[\"']?(?P[^>'\"]*)[\"']?[^>]*\bname=[\"']?(?P%s)\b" % conf.csrfToken, page or "", re.I) if not match: - match = re.search(r"\b(?P%s)\s*[:=]\s*(?P\w+)" % conf.csrfToken, str(headers), re.I) + match = re.search(r"(?P%s)[\"']:[\"'](?P[^\"']+)" % conf.csrfToken, page or "", re.I) if not match: - match = re.search(r"\b(?P%s)\s*=\s*['\"]?(?P[^;'\"]+)" % conf.csrfToken, page or "", re.I) + match = re.search(r"\b(?P%s)\s*[:=]\s*(?P\w+)" % conf.csrfToken, str(headers), re.I) - if match: - token.name, token.value = match.group("name"), match.group("value") + if not match: + match = re.search(r"\b(?P%s)\s*=\s*['\"]?(?P[^;'\"]+)" % conf.csrfToken, page or "", re.I) - match = re.search(r"String\.fromCharCode\(([\d+, ]+)\)", token.value) if match: - token.value = "".join(_unichr(int(_)) for _ in match.group(1).replace(' ', "").split(',')) + token.name, token.value = match.group("name"), match.group("value") - if not token: - if conf.csrfUrl and conf.csrfToken and conf.csrfUrl != conf.url and code == _http_client.OK: - if headers and "text/plain" in headers.get(HTTP_HEADER.CONTENT_TYPE, ""): - token.name = conf.csrfToken - token.value = page - - if not token and conf.cj and any(re.search(conf.csrfToken, _.name, re.I) for _ in conf.cj): - for _ in conf.cj: - if re.search(conf.csrfToken, _.name, re.I): - token.name, token.value = _.name, _.value - if not any(re.search(conf.csrfToken, ' '.join(_), re.I) for _ in (conf.paramDict.get(PLACE.GET, {}), conf.paramDict.get(PLACE.POST, {}))): - if post: - post = "%s%s%s=%s" % (post, conf.paramDel or DEFAULT_GET_POST_DELIMITER, token.name, token.value) - elif get: - get = "%s%s%s=%s" % (get, conf.paramDel or DEFAULT_GET_POST_DELIMITER, token.name, token.value) - else: - get = "%s=%s" % (token.name, token.value) - break + match = re.search(r"String\.fromCharCode\(([\d+, ]+)\)", token.value) + if match: + token.value = "".join(_unichr(int(_)) for _ in match.group(1).replace(' ', "").split(',')) if not token: - errMsg = "anti-CSRF token '%s' can't be found at '%s'" % (conf.csrfToken._original, conf.csrfUrl or conf.url) - if not conf.csrfUrl: - errMsg += ". You can try to rerun by providing " - errMsg += "a valid value for option '--csrf-url'" - raise SqlmapTokenException(errMsg) + if conf.csrfUrl and conf.csrfToken and conf.csrfUrl != conf.url and code == _http_client.OK: + if headers and "text/plain" in headers.get(HTTP_HEADER.CONTENT_TYPE, ""): + token.name = conf.csrfToken + token.value = page + + if not token and conf.cj and any(re.search(conf.csrfToken, _.name, re.I) for _ in conf.cj): + for _ in conf.cj: + if re.search(conf.csrfToken, _.name, re.I): + token.name, token.value = _.name, _.value + if not any(re.search(conf.csrfToken, ' '.join(_), re.I) for _ in (conf.paramDict.get(PLACE.GET, {}), conf.paramDict.get(PLACE.POST, {}))): + if post: + post = "%s%s%s=%s" % (post, conf.paramDel or DEFAULT_GET_POST_DELIMITER, token.name, token.value) + elif get: + get = "%s%s%s=%s" % (get, conf.paramDel or DEFAULT_GET_POST_DELIMITER, token.name, token.value) + else: + get = "%s=%s" % (token.name, token.value) + break + + if not token: + errMsg = "anti-CSRF token '%s' can't be found at '%s'" % (conf.csrfToken._original, conf.csrfUrl or conf.url) + if not conf.csrfUrl: + errMsg += ". You can try to rerun by providing " + errMsg += "a valid value for option '--csrf-url'" + raise SqlmapTokenException(errMsg) if token: token.value = token.value.strip("'\"") diff --git a/sqlmap.conf b/sqlmap.conf index cbef1f005..fa4389f1d 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -189,6 +189,9 @@ csrfUrl = # HTTP method to use during anti-CSRF token page visit. csrfMethod = +# Retries for anti-CSRF token retrieval. +csrfRetries = + # Force usage of SSL/HTTPS # Valid: True or False forceSSL = False