diff --git a/src/sqlmap-master/lib/controller/controller.py b/src/sqlmap-master/lib/controller/controller.py index cbb8cd7..6769079 100644 --- a/src/sqlmap-master/lib/controller/controller.py +++ b/src/sqlmap-master/lib/controller/controller.py @@ -86,55 +86,71 @@ def _selectInjection(): points = {} + # 遍历kb.injections中的每一个injection for injection in kb.injections: place = injection.place parameter = injection.parameter ptype = injection.ptype + # 将place, parameter, ptype组成一个元组point point = (place, parameter, ptype) + # 如果point不在points中,则将injection添加到points中 if point not in points: points[point] = injection else: + # 如果point已经在points中,则将injection中的非data字段添加到points中 for key in points[point]: if key != 'data': points[point][key] = points[point][key] or injection[key] + # 将injection中的data字段更新到points中 points[point]['data'].update(injection['data']) + # 如果points中只有一个injection,则将kb.injection设置为kb.injections[0] if len(points) == 1: kb.injection = kb.injections[0] + # 如果points中有多个injection,则提示用户选择一个injection elif len(points) > 1: message = "there were multiple injection points, please select " message += "the one to use for following injections:\n" points = [] + # 遍历kb.injections中的每一个injection for i in xrange(0, len(kb.injections)): place = kb.injections[i].place parameter = kb.injections[i].parameter ptype = kb.injections[i].ptype point = (place, parameter, ptype) + # 如果point不在points中,则将point添加到points中 if point not in points: points.append(point) + # 如果ptype是整数,则将其转换为PAYLOAD.PARAMETER[ptype] ptype = PAYLOAD.PARAMETER[ptype] if isinstance(ptype, int) else ptype + # 构造提示信息 message += "[%d] place: %s, parameter: " % (i, place) message += "%s, type: %s" % (parameter, ptype) + # 如果是第一个injection,则设置为默认 if i == 0: message += " (default)" message += "\n" + # 提示用户选择一个injection message += "[q] Quit" choice = readInput(message, default='0').upper() + # 如果用户选择的是一个有效的injection,则将kb.injection设置为kb.injections[index] if isDigit(choice) and int(choice) < len(kb.injections) and int(choice) >= 0: index = int(choice) + # 如果用户选择的是q,则抛出SqlmapUserQuitException异常 elif choice == 'Q': raise SqlmapUserQuitException + # 如果用户选择的是一个无效的injection,则抛出SqlmapValueException异常 else: errMsg = "invalid choice" raise SqlmapValueException(errMsg) @@ -142,24 +158,33 @@ def _selectInjection(): kb.injection = kb.injections[index] def _formatInjection(inj): + # 获取参数类型 paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else inj.place + # 格式化数据 data = "Parameter: %s (%s)\n" % (inj.parameter, paramType) + # 遍历inj.data中的每个键值对 for stype, sdata in inj.data.items(): + # 获取title、vector、comment、payload title = sdata.title vector = sdata.vector comment = sdata.comment payload = agent.adjustLateValues(sdata.payload) + # 如果inj.place为CUSTOM_HEADER,则payload取split后的第二个元素 if inj.place == PLACE.CUSTOM_HEADER: payload = payload.split(',', 1)[1] + # 如果stype为UNION,则count为payload中逗号的数量加1,title中替换为count,vector中forgeUnionQuery if stype == PAYLOAD.TECHNIQUE.UNION: count = re.sub(r"(?i)(\(.+\))|(\blimit[^a-z]+)", "", sdata.payload).count(',') + 1 title = re.sub(r"\d+ to \d+", str(count), title) vector = agent.forgeUnionQuery("[QUERY]", vector[0], vector[1], vector[2], None, None, vector[5], vector[6]) + # 如果count为1,则title中替换为column if count == 1: title = title.replace("columns", "column") + # 如果comment不为空,则vector拼接comment elif comment: vector = "%s%s" % (vector, comment) + # 格式化数据 data += " Type: %s\n" % PAYLOAD.SQLINJECTION[stype] data += " Title: %s\n" % title data += " Payload: %s\n" % urldecode(payload, unsafe="&", spaceplus=(inj.place != PLACE.GET and kb.postSpaceToPlus)) @@ -168,98 +193,137 @@ def _formatInjection(inj): return data def _showInjections(): + # 如果conf.wizard且kb.wizardMode为True,则将kb.wizardMode置为False if conf.wizard and kb.wizardMode: kb.wizardMode = False + # 如果kb.testQueryCount大于0,则header为sqlmap识别到的注入点数量 if kb.testQueryCount > 0: header = "sqlmap identified the following injection point(s) with " header += "a total of %d HTTP(s) requests" % kb.testQueryCount + # 否则header为sqlmap从存储的会话中恢复的注入点 else: header = "sqlmap resumed the following injection point(s) from stored session" + # 如果conf.api,则使用conf.dumper.string输出url、query、data和kb.injections if conf.api: conf.dumper.string("", {"url": conf.url, "query": conf.parameters.get(PLACE.GET), "data": conf.parameters.get(PLACE.POST)}, content_type=CONTENT_TYPE.TARGET) conf.dumper.string("", kb.injections, content_type=CONTENT_TYPE.TECHNIQUES) + # 否则使用conf.dumper.string输出header和格式化后的kb.injections else: data = "".join(set(_formatInjection(_) for _ in kb.injections)).rstrip("\n") conf.dumper.string(header, data) + # 如果conf.tamper,则输出警告信息 if conf.tamper: warnMsg = "changes made by tampering scripts are not " warnMsg += "included in shown payload content(s)" logger.warning(warnMsg) + # 如果conf.hpp,则输出警告信息 if conf.hpp: warnMsg = "changes made by HTTP parameter pollution are not " warnMsg += "included in shown payload content(s)" logger.warning(warnMsg) def _randomFillBlankFields(value): + # 将retVal赋值为value retVal = value + # 如果value中存在EMPTY_FORM_FIELDS_REGEX,则询问是否填充空白字段 if extractRegexResult(EMPTY_FORM_FIELDS_REGEX, value): message = "do you want to fill blank fields with random values? [Y/n] " + # 如果输入为Y或空,则将retVal中匹配到的字段替换为随机值 if readInput(message, default='Y', boolean=True): for match in re.finditer(EMPTY_FORM_FIELDS_REGEX, retVal): item = match.group("result") + # 如果item不在IGNORE_PARAMETERS中且不匹配ASP_NET_CONTROL_REGEX,则替换为随机值 if not any(_ in item for _ in IGNORE_PARAMETERS) and not re.search(ASP_NET_CONTROL_REGEX, item): newValue = randomStr() if not re.search(r"^id|id$", item, re.I) else randomInt() + # 如果item以DEFAULT_GET_POST_DELIMITER结尾,则替换为"%s%s%s" % (item[:-1], newValue, DEFAULT_GET_POST_DELIMITER) if item[-1] == DEFAULT_GET_POST_DELIMITER: retVal = retVal.replace(item, "%s%s%s" % (item[:-1], newValue, DEFAULT_GET_POST_DELIMITER)) + # 否则替换为"%s%s" % (item, newValue) else: retVal = retVal.replace(item, "%s%s" % (item, newValue)) return retVal def _saveToHashDB(): + # 从hashDB中获取kb.injections injections = hashDBRetrieve(HASHDB_KEYS.KB_INJECTIONS, True) + # 如果injections不是列表,则将其赋值为空列表 if not isListLike(injections): injections = [] + # 将kb.injections中满足条件的元素添加到injections中 injections.extend(_ for _ in kb.injections if _ and _.place is not None and _.parameter is not None) _ = dict() + # 遍历injections中的元素 for injection in injections: + # 将injection.place、injection.parameter、injection.ptype作为key key = (injection.place, injection.parameter, injection.ptype) + # 如果key不在_中,则将injection添加到_中 if key not in _: _[key] = injection + # 否则,将injection.data中的元素更新到_中 else: _[key].data.update(injection.data) + # 将_中的元素写入hashDB hashDBWrite(HASHDB_KEYS.KB_INJECTIONS, list(_.values()), True) + # 从hashDB中获取kb.absFilePaths _ = hashDBRetrieve(HASHDB_KEYS.KB_ABS_FILE_PATHS, True) + # 将kb.absFilePaths中的元素添加到_中 hashDBWrite(HASHDB_KEYS.KB_ABS_FILE_PATHS, kb.absFilePaths | (_ if isinstance(_, set) else set()), True) + # 如果hashDB中没有kb.chars,则将kb.chars写入hashDB if not hashDBRetrieve(HASHDB_KEYS.KB_CHARS): hashDBWrite(HASHDB_KEYS.KB_CHARS, kb.chars, True) + # 如果hashDB中没有kb.dynamicMarkings,则将kb.dynamicMarkings写入hashDB if not hashDBRetrieve(HASHDB_KEYS.KB_DYNAMIC_MARKINGS): hashDBWrite(HASHDB_KEYS.KB_DYNAMIC_MARKINGS, kb.dynamicMarkings, True) def _saveToResultsFile(): + # 如果没有设置conf.resultsFP,则返回 if not conf.resultsFP: return + # 创建results字典 results = {} + # 获取PAYLOAD.TECHNIQUE中的所有公共成员,并将其转换为字典 techniques = dict((_[1], _[0]) for _ in getPublicTypeMembers(PAYLOAD.TECHNIQUE)) + # 遍历kb.injections和kb.falsePositives中的元素 for injection in kb.injections + kb.falsePositives: + # 如果injection.place或injection.parameter为None,则跳过 if injection.place is None or injection.parameter is None: continue + # 将injection.place、injection.parameter、injection.notes作为key key = (injection.place, injection.parameter, ';'.join(injection.notes)) + # 如果key不在results中,则将key添加到results中 if key not in results: results[key] = [] + # 将injection.data中的元素添加到results[key]中 results[key].extend(list(injection.data.keys())) + # 尝试将results中的元素写入conf.resultsFP try: for key, value in results.items(): + # 获取key中的元素 place, parameter, notes = key + # 将key中的元素转换为字符串,并添加到line中 line = "%s,%s,%s,%s,%s%s" % (safeCSValue(kb.originalUrls.get(conf.url) or conf.url), place, parameter, "".join(techniques[_][0].upper() for _ in sorted(value)), notes, os.linesep) + # 将line写入conf.resultsFP conf.resultsFP.write(line) + # 刷新conf.resultsFP conf.resultsFP.flush() + # 如果发生IOError,则抛出SqlmapSystemException except IOError as ex: errMsg = "unable to write to the results file '%s' ('%s'). " % (conf.resultsFile, getSafeExString(ex)) raise SqlmapSystemException(errMsg) @@ -272,43 +336,55 @@ def start(): check if they are dynamic and SQL injection affected """ + # 如果设置了conf.hashFile,则调用crackHashFile函数 if conf.hashFile: crackHashFile(conf.hashFile) + # 如果设置了conf.direct,则调用initTargetEnv、setupTargetEnv、action函数 if conf.direct: initTargetEnv() setupTargetEnv() action() return True + # 如果设置了conf.url且没有设置conf.forms和conf.crawlDepth,则将(conf.url, conf.method, conf.data, conf.cookie, None)添加到kb.targets中 if conf.url and not any((conf.forms, conf.crawlDepth)): kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None)) + # 如果设置了conf.configFile且没有设置kb.targets,则抛出SqlmapSystemException 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 + # 如果kb.targets不为空且kb.targets是列表,且kb.targets的长度大于1,则输出信息 if kb.targets and isListLike(kb.targets) and len(kb.targets) > 1: infoMsg = "found a total of %d targets" % len(kb.targets) logger.info(infoMsg) + # 初始化targetCount为0 targetCount = 0 + # 初始化initialHeaders为conf.httpHeaders initialHeaders = list(conf.httpHeaders) + # 遍历kb.targets中的元素 for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets: + # 将targetCount加1 targetCount += 1 try: + # 如果设置了conf.checkInternet,则调用checkInternet函数 if conf.checkInternet: infoMsg = "checking for Internet connection" logger.info(infoMsg) + # 如果没有检测到Internet连接,则输出警告信息 if not checkInternet(): warnMsg = "[%s] [WARNING] no connection detected" % time.strftime("%X") dataToStdout(warnMsg) + # 尝试检测Internet连接 valid = False for _ in xrange(conf.retries): if checkInternet(): @@ -318,29 +394,38 @@ def start(): dataToStdout('.') time.sleep(5) + # 如果没有检测到Internet连接,则抛出SqlmapConnectionException if not valid: errMsg = "please check your Internet connection and rerun" raise SqlmapConnectionException(errMsg) else: dataToStdout("\n") + # 将conf.url赋值为targetUrl conf.url = targetUrl + # 将conf.method赋值为targetMethod的大写形式 conf.method = targetMethod.upper().strip() if targetMethod else targetMethod + # 将conf.data赋值为targetData conf.data = targetData + # 将conf.cookie赋值为targetCookie conf.cookie = targetCookie + # 将conf.httpHeaders赋值为initialHeaders和targetHeaders的并集 conf.httpHeaders = list(initialHeaders) conf.httpHeaders.extend(targetHeaders or []) + # 如果设置了conf.randomAgent或conf.mobile,则将initialHeaders中USER_AGENT的值赋值给conf.httpHeaders 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.data,则将conf.data中的__ ASP(.NET)参数进行URL编码 if conf.data: # Note: explicitly URL encode __ ASP(.NET) parameters (e.g. to avoid problems with Base64 encoded '+' character) - standard procedure in web browsers conf.data = re.sub(r"\b(__\w+)=([^&]+)", lambda match: "%s=%s" % (match.group(1), urlencode(match.group(2), safe='%')), conf.data) + # 将conf.httpHeaders中重复的header删除 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() @@ -348,19 +433,24 @@ def start(): testSqlInj = False + # 如果配置中包含PLACE.GET参数,并且没有conf.data和conf.testParameter参数,则遍历conf.parameters[PLACE.GET]中的参数 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]) + # 如果paramKey不在kb.testedParams中,则设置testSqlInj为True if paramKey not in kb.testedParams: testSqlInj = True break else: + # 否则,设置paramKey为(conf.hostname, conf.path, None, None),并检查paramKey是否在kb.testedParams中 paramKey = (conf.hostname, conf.path, None, None) if paramKey not in kb.testedParams: testSqlInj = True + # 如果testSqlInj为True,并且conf.hostname在kb.vulnHosts中,则检查是否跳过该主机 if testSqlInj and conf.hostname in kb.vulnHosts: + # 如果kb.skipVulnHost为None,则提示用户是否跳过该主机的进一步测试 if kb.skipVulnHost is None: message = "SQL injection vulnerability has already been detected " message += "against '%s'. Do you want to skip " % conf.hostname @@ -368,13 +458,16 @@ def start(): kb.skipVulnHost = readInput(message, default='Y', boolean=True) + # 如果kb.skipVulnHost为True,则设置testSqlInj为False testSqlInj = not kb.skipVulnHost + # 如果testSqlInj为False,则跳过该URL if not testSqlInj: infoMsg = "skipping '%s'" % targetUrl logger.info(infoMsg) continue + # 如果conf.multipleTargets为True,则提示用户是否测试该表单或URL if conf.multipleTargets: if conf.forms and conf.method: message = "[%d/%s] Form:\n%s %s" % (targetCount, len(kb.targets) if isListLike(kb.targets) else '?', conf.method, targetUrl) @@ -388,23 +481,28 @@ def start(): message += "\n%s data: %s" % ((conf.method if conf.method != HTTPMETHOD.GET else None) 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: + # 如果conf.method为GET,并且targetUrl中不包含"?",则跳过 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() + # 如果用户选择"N",则跳过 if choice == 'N': continue + # 如果用户选择"Q",则退出 elif choice == 'Q': break else: + # 如果conf.method不为GET,则编辑POST数据 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 + # 如果conf.method为GET,则编辑GET数据 else: if '?' in targetUrl: firstPart, secondPart = targetUrl.split('?', 1) @@ -413,29 +511,37 @@ def start(): test = _randomFillBlankFields(test) conf.url = "%s?%s" % (firstPart, test) + # 解析目标URL parseTargetUrl() else: + # 如果conf.scope为False,则提示用户是否测试该URL if not conf.scope: message += "\ndo you want to test this URL? [Y/n/q]" choice = readInput(message, default='Y').upper() + # 如果用户选择"N",则跳过 if choice == 'N': dataToStdout(os.linesep) continue + # 如果用户选择"Q",则退出 elif choice == 'Q': break else: pass + # 打印测试URL infoMsg = "testing URL '%s'" % targetUrl logger.info(infoMsg) + # 设置目标环境 setupTargetEnv() + # 检查连接 if not checkConnection(suppressOutput=conf.forms): continue + # 如果conf.rParam为True,并且kb.originalPage不为空,则更新kb.randomPool if conf.rParam and kb.originalPage: kb.randomPool = dict([_ for _ in kb.randomPool.items() if isinstance(_[1], list)]) @@ -445,12 +551,16 @@ def start(): if options: kb.randomPool[name] = options + # 检查WAF checkWaf() + # 如果conf.nullConnection为True,则检查空连接 if conf.nullConnection: checkNullConnection() + # 如果kb.injections为空,或者kb.injections中只有一个place为None的元素,或者kb.injection.place为None,或者kb.injection.parameter为None,则进行以下操作 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): + # 如果conf.string和conf.notString和conf.regexp都为空,并且PAYLOAD.TECHNIQUE.BOOLEAN在conf.technique中,则检查页面稳定性 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 @@ -595,6 +705,7 @@ def start(): 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) @@ -608,7 +719,9 @@ def start(): 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: @@ -616,6 +729,7 @@ def start(): kb.injections.append(injection) + # 如果没有发出警报,则发出警报 if not kb.alerted: if conf.alert: infoMsg = "executing alerting shell command(s) ('%s')" % conf.alert @@ -649,37 +763,53 @@ def start(): if place == PLACE.COOKIE: kb.mergeCookies = popValue() + # 如果kb.injections的长度为0,或者长度为1且kb.injections[0].place为None,则执行以下代码 if len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None): + # 如果kb.vainRun为True且conf.multipleTargets为False,则执行以下代码 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')" + # 如果kb.originalPage存在,则执行以下代码 if kb.originalPage: advice = [] + # 如果conf.forms为False且kb.originalPage中存在