diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 68d5eb8e8..fe9c448d8 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -457,6 +457,12 @@ def start(): infoMsg = "skipping %s parameter '%s'" % (place, parameter) logger.info(infoMsg) + elif parameter == conf.csrfToken: + testSqlInj = False + + infoMsg = "skipping CSRF protection token parameter '%s'" % parameter + logger.info(infoMsg) + # Ignore session-like parameters for --level < 4 elif conf.level < 4 and (parameter.upper() in IGNORE_PARAMETERS or parameter.upper().startswith(GOOGLE_ANALYTICS_COOKIE_PREFIX)): testSqlInj = False diff --git a/lib/core/exception.py b/lib/core/exception.py index 8fe6a7756..562d75ecc 100644 --- a/lib/core/exception.py +++ b/lib/core/exception.py @@ -53,6 +53,9 @@ class SqlmapSyntaxException(SqlmapBaseException): class SqlmapThreadException(SqlmapBaseException): pass +class SqlmapTokenException(SqlmapBaseException): + pass + class SqlmapUndefinedMethod(SqlmapBaseException): pass diff --git a/lib/core/target.py b/lib/core/target.py index 1c91a4d59..343cb32a2 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -344,6 +344,12 @@ def _setRequestParams(): errMsg += "within the given request data" raise SqlmapGenericException(errMsg) + if conf.csrfToken: + if not any(conf.csrfToken in _ for _ in (conf.paramDict.get(PLACE.GET, {}), conf.paramDict.get(PLACE.POST, {}))): + errMsg = "CSRF protection token parameter '%s' not " % conf.csrfToken + errMsg += "found in provided GET and/or POST values" + raise SqlmapGenericException(errMsg) + def _setHashDB(): """ Check and set the HashDB SQLite file for query resume functionality. diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 0084e731c..85aa91d63 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -190,6 +190,12 @@ def cmdLineParser(): action="store_true", help="Skip URL encoding of payload data") + request.add_option("--csrf-token", dest="csrfToken", + help="Parameter used as a CSRF protection token") + + request.add_option("--csrf-url", dest="csrfUrl", + help="URL address to visit to extract CSRF protection token") + request.add_option("--force-ssl", dest="forceSSL", action="store_true", help="Force usage of SSL/HTTPS") diff --git a/lib/request/basic.py b/lib/request/basic.py index 75bfbada2..ac887e08c 100755 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -106,7 +106,7 @@ def forgeHeaders(items=None): elif not kb.testMode: headers[HTTP_HEADER.COOKIE] += "%s %s=%s" % (conf.cookieDel or DEFAULT_COOKIE_DELIMITER, cookie.name, getUnicode(cookie.value)) - if kb.testMode: + if kb.testMode and not conf.csrfToken: resetCookieJar(conf.cj) return headers diff --git a/lib/request/connect.py b/lib/request/connect.py index 99e88cacc..157e0cce2 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -63,6 +63,7 @@ from lib.core.enums import WEB_API from lib.core.exception import SqlmapCompressionException from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapSyntaxException +from lib.core.exception import SqlmapTokenException from lib.core.exception import SqlmapValueException from lib.core.settings import ASTERISK_MARKER from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR @@ -748,6 +749,34 @@ class Connect(object): if value and place == PLACE.CUSTOM_HEADER: auxHeaders[value.split(',')[0]] = value.split(',', 1)[1] + if conf.csrfToken: + def _adjustParameter(paramString, parameter, newValue): + retVal = paramString + match = re.search("%s=(?P[^&]*)" % parameter, paramString) + if match: + origValue = match.group("value") + retVal = re.sub("%s=[^&]*" % parameter, "%s=%s" % (parameter, newValue), paramString) + return retVal + + page, _, _ = Connect.getPage(url=conf.csrfUrl or conf.url, 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)) + match = re.search(r"]+name=[\"']?%s[\"']?\s[^>]*value=(\"([^\"]+)|'([^']+)|([^ >]+))" % conf.csrfToken, page) + token = (match.group(2) or match.group(3) or match.group(4)) if match else None + + if not token: + errMsg = "CSRF token value '%s' can't be found at '%s'" % (conf.csrfToken, 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: + for item in (PLACE.GET, PLACE.POST): + if item in conf.parameters: + if item == PLACE.GET and get: + get = _adjustParameter(get, conf.csrfToken, token) + elif item == PLACE.POST and post: + post = _adjustParameter(post, conf.csrfToken, token) + if conf.rParam: def _randomizeParameter(paramString, randomParameter): retVal = paramString