|
|
|
@ -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中存在<form,则将--forms添加到advice中
|
|
|
|
|
if not conf.forms and re.search(r"<form", kb.originalPage) is not None:
|
|
|
|
|
advice.append("--forms")
|
|
|
|
|
# 如果conf.crawlDepth为False且kb.originalPage中存在href,则将--crawl=2添加到advice中
|
|
|
|
|
if not conf.crawlDepth and re.search(r"href=[\"']/?\w", kb.originalPage) is not None:
|
|
|
|
|
advice.append("--crawl=2")
|
|
|
|
|
# 如果advice不为空,则将advice添加到错误信息中
|
|
|
|
|
if advice:
|
|
|
|
|
errMsg += ". You are advised to rerun with '%s'" % ' '.join(advice)
|
|
|
|
|
# 抛出SqlmapNoneDataException异常,并传递错误信息
|
|
|
|
|
raise SqlmapNoneDataException(errMsg)
|
|
|
|
|
# 否则,执行以下代码
|
|
|
|
|
else:
|
|
|
|
|
# 定义错误信息
|
|
|
|
|
errMsg = "all tested parameters do not appear to be injectable."
|
|
|
|
|
|
|
|
|
|
# 如果conf.level小于5或conf.risk小于3,则将以下信息添加到错误信息中
|
|
|
|
|
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."
|
|
|
|
|
|
|
|
|
|
# 如果conf.technique为列表且长度小于5,则将以下信息添加到错误信息中
|
|
|
|
|
if isinstance(conf.technique, list) and len(conf.technique) < 5:
|
|
|
|
|
errMsg += " Rerun without providing the option '--technique'."
|
|
|
|
|
|
|
|
|
|
# 如果conf.textOnly为False且kb.originalPage存在,则执行以下代码
|
|
|
|
|
if not conf.textOnly and kb.originalPage:
|
|
|
|
|
# 计算kb.originalPage中文本内容的百分比
|
|
|
|
|
percent = (100.0 * len(getFilteredPageContent(kb.originalPage)) / len(kb.originalPage))
|
|
|
|
|
|
|
|
|
|
# 如果kb.dynamicMarkings为True,则将以下信息添加到错误信息中
|
|
|
|
|
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)."
|
|
|
|
|
# 如果文本内容的百分比小于LOW_TEXT_PERCENT且kb.errorIsNone为False,则将以下信息添加到错误信息中
|
|
|
|
|
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 "
|
|
|
|
@ -688,6 +818,7 @@ def start():
|
|
|
|
|
errMsg += "of comparison engine to detect at least "
|
|
|
|
|
errMsg += "one dynamic parameter)."
|
|
|
|
|
|
|
|
|
|
# 如果kb.heuristicTest为HEURISTIC_TEST.POSITIVE,则将以下信息添加到错误信息中
|
|
|
|
|
if kb.heuristicTest == HEURISTIC_TEST.POSITIVE:
|
|
|
|
|
errMsg += " As heuristic test turned out positive you are "
|
|
|
|
|
errMsg += "strongly advised to continue on with the tests."
|
|
|
|
@ -716,29 +847,41 @@ def start():
|
|
|
|
|
# Flush the flag
|
|
|
|
|
kb.testMode = False
|
|
|
|
|
|
|
|
|
|
# Save the results to a file
|
|
|
|
|
_saveToResultsFile()
|
|
|
|
|
# Save the results to a hash database
|
|
|
|
|
_saveToHashDB()
|
|
|
|
|
# Show the injections
|
|
|
|
|
_showInjections()
|
|
|
|
|
# Select an injection
|
|
|
|
|
_selectInjection()
|
|
|
|
|
|
|
|
|
|
# Check if there is an injection
|
|
|
|
|
if kb.injection.place is not None and kb.injection.parameter is not None:
|
|
|
|
|
# Check if there are multiple targets
|
|
|
|
|
if conf.multipleTargets:
|
|
|
|
|
# Ask the user if they want to exploit the SQL injection
|
|
|
|
|
message = "do you want to exploit this SQL injection? [Y/n] "
|
|
|
|
|
condition = readInput(message, default='Y', boolean=True)
|
|
|
|
|
else:
|
|
|
|
|
# Set the condition to True
|
|
|
|
|
condition = True
|
|
|
|
|
|
|
|
|
|
# If the condition is True, execute the action
|
|
|
|
|
if condition:
|
|
|
|
|
action()
|
|
|
|
|
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
# 捕获用户中断异常
|
|
|
|
|
if kb.lastCtrlCTime and (time.time() - kb.lastCtrlCTime < 1):
|
|
|
|
|
# 如果用户在1秒内连续按下了Ctrl+C,则设置kb.multipleCtrlC为True,并抛出SqlmapUserQuitException异常
|
|
|
|
|
kb.multipleCtrlC = True
|
|
|
|
|
raise SqlmapUserQuitException("user aborted (Ctrl+C was pressed multiple times)")
|
|
|
|
|
|
|
|
|
|
kb.lastCtrlCTime = time.time()
|
|
|
|
|
|
|
|
|
|
if conf.multipleTargets:
|
|
|
|
|
# 如果在多个目标模式下,用户中断了扫描
|
|
|
|
|
warnMsg = "user aborted in multiple target mode"
|
|
|
|
|
logger.warning(warnMsg)
|
|
|
|
|
|
|
|
|
@ -746,49 +889,63 @@ def start():
|
|
|
|
|
choice = readInput(message, default='Y').upper()
|
|
|
|
|
|
|
|
|
|
if choice == 'N':
|
|
|
|
|
# 如果用户选择不跳过,则返回False
|
|
|
|
|
return False
|
|
|
|
|
elif choice == 'Q':
|
|
|
|
|
# 如果用户选择退出,则抛出SqlmapUserQuitException异常
|
|
|
|
|
raise SqlmapUserQuitException
|
|
|
|
|
else:
|
|
|
|
|
# 如果不在多个目标模式下,则抛出异常
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
except SqlmapSkipTargetException:
|
|
|
|
|
# 捕获SqlmapSkipTargetException异常,不做任何操作
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
except SqlmapUserQuitException:
|
|
|
|
|
# 捕获SqlmapUserQuitException异常,抛出异常
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
except SqlmapSilentQuitException:
|
|
|
|
|
# 捕获SqlmapSilentQuitException异常,抛出异常
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
except SqlmapBaseException as ex:
|
|
|
|
|
# 捕获SqlmapBaseException异常,获取异常信息
|
|
|
|
|
errMsg = getSafeExString(ex)
|
|
|
|
|
|
|
|
|
|
if conf.multipleTargets:
|
|
|
|
|
# 如果在多个目标模式下,保存结果到文件,并跳过下一个目标
|
|
|
|
|
_saveToResultsFile()
|
|
|
|
|
|
|
|
|
|
errMsg += ", skipping to the next target"
|
|
|
|
|
logger.error(errMsg.lstrip(", "))
|
|
|
|
|
else:
|
|
|
|
|
# 如果不在多个目标模式下,记录错误信息,并返回False
|
|
|
|
|
logger.critical(errMsg)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
finally:
|
|
|
|
|
# 显示HTTP错误码
|
|
|
|
|
showHttpErrorCodes()
|
|
|
|
|
|
|
|
|
|
if kb.maxConnectionsFlag:
|
|
|
|
|
# 如果设置了最大连接数,则记录警告信息
|
|
|
|
|
warnMsg = "it appears that the target "
|
|
|
|
|
warnMsg += "has a maximum connections "
|
|
|
|
|
warnMsg += "constraint"
|
|
|
|
|
logger.warning(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)
|
|
|
|
|
|
|
|
|
|
# 返回True
|
|
|
|
|
return True
|
|
|
|
|