diff --git a/extra/cloak/cloak.py b/extra/cloak/cloak.py index a94f6756f..5e7d3c6c6 100755 --- a/extra/cloak/cloak.py +++ b/extra/cloak/cloak.py @@ -24,17 +24,19 @@ def hideAscii(data): return retVal -def cloak(inputFile): - f = open(inputFile, 'rb') - data = zlib.compress(f.read()) - f.close() +def cloak(inputFile=None, data=None): + if data is None: + with open(inputFile, "rb") as f: + data = f.read() - return hideAscii(data) + return hideAscii(zlib.compress(data)) -def decloak(inputFile): - f = open(inputFile, 'rb') +def decloak(inputFile=None, data=None): + if data is None: + with open(inputFile, "rb") as f: + data = f.read() try: - data = zlib.decompress(hideAscii(f.read())) + data = zlib.decompress(hideAscii(data)) except: print 'ERROR: the provided input file \'%s\' does not contain valid cloaked content' % inputFile sys.exit(1) diff --git a/extra/shellcodeexec/windows/shellcodeexec.x32.exe_ b/extra/shellcodeexec/windows/shellcodeexec.x32.exe_ index 4d699f123..c4204cce6 100644 Binary files a/extra/shellcodeexec/windows/shellcodeexec.x32.exe_ and b/extra/shellcodeexec/windows/shellcodeexec.x32.exe_ differ diff --git a/lib/core/common.py b/lib/core/common.py index 9aada65ed..556865764 100755 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -3556,7 +3556,7 @@ def findPageForms(content, url, raise_=False, addToTargets=False): for form in forms: try: for control in form.controls: - if hasattr(control, "items") and not control.disabled: + if hasattr(control, "items") and not any((control.disabled, control.readonly)): # if control has selectable items select first non-disabled for item in control.items: if not item.disabled: diff --git a/lib/core/settings.py b/lib/core/settings.py index 2457770d1..303c10cf4 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -75,6 +75,12 @@ GOOGLE_REGEX = r"url\?\w+=((?![^>]+webcache\.googleusercontent\.com)http[^>]+)&( # Regular expression used for extracting results from DuckDuckGo search DUCKDUCKGO_REGEX = r'"u":"([^"]+)' +# Regular expression used for extracting results from Disconnect Search +DISCONNECT_SEARCH_REGEX = r'

([^<]+)

' + +# Dummy user agent for search (if default one returns different results) +DUMMY_SEARCH_USER_AGENT = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:40.0) Gecko/20100101 Firefox/40.0" + # Regular expression used for extracting content from "textual" tags TEXT_TAG_REGEX = r"(?si)<(abbr|acronym|b|blockquote|br|center|cite|code|dt|em|font|h\d|i|li|p|pre|q|strong|sub|sup|td|th|title|tt|u)(?!\w).*?>(?P[^<]+)" @@ -437,6 +443,9 @@ BRUTE_COLUMN_EXISTS_TEMPLATE = "EXISTS(SELECT %s FROM %s)" # Payload used for checking of existence of IDS/WAF (dummier the better) IDS_WAF_CHECK_PAYLOAD = "AND 1=1 UNION ALL SELECT 1,2,3,table_name FROM information_schema.tables WHERE 2>1-- ../../../etc/passwd" +# Data inside shellcodeexec to be filled with random string +SHELLCODEEXEC_RANDOM_STRING_MARKER = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + # Vectors used for provoking specific WAF/IDS/IPS behavior(s) WAF_ATTACK_VECTORS = ( "", # NIL diff --git a/lib/core/target.py b/lib/core/target.py index 50158817a..cdfd538f0 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -92,8 +92,8 @@ def _setRequestParams(): # Perform checks on POST parameters if conf.method == HTTPMETHOD.POST and conf.data is None: - errMsg = "HTTP POST method depends on HTTP data value to be posted" - raise SqlmapSyntaxException(errMsg) + logger.warn("detected empty POST body") + conf.data = "" if conf.data is not None: conf.method = HTTPMETHOD.POST if not conf.method or conf.method == HTTPMETHOD.GET else conf.method @@ -223,11 +223,11 @@ def _setRequestParams(): message += "in the target URL itself? [Y/n/q] " test = readInput(message, default="Y") - if not test or test[0] not in ("n", "N"): + if test and test[0] in ("q", "Q"): + raise SqlmapUserQuitException + elif not test or test[0] not in ("n", "N"): conf.url = "%s%s" % (conf.url, CUSTOM_INJECTION_MARK_CHAR) kb.processUserMarks = True - elif test[0] in ("q", "Q"): - raise SqlmapUserQuitException for place, value in ((PLACE.URI, conf.url), (PLACE.CUSTOM_POST, conf.data), (PLACE.CUSTOM_HEADER, str(conf.httpHeaders))): _ = re.sub(PROBLEMATIC_CUSTOM_INJECTION_PATTERNS, "", value or "") if place == PLACE.CUSTOM_HEADER else value or "" diff --git a/lib/takeover/icmpsh.py b/lib/takeover/icmpsh.py index 35cfe9881..2e5d3253c 100644 --- a/lib/takeover/icmpsh.py +++ b/lib/takeover/icmpsh.py @@ -32,14 +32,26 @@ class ICMPsh: self._icmpslave = normalizePath(os.path.join(paths.SQLMAP_EXTRAS_PATH, "icmpsh", "icmpsh.exe_")) def _selectRhost(self): - message = "what is the back-end DBMS address? [%s] " % self.remoteIP - address = readInput(message, default=self.remoteIP) + address = None + message = "what is the back-end DBMS address? " + + if self.remoteIP: + message += "[Enter for '%s' (detected)] " % self.remoteIP + + while not address: + address = readInput(message, default=self.remoteIP) return address def _selectLhost(self): - message = "what is the local address? [%s] " % self.localIP - address = readInput(message, default=self.localIP) + address = None + message = "what is the local address? " + + if self.localIP: + message += "[Enter for '%s' (detected)] " % self.localIP + + while not address: + address = readInput(message, default=self.localIP) return address diff --git a/lib/takeover/metasploit.py b/lib/takeover/metasploit.py index 8717b6c73..10d3a3022 100644 --- a/lib/takeover/metasploit.py +++ b/lib/takeover/metasploit.py @@ -8,10 +8,13 @@ See the file 'doc/COPYING' for copying permission import os import re import sys +import tempfile import time from subprocess import PIPE +from extra.cloak.cloak import cloak +from extra.cloak.cloak import decloak from lib.core.common import dataToStdout from lib.core.common import Backend from lib.core.common import getLocalIP @@ -34,6 +37,7 @@ from lib.core.exception import SqlmapFilePathException from lib.core.exception import SqlmapGenericException from lib.core.settings import IS_WIN from lib.core.settings import METASPLOIT_SESSION_TIMEOUT +from lib.core.settings import SHELLCODEEXEC_RANDOM_STRING_MARKER from lib.core.settings import UNICODE_ENCODING from lib.core.subprocessng import blockingReadFromFD from lib.core.subprocessng import blockingWriteToFD @@ -288,7 +292,7 @@ class Metasploit: def _selectRhost(self): if self.connectionStr.startswith("bind"): - message = "what is the back-end DBMS address? [%s] " % self.remoteIP + message = "what is the back-end DBMS address? [Enter for '%s' (detected)] " % self.remoteIP address = readInput(message, default=self.remoteIP) if not address: @@ -304,7 +308,7 @@ class Metasploit: def _selectLhost(self): if self.connectionStr.startswith("reverse"): - message = "what is the local address? [%s] " % self.localIP + message = "what is the local address? [Enter for '%s' (detected)] " % self.localIP address = readInput(message, default=self.localIP) if not address: @@ -640,6 +644,14 @@ class Metasploit: if Backend.isOs(OS.WINDOWS): self.shellcodeexecLocal = os.path.join(self.shellcodeexecLocal, "windows", "shellcodeexec.x%s.exe_" % "32") + content = decloak(self.shellcodeexecLocal) + if SHELLCODEEXEC_RANDOM_STRING_MARKER in content: + content = content.replace(SHELLCODEEXEC_RANDOM_STRING_MARKER, randomStr(len(SHELLCODEEXEC_RANDOM_STRING_MARKER))) + _ = cloak(data=content) + handle, self.shellcodeexecLocal = tempfile.mkstemp(suffix="%s.exe_" % "32") + os.close(handle) + with open(self.shellcodeexecLocal, "w+b") as f: + f.write(_) else: self.shellcodeexecLocal = os.path.join(self.shellcodeexecLocal, "linux", "shellcodeexec.x%s_" % Backend.getArch()) diff --git a/lib/utils/google.py b/lib/utils/google.py index e0f4b08bd..800f366e8 100644 --- a/lib/utils/google.py +++ b/lib/utils/google.py @@ -21,8 +21,11 @@ from lib.core.enums import CUSTOM_LOGGING from lib.core.enums import HTTP_HEADER from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapGenericException -from lib.core.settings import GOOGLE_REGEX +from lib.core.exception import SqlmapUserQuitException +from lib.core.settings import DUMMY_SEARCH_USER_AGENT from lib.core.settings import DUCKDUCKGO_REGEX +from lib.core.settings import DISCONNECT_SEARCH_REGEX +from lib.core.settings import GOOGLE_REGEX from lib.core.settings import HTTP_ACCEPT_ENCODING_HEADER_VALUE from lib.core.settings import UNICODE_ENCODING from lib.request.basic import decodePage @@ -96,7 +99,7 @@ class Google(object): warnMsg += "to get error page information (%d)" % e.code logger.critical(warnMsg) return None - except (urllib2.URLError, socket.error, socket.timeout): + except (urllib2.URLError, httplib.error, socket.error, socket.timeout): errMsg = "unable to connect to Google" raise SqlmapConnectionException(errMsg) @@ -108,54 +111,67 @@ class Google(object): raise SqlmapGenericException(warnMsg) if not retVal: - message = "no usable links found. " - message += "do you want to (re)try with DuckDuckGo? [Y/n] " - output = readInput(message, default="Y") + message = "no usable links found. What do you want to do?" + message += "\n[1] (re)try with DuckDuckGo (default)" + message += "\n[2] (re)try with Disconnect Search" + message += "\n[3] quit" + choice = readInput(message, default="1").strip().upper() - if output.strip().lower() != 'n': + if choice == "Q": + raise SqlmapUserQuitException + elif choice == "2": + url = "https://search.disconnect.me/searchTerms/search?" + url += "start=nav&option=Web" + url += "&query=%s" % urlencode(dork, convall=True) + url += "&ses=Google&location_option=US" + url += "&nextDDG=%s" % urlencode("/search?q=&num=100&hl=en&start=%d&sa=N" % ((gpage - 1) * 10), convall=True) + url += "&sa=N&showIcons=false&filterIcons=none&js_enabled=1" + regex = DISCONNECT_SEARCH_REGEX + else: url = "https://duckduckgo.com/d.js?" url += "q=%s&p=%d&s=100" % (urlencode(dork, convall=True), gpage) + regex = DUCKDUCKGO_REGEX - if not conf.randomAgent: - self.opener.addheaders = [_ for _ in self.opener.addheaders if _[0].lower() != HTTP_HEADER.USER_AGENT.lower()] - self.opener.addheaders.append((HTTP_HEADER.USER_AGENT, "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0")) + if not conf.randomAgent: + self.opener.addheaders = [_ for _ in self.opener.addheaders if _[0].lower() != HTTP_HEADER.USER_AGENT.lower()] + self.opener.addheaders.append((HTTP_HEADER.USER_AGENT, DUMMY_SEARCH_USER_AGENT)) - self.opener.addheaders = [_ for _ in self.opener.addheaders if _[0].lower() != HTTP_HEADER.ACCEPT_ENCODING.lower()] - self.opener.addheaders.append((HTTP_HEADER.ACCEPT_ENCODING, HTTP_ACCEPT_ENCODING_HEADER_VALUE)) + self.opener.addheaders = [_ for _ in self.opener.addheaders if _[0].lower() != HTTP_HEADER.ACCEPT_ENCODING.lower()] + self.opener.addheaders.append((HTTP_HEADER.ACCEPT_ENCODING, HTTP_ACCEPT_ENCODING_HEADER_VALUE)) + try: + conn = self.opener.open(url) + + requestMsg = "HTTP request:\nGET %s" % url + requestMsg += " %s" % httplib.HTTPConnection._http_vsn_str + logger.log(CUSTOM_LOGGING.TRAFFIC_OUT, requestMsg) + + page = conn.read() + code = conn.code + status = conn.msg + responseHeaders = conn.info() + page = decodePage(page, responseHeaders.get("Content-Encoding"), responseHeaders.get("Content-Type")) + + responseMsg = "HTTP response (%s - %d):\n" % (status, code) + + if conf.verbose <= 4: + responseMsg += getUnicode(responseHeaders, UNICODE_ENCODING) + elif conf.verbose > 4: + responseMsg += "%s\n%s\n" % (responseHeaders, page) + + logger.log(CUSTOM_LOGGING.TRAFFIC_IN, responseMsg) + except urllib2.HTTPError, e: try: - conn = self.opener.open(url) + page = e.read() + except socket.timeout: + warnMsg = "connection timed out while trying " + warnMsg += "to get error page information (%d)" % e.code + logger.critical(warnMsg) + return None + except: + errMsg = "unable to connect" + raise SqlmapConnectionException(errMsg) - requestMsg = "HTTP request:\nGET %s" % url - requestMsg += " %s" % httplib.HTTPConnection._http_vsn_str - logger.log(CUSTOM_LOGGING.TRAFFIC_OUT, requestMsg) - - page = conn.read() - code = conn.code - status = conn.msg - responseHeaders = conn.info() - page = decodePage(page, responseHeaders.get("Content-Encoding"), responseHeaders.get("Content-Type")) - - responseMsg = "HTTP response (%s - %d):\n" % (status, code) - - if conf.verbose <= 4: - responseMsg += getUnicode(responseHeaders, UNICODE_ENCODING) - elif conf.verbose > 4: - responseMsg += "%s\n%s\n" % (responseHeaders, page) - - logger.log(CUSTOM_LOGGING.TRAFFIC_IN, responseMsg) - except urllib2.HTTPError, e: - try: - page = e.read() - except socket.timeout: - warnMsg = "connection timed out while trying " - warnMsg += "to get error page information (%d)" % e.code - logger.critical(warnMsg) - return None - except: - errMsg = "unable to connect to DuckDuckGo" - raise SqlmapConnectionException(errMsg) - - retVal = [urllib.unquote(match.group(1)) for match in re.finditer(DUCKDUCKGO_REGEX, page, re.I | re.S)] + retVal = [urllib.unquote(match.group(1)) for match in re.finditer(regex, page, re.I | re.S)] return retVal diff --git a/plugins/generic/filesystem.py b/plugins/generic/filesystem.py index 21b698b4e..30daccdc1 100644 --- a/plugins/generic/filesystem.py +++ b/plugins/generic/filesystem.py @@ -55,10 +55,10 @@ class Filesystem: localFileSize = os.path.getsize(localFile) if fileRead and Backend.isDbms(DBMS.PGSQL): - logger.info("length of read file %s cannot be checked on PostgreSQL" % remoteFile) + logger.info("length of read file '%s' cannot be checked on PostgreSQL" % remoteFile) sameFile = True else: - logger.debug("checking the length of the remote file %s" % remoteFile) + logger.debug("checking the length of the remote file '%s'" % remoteFile) remoteFileSize = inject.getValue(lengthQuery, resumeValue=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) sameFile = None @@ -69,14 +69,14 @@ class Filesystem: if localFileSize == remoteFileSize: sameFile = True - infoMsg = "the local file %s and the remote file " % localFile - infoMsg += "%s have the same size (%db)" % (remoteFile, localFileSize) + infoMsg = "the local file '%s' and the remote file " % localFile + infoMsg += "'%s' have the same size (%d B)" % (remoteFile, localFileSize) elif remoteFileSize > localFileSize: - infoMsg = "the remote file %s is larger (%db) than " % (remoteFile, remoteFileSize) - infoMsg += "the local file %s (%db)" % (localFile, localFileSize) + infoMsg = "the remote file '%s' is larger (%d B) than " % (remoteFile, remoteFileSize) + infoMsg += "the local file '%s' (%dB)" % (localFile, localFileSize) else: - infoMsg = "the remote file %s is smaller (%db) than " % (remoteFile, remoteFileSize) - infoMsg += "file %s (%db)" % (localFile, localFileSize) + infoMsg = "the remote file '%s' is smaller (%d B) than " % (remoteFile, remoteFileSize) + infoMsg += "file '%s' (%d B)" % (localFile, localFileSize) logger.info(infoMsg) else: @@ -153,7 +153,7 @@ class Filesystem: if forceCheck is not True: message = "do you want confirmation that the local file '%s' " % localFile message += "has been successfully written on the back-end DBMS " - message += "file system (%s)? [Y/n] " % remoteFile + message += "file system ('%s')? [Y/n] " % remoteFile output = readInput(message, default="Y") if forceCheck or (output and output.lower() == "y"): @@ -276,14 +276,14 @@ class Filesystem: if conf.direct or isStackingAvailable(): if isStackingAvailable(): - debugMsg = "going to upload the %s file with " % fileType + debugMsg = "going to upload the file '%s' with " % fileType debugMsg += "stacked query SQL injection technique" logger.debug(debugMsg) written = self.stackedWriteFile(localFile, remoteFile, fileType, forceCheck) self.cleanup(onlyFileTbl=True) elif isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) and Backend.isDbms(DBMS.MYSQL): - debugMsg = "going to upload the %s file with " % fileType + debugMsg = "going to upload the file '%s' with " % fileType debugMsg += "UNION query SQL injection technique" logger.debug(debugMsg)