From 87237c82d3c25dec8b6b05595c14a926cbb0bf34 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 19 Mar 2019 14:07:39 +0100 Subject: [PATCH] Revamp of #3536 --- lib/core/common.py | 73 ++++++++----------- lib/core/option.py | 25 +++---- lib/core/optiondict.py | 1 + lib/core/settings.py | 6 +- lib/parse/cmdline.py | 7 +- .../{httphandler.py => chunkedhandler.py} | 13 ++-- lib/request/connect.py | 9 ++- sqlmap.conf | 4 + txt/checksum.md5 | 13 ++-- 9 files changed, 72 insertions(+), 79 deletions(-) rename lib/request/{httphandler.py => chunkedhandler.py} (85%) diff --git a/lib/core/common.py b/lib/core/common.py index 863d297e1..02e43bb8f 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -98,7 +98,7 @@ from lib.core.exception import SqlmapUserQuitException from lib.core.exception import SqlmapValueException from lib.core.log import LOGGER_HANDLER from lib.core.optiondict import optDict -from lib.core.settings import BANNER, CHUNKED_KEYWORDS +from lib.core.settings import BANNER from lib.core.settings import BOLD_PATTERNS from lib.core.settings import BOUNDED_INJECTION_MARKER from lib.core.settings import BRUTE_DOC_ROOT_PREFIXES @@ -126,6 +126,7 @@ from lib.core.settings import GITHUB_REPORT_OAUTH_TOKEN from lib.core.settings import GOOGLE_ANALYTICS_COOKIE_PREFIX from lib.core.settings import HASHDB_MILESTONE_VALUE from lib.core.settings import HOST_ALIASES +from lib.core.settings import HTTP_CHUNKED_SPLIT_KEYWORDS from lib.core.settings import IGNORE_SAVE_OPTIONS from lib.core.settings import INFERENCE_UNKNOWN_CHAR from lib.core.settings import INVALID_UNICODE_CHAR_FORMAT @@ -4896,49 +4897,35 @@ def firstNotNone(*args): return retVal -def generateChunkDdata(data): +def chunkSplitPostData(data): """ - Convert post data to chunked format data. If the keyword is in a block, the keyword will be cut. - - >>> generateChunkDdata('select 1,2,3,4 from admin') - 4;AZdYz - sele - 2;fJS4D - ct - 5;qbCOT - 1,2, - 7;KItpi - 3,4 fro - 2;pFu1R - m - 5;uRoYZ - admin - 0 - - + Convert POST data to chunked transfer-encoded data (Note: splitting done by SQL keywords) """ - dl = len(data) - ret = "" - keywords = CHUNKED_KEYWORDS + + length = len(data) + retVal = "" index = 0 - while index < dl: - chunk_size = random.randint(1, 9) - if index + chunk_size >= dl: - chunk_size = dl - index - salt = ''.join(random.sample(string.ascii_letters + string.digits, 5)) - while 1: - tmp_chunk = data[index:index + chunk_size] - tmp_bool = True - for k in keywords: - if k in tmp_chunk: - chunk_size -= 1 - tmp_bool = False - break - if tmp_bool: - break - index += chunk_size - ret += "%s;%s\r\n" % (hex(chunk_size)[2:], salt) - ret += "%s\r\n" % tmp_chunk - ret += "0\r\n\r\n" - return ret + while index < length: + chunkSize = randomInt(1) + + if index + chunkSize >= length: + chunkSize = length - index + + salt = randomStr(5, alphabet=string.ascii_letters + string.digits) + + while chunkSize: + candidate = data[index:index + chunkSize] + + if re.search(r"\b%s\b" % '|'.join(HTTP_CHUNKED_SPLIT_KEYWORDS), candidate, re.I): + chunkSize -= 1 + else: + break + + index += chunkSize + retVal += "%x;%s\r\n" % (chunkSize, salt) + retVal += "%s\r\n" % candidate + + retVal += "0\r\n\r\n" + + return retVal diff --git a/lib/core/option.py b/lib/core/option.py index 2b241a966..7be6dde33 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -140,7 +140,7 @@ from lib.request.basic import checkCharEncoding from lib.request.connect import Connect as Request from lib.request.dns import DNSServer from lib.request.basicauthhandler import SmartHTTPBasicAuthHandler -from lib.request.httphandler import HTTPHandler +from lib.request.chunkedhandler import ChunkedHandler from lib.request.httpshandler import HTTPSHandler from lib.request.pkihandler import HTTPSPKIAuthHandler from lib.request.rangehandler import HTTPRangeHandler @@ -158,7 +158,7 @@ from thirdparty.socks import socks from xml.etree.ElementTree import ElementTree authHandler = urllib2.BaseHandler() -httpHandler = HTTPHandler() +chunkedHandler = ChunkedHandler() httpsHandler = HTTPSHandler() keepAliveHandler = keepalive.HTTPHandler() proxyHandler = urllib2.ProxyHandler() @@ -1109,7 +1109,7 @@ def _setHTTPHandlers(): debugMsg = "creating HTTP requests opener object" logger.debug(debugMsg) - handlers = filter(None, [multipartPostHandler, proxyHandler if proxyHandler.proxies else None, authHandler, redirectHandler, rangeHandler, httpHandler, httpsHandler]) + handlers = filter(None, [multipartPostHandler, proxyHandler if proxyHandler.proxies else None, authHandler, redirectHandler, rangeHandler, chunkedHandler if conf.chunked else None, httpsHandler]) if not conf.dropSetCookie: if not conf.loadCookies: @@ -2314,6 +2314,10 @@ def _setTorSocksProxySettings(): socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5 if conf.torType == PROXY_TYPE.SOCKS5 else socks.PROXY_TYPE_SOCKS4, LOCALHOST, port) socks.wrapmodule(urllib2) +def _setHttpChunked(): + if conf.chunked and conf.data: + httplib.HTTPConnection._set_content_length = lambda self, a, b: None + def _checkWebSocket(): if conf.url and (conf.url.startswith("ws:/") or conf.url.startswith("wss:/")): try: @@ -2401,6 +2405,10 @@ def _basicOptionValidation(): errMsg = "switch '--dump' is incompatible with switch '--search'" raise SqlmapSyntaxException(errMsg) + if conf.chunked and not any((conf.data, conf.requestFile)): + errMsg = "switch '--chunked' requires usage of option '--data' or '-r'" + raise SqlmapSyntaxException(errMsg) + if conf.api and not conf.configFile: errMsg = "switch '--api' requires usage of option '-c'" raise SqlmapSyntaxException(errMsg) @@ -2605,15 +2613,6 @@ def initOptions(inputOptions=AttribDict(), overrideOptions=False): _setKnowledgeBaseAttributes() _mergeOptions(inputOptions, overrideOptions) -def _setHttpChunked(): - conf.chunk = conf.chunk and conf.data - if conf.chunk: - def hook(self, a, b): - pass - - httplib.HTTPConnection._set_content_length = hook - - def init(): """ Set attributes into both configuration and knowledge base singletons @@ -2639,11 +2638,11 @@ def init(): _listTamperingFunctions() _setTamperingFunctions() _setPreprocessFunctions() - _setHttpChunked() _setWafFunctions() _setTrafficOutputFP() _setupHTTPCollector() _resolveCrossReferences() + _setHttpChunked() _checkWebSocket() parseTargetDirect() diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 17f88f985..5232f674a 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -61,6 +61,7 @@ optDict = { "csrfToken": "string", "csrfUrl": "string", "forceSSL": "boolean", + "chunked": "boolean", "hpp": "boolean", "evalCode": "string", }, diff --git a/lib/core/settings.py b/lib/core/settings.py index 192bf3f59..c669085f9 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.3.3.31" +VERSION = "1.3.3.32" 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) @@ -794,8 +794,8 @@ KB_CHARS_BOUNDARY_CHAR = 'q' # Letters of lower frequency used in kb.chars KB_CHARS_LOW_FREQUENCY_ALPHABET = "zqxjkvbp" -# Keywords that need to be cut in the chunked -CHUNKED_KEYWORDS = ['select', 'update', 'insert', 'from', 'load_file', 'sysdatabases', 'msysaccessobjects', 'msysqueries', 'sysmodules', 'information_schema', 'union'] +# SQL keywords used for splitting in HTTP Chunked encoding (switch --chunk) +HTTP_CHUNKED_SPLIT_KEYWORDS = ("SELECT", "UPDATE", "INSERT", "FROM", "LOAD_FILE", "UNION", "information_schema", "sysdatabases", "msysaccessobjects", "msysqueries", "sysmodules") # CSS style used in HTML dump format HTML_DUMP_CSS_STYLE = """