From 2cb1b054bb0909a974d36acc38591af8b476d88e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 16 Oct 2012 12:32:58 +0200 Subject: [PATCH] Implementation for an Issue #79 --- lib/core/common.py | 26 +++++++++++++++++++++++++- lib/core/dicts.py | 1 + lib/core/enums.py | 1 + lib/core/settings.py | 5 ++++- lib/core/target.py | 17 ++++++++++++++--- lib/request/connect.py | 10 ++++++++++ 6 files changed, 55 insertions(+), 5 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index c00272a7c..0c5076ad8 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1962,7 +1962,31 @@ def extractErrorMessage(page): return retVal -def urldecode(value, encoding=None, unsafe="%&=", convall=False): +def findMultipartPostBoundary(post): + """ + Finds value for a boundary parameter in given multipart POST body + """ + + retVal = None + + done = set() + candidates = [] + + for match in re.finditer(r"(?m)^--(.+?)(--)?$", post or ""): + _ = match.group(1) + if _ in done: + continue + else: + candidates.append((post.count(_), _)) + done.add(_) + + if candidates: + candidates.sort(key=lambda _: _[0], reverse=True) + retVal = candidates[0][1] + + return retVal + +def urldecode(value, encoding=None, unsafe="%%&=%s" % CUSTOM_INJECTION_MARK_CHAR, convall=False): result = None if value: diff --git a/lib/core/dicts.py b/lib/core/dicts.py index 66d7e5627..49eaa34d2 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -197,6 +197,7 @@ SQL_STATEMENTS = { POST_HINT_CONTENT_TYPES = { POST_HINT.JSON: "application/json", + POST_HINT.MULTIPART: "multipart/form-data", POST_HINT.SOAP: "application/soap+xml", POST_HINT.XML: "application/xml" } diff --git a/lib/core/enums.py b/lib/core/enums.py index 157628fd6..36e1fcf19 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -68,6 +68,7 @@ class PLACE: class POST_HINT: SOAP = "SOAP" JSON = "JSON" + MULTIPART = "MULTIPART" XML = "XML (generic)" class HTTPMETHOD: diff --git a/lib/core/settings.py b/lib/core/settings.py index fe884cf79..e11ca9f60 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -471,8 +471,11 @@ LIMITED_ROWS_TEST_NUMBER = 15 # Regular expression for SOAP-like POST data SOAP_RECOGNITION_REGEX = r"(?s)\A(<\?xml[^>]+>)?\s*<([^> ]+)( [^>]+)?>.+\s*\Z" -# Regular expressing used for detecting JSON-like POST data +# Regular expression used for detecting JSON-like POST data JSON_RECOGNITION_REGEX = r'(?s)\A\s*\{.*"[^"]+"\s*:\s*("[^"]+"|\d+).*\}\s*\Z' +# Regular expression used for detecting multipart POST data +MULTIPART_RECOGNITION_REGEX = r"(?i)Content-Disposition:[^;]+;\s*name=" + # Default POST data content-type DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded" diff --git a/lib/core/target.py b/lib/core/target.py index f5c6c7dcb..f8f98d375 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -41,6 +41,7 @@ from lib.core.option import __setAuthCred from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR from lib.core.settings import HOST_ALIASES from lib.core.settings import JSON_RECOGNITION_REGEX +from lib.core.settings import MULTIPART_RECOGNITION_REGEX from lib.core.settings import REFERER_ALIASES from lib.core.settings import RESULTS_FILE_FORMAT from lib.core.settings import SOAP_RECOGNITION_REGEX @@ -95,7 +96,6 @@ def __setRequestParams(): elif test[0] not in ("n", "N"): conf.data = re.sub(r'("[^"]+"\s*:\s*"[^"]+)"', r'\g<1>%s"' % CUSTOM_INJECTION_MARK_CHAR, conf.data) conf.data = re.sub(r'("[^"]+"\s*:\s*)(-?\d[\d\.]*\b)', r'\g<0>%s' % CUSTOM_INJECTION_MARK_CHAR, conf.data) - kb.processUserMarks = True kb.postHint = POST_HINT.JSON elif re.search(SOAP_RECOGNITION_REGEX, conf.data): @@ -105,10 +105,19 @@ def __setRequestParams(): if test and test[0] in ("q", "Q"): raise sqlmapUserQuitException elif test[0] not in ("n", "N"): - conf.data = re.sub(r"(<([^>]+)( [^<]*)?>)([^<]+)(\g<4>*\g<5>", conf.data) - kb.processUserMarks = True + conf.data = re.sub(r"(<([^>]+)( [^<]*)?>)([^<]+)(\g<4>%s\g<5>" % CUSTOM_INJECTION_MARK_CHAR, conf.data) kb.postHint = POST_HINT.SOAP if "soap" in conf.data.lower() else POST_HINT.XML + elif re.search(MULTIPART_RECOGNITION_REGEX, conf.data): + message = "Multipart like data found in POST data. " + message += "Do you want to process it? [Y/n/q] " + test = readInput(message, default="Y") + if test and test[0] in ("q", "Q"): + raise sqlmapUserQuitException + elif test[0] not in ("n", "N"): + conf.data = re.sub(r"(?si)(Content-Disposition.+?)((\r)?\n--)", r"\g<1>%s\g<2>" % CUSTOM_INJECTION_MARK_CHAR, conf.data) + kb.postHint = POST_HINT.MULTIPART + else: place = PLACE.POST @@ -119,6 +128,8 @@ def __setRequestParams(): conf.paramDict[place] = paramDict testableParameters = True + kb.processUserMarks = True if kb.postHint else kb.processUserMarks + if re.search(URI_INJECTABLE_REGEX, conf.url, re.I) and not any(map(lambda place: place in conf.parameters, [PLACE.GET, PLACE.POST])): warnMsg = "you've provided target url without any GET " warnMsg += "parameters (e.g. www.site.com/article.php?id=1) " diff --git a/lib/request/connect.py b/lib/request/connect.py index a651f2dba..bbf3c2f4d 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -23,6 +23,7 @@ from lib.core.common import clearConsoleLine from lib.core.common import cpuThrottle from lib.core.common import evaluateCode from lib.core.common import extractRegexResult +from lib.core.common import findMultipartPostBoundary from lib.core.common import getCurrentThreadData from lib.core.common import getHostHeader from lib.core.common import getRequestHeader @@ -292,6 +293,15 @@ class Connect: if post and HTTPHEADER.CONTENT_TYPE not in headers: headers[HTTPHEADER.CONTENT_TYPE] = POST_HINT_CONTENT_TYPES.get(kb.postHint, DEFAULT_CONTENT_TYPE) + if headers[HTTPHEADER.CONTENT_TYPE] == POST_HINT_CONTENT_TYPES[POST_HINT.MULTIPART]: + warnMsg = "missing 'boundary parameter' in '%s' header. " % HTTPHEADER.CONTENT_TYPE + warnMsg += "Will try to reconstruct" + singleTimeWarnMessage(warnMsg) + + boundary = findMultipartPostBoundary(conf.data) + if boundary: + headers[HTTPHEADER.CONTENT_TYPE] = "%s; boundary=%s" % (headers[HTTPHEADER.CONTENT_TYPE], boundary) + if auxHeaders: for key, item in auxHeaders.items(): headers[key] = item