diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 9cdc3534d..eacfb49ed 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -470,7 +470,21 @@ def start(): paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place + # Creating csrf token pattern + csrfTokenPattern = r"" + strings = conf.csrfToken.split("*") + for index, string in enumerate(strings): + csrfTokenPattern += re.escape(string) + if index < len(strings) - 1: + csrfTokenPattern += ".*" + for parameter, value in paramDict.items(): + # Csrf parameter should never be injected + if (re.match(csrfTokenPattern, parameter)): + infoMsg = "skipping csrf parameter '%s'" % parameter + logger.info(infoMsg) + continue + if not proceed: break diff --git a/lib/core/option.py b/lib/core/option.py index 8081e3462..587d56c70 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2341,6 +2341,7 @@ def _basicOptionValidation(): errMsg = "option '--safe-req' is incompatible with option '--safe-url' and option '--safe-post'" raise SqlmapSyntaxException(errMsg) + conf.originalCsrfToken = conf.csrfToken if conf.csrfUrl and not conf.csrfToken: errMsg = "option '--csrf-url' requires usage of option '--csrf-token'" raise SqlmapSyntaxException(errMsg) diff --git a/lib/core/settings.py b/lib/core/settings.py index e6136207b..3ec3feaf4 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from lib.core.enums import DBMS_DIRECTORY_NAME from lib.core.enums import OS # sqlmap version (...) -VERSION = "1.2.12.0" +VERSION = "1.2.11.19" 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/core/target.py b/lib/core/target.py index a89c0c891..8b3707f3f 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -393,7 +393,20 @@ def _setRequestParams(): raise SqlmapGenericException(errMsg) if conf.csrfToken: - if not any(conf.csrfToken in _ for _ in (conf.paramDict.get(PLACE.GET, {}), conf.paramDict.get(PLACE.POST, {}))) and not re.search(r"\b%s\b" % re.escape(conf.csrfToken), conf.data or "") and conf.csrfToken not in set(_[0].lower() for _ in conf.httpHeaders) and conf.csrfToken not in conf.paramDict.get(PLACE.COOKIE, {}): + csrfTokenPattern = "" + strings = conf.csrfToken.split("*") + for index, string in enumerate(strings): + csrfTokenPattern += re.escape(string) + if index < len(strings) - 1: + csrfTokenPattern += ".*" + + def csrfTokenAppearsInGetOrPost(): + for params in (conf.paramDict.get(PLACE.GET, {}), conf.paramDict.get(PLACE.POST, {})): + for paramKey in params: + if re.search(r"\b%s\b" % csrfTokenPattern, paramKey): + return True + + if not csrfTokenAppearsInGetOrPost() and not re.search(r"\b%s\b" % re.escape(conf.csrfToken), conf.data or "") and conf.csrfToken not in set(_[0].lower() for _ in conf.httpHeaders) and conf.csrfToken not in conf.paramDict.get(PLACE.COOKIE, {}): errMsg = "anti-CSRF token parameter '%s' not " % conf.csrfToken errMsg += "found in provided GET, POST, Cookie or header values" raise SqlmapGenericException(errMsg) diff --git a/lib/request/connect.py b/lib/request/connect.py index 8d46dbc7f..a478f502d 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -61,6 +61,7 @@ from lib.core.common import unicodeencode from lib.core.common import unsafeVariableNaming from lib.core.common import urldecode from lib.core.common import urlencode +from lib.core.common import paramToDict from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger @@ -228,7 +229,6 @@ class Connect(object): This method connects to the target URL or proxy and returns the target URL page content """ - start = time.time() if isinstance(conf.delay, (int, float)) and conf.delay > 0: @@ -771,6 +771,11 @@ class Connect(object): if not multipart: logger.log(CUSTOM_LOGGING.TRAFFIC_IN, responseMsg) + #if "Invalid csrf token." in page: + # print "INVALID CSRF TOKEN" + #else: + # print "Valid CSRF Token" + return page, responseHeaders, code @staticmethod @@ -781,7 +786,7 @@ class Connect(object): and returns its page ratio (0 <= ratio <= 1) or a boolean value representing False/True match in case of !getRatioValue """ - + #print "queryPage()" if conf.direct: return direct(value, content) @@ -970,7 +975,99 @@ class Connect(object): return retVal page, headers, code = Connect.getPage(url=conf.csrfUrl or conf.url, data=conf.data if conf.csrfUrl == conf.url else None, method=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)) - token = extractRegexResult(r"(?i)]+\bname=[\"']?%s\b[^>]*\bvalue=[\"']?(?P[^>'\"]*)" % re.escape(conf.csrfToken), page or "") + + # Adjustments for csrf tokens with the star wildcard + conf.csrfToken = conf.originalCsrfToken + if "*" in conf.csrfToken: + csrfTokenPattern = r"" + strings = conf.csrfToken.split("*") + for index, string in enumerate(strings): + csrfTokenPattern += re.escape(string) + if index < len(strings) - 1: + csrfTokenPattern += ".*" + + csrfTokenKey = extractRegexResult( + r"(?i)]+\bname=[\"']?(?P%s)\b[^>]*\bvalue=[\"']?[^>'\"]*" % csrfTokenPattern, + page or "")[:-2] + + if not csrfTokenKey: + if get: + for key, value in urlparse.parse_qs(conf.parameters[PLACE.GET]).items(): + if re.search(r"\b%s\b" % csrfTokenPattern, key): + csrfTokenKey = key + break + elif post: + for key, value in urlparse.parse_qs(conf.parameters[PLACE.POST]).items(): + if re.search(r"\b%s\b" % csrfTokenPattern, key): + csrfTokenKey = key + break + + if csrfTokenKey: + conf.csrfToken = csrfTokenKey + + token = extractRegexResult(r"(?i)]+\bname=[\"']?%s\b[^>]*\bvalue=[\"']?(?P[^>'\"]*)" % re.escape(conf.csrfToken), page or "") + + # A fix for urlencoder to give %20 + # See: https://bugs.python.org/issue13866 + urllib.quote_plus = urllib.quote + + # Overwriting parameters for new csrf token key/ value... + if PLACE.GET in conf.parameters: + getParamsDict = dict(urlparse.parse_qsl(conf.parameters[PLACE.GET])) + for key, value in getParamsDict.iteritems(): + if re.search(r"\b%s\b" % csrfTokenPattern, key): + getParamsDict[conf.csrfToken] = token + if key != conf.csrfToken: + del getParamsDict[key] + break + conf.parameters[PLACE.GET] = urllib.urlencode(getParamsDict) + + for key, value in conf.paramDict[PLACE.GET].items(): + if re.search(r"\b%s\b" % csrfTokenPattern, key): + conf.paramDict[PLACE.GET][conf.csrfToken] = token + if key != conf.csrfToken: + del conf.paramDict[PLACE.GET][key] + break + + getDict = dict(urlparse.parse_qsl(get)) + for key, value in getDict.iteritems(): + if re.search(r"\b%s\b" % csrfTokenPattern, key): + getDict[conf.csrfToken] = token + if key != conf.csrfToken: + del getDict[key] + break + get = urllib.urlencode(getDict) + + if PLACE.POST in conf.parameters: + postParamsDict = dict(urlparse.parse_qsl(conf.parameters[PLACE.POST])) + for key, value in postParamsDict.iteritems(): + if re.search(r"\b%s\b" % csrfTokenPattern, key): + postParamsDict[conf.csrfToken] = token + if key != conf.csrfToken: + del postParamsDict[key] + break + conf.parameters[PLACE.POST] = urllib.urlencode(postParamsDict) + + for key, value in conf.paramDict[PLACE.POST].items(): + if re.search(r"\b%s\b" % csrfTokenPattern, key): + conf.paramDict[PLACE.POST][conf.csrfToken] = token + if key != conf.csrfToken: + del conf.paramDict[PLACE.POST][key] + break + + postDict = dict(urlparse.parse_qsl(post)) + for key, value in postDict.iteritems(): + if re.search(r"\b%s\b" % csrfTokenPattern, key): + postDict[conf.csrfToken] = token + if key != conf.csrfToken: + del postDict[key] + break + post = urllib.urlencode(postDict) + else: + token = extractRegexResult( + r"(?i)]+\bname=[\"']?%s\b[^>]*\bvalue=[\"']?(?P[^>'\"]*)" % re.escape(conf.csrfToken), + page or "") + if not token: token = extractRegexResult(r"(?i)]+\bvalue=[\"']?(?P[^>'\"]*)[\"']?[^>]*\bname=[\"']?%s\b" % re.escape(conf.csrfToken), page or "")