diff --git a/src/sqlmap-master/lib/request/__init__.py b/src/sqlmap-master/lib/request/__init__.py deleted file mode 100644 index 7777bde..0000000 --- a/src/sqlmap-master/lib/request/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) -See the file 'LICENSE' for copying permission -""" - -pass diff --git a/src/sqlmap-master/lib/request/basic.py b/src/sqlmap-master/lib/request/basic.py deleted file mode 100644 index 921ed2f..0000000 --- a/src/sqlmap-master/lib/request/basic.py +++ /dev/null @@ -1,446 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) -See the file 'LICENSE' for copying permission -""" - -import codecs -import gzip -import io -import logging -import re -import struct -import zlib - -from lib.core.common import Backend -from lib.core.common import extractErrorMessage -from lib.core.common import extractRegexResult -from lib.core.common import filterNone -from lib.core.common import getPublicTypeMembers -from lib.core.common import getSafeExString -from lib.core.common import isListLike -from lib.core.common import randomStr -from lib.core.common import readInput -from lib.core.common import resetCookieJar -from lib.core.common import singleTimeLogMessage -from lib.core.common import singleTimeWarnMessage -from lib.core.common import unArrayizeValue -from lib.core.convert import decodeHex -from lib.core.convert import getBytes -from lib.core.convert import getText -from lib.core.convert import getUnicode -from lib.core.data import conf -from lib.core.data import kb -from lib.core.data import logger -from lib.core.decorators import cachedmethod -from lib.core.decorators import lockedmethod -from lib.core.dicts import HTML_ENTITIES -from lib.core.enums import DBMS -from lib.core.enums import HTTP_HEADER -from lib.core.enums import PLACE -from lib.core.exception import SqlmapCompressionException -from lib.core.settings import BLOCKED_IP_REGEX -from lib.core.settings import DEFAULT_COOKIE_DELIMITER -from lib.core.settings import EVENTVALIDATION_REGEX -from lib.core.settings import HEURISTIC_PAGE_SIZE_THRESHOLD -from lib.core.settings import IDENTYWAF_PARSE_LIMIT -from lib.core.settings import MAX_CONNECTION_TOTAL_SIZE -from lib.core.settings import META_CHARSET_REGEX -from lib.core.settings import PARSE_HEADERS_LIMIT -from lib.core.settings import PRINTABLE_BYTES -from lib.core.settings import SELECT_FROM_TABLE_REGEX -from lib.core.settings import UNICODE_ENCODING -from lib.core.settings import VIEWSTATE_REGEX -from lib.parse.headers import headersParser -from lib.parse.html import htmlParser -from thirdparty import six -from thirdparty.chardet import detect -from thirdparty.identywaf import identYwaf -from thirdparty.odict import OrderedDict -from thirdparty.six import unichr as _unichr -from thirdparty.six.moves import http_client as _http_client - -@lockedmethod -def forgeHeaders(items=None, base=None): - """ - Prepare HTTP Cookie, HTTP User-Agent and HTTP Referer headers to use when performing - the HTTP requests - """ - - items = items or {} - - for _ in list(items.keys()): - if items[_] is None: - del items[_] - - headers = OrderedDict(conf.httpHeaders if base is None else base) - headers.update(items.items()) - - class _str(str): - def capitalize(self): - return _str(self) - - def title(self): - return _str(self) - - _ = headers - headers = OrderedDict() - for key, value in _.items(): - success = False - - for _ in headers: - if _.upper() == key.upper(): - del headers[_] - break - - if key.upper() not in (_.upper() for _ in getPublicTypeMembers(HTTP_HEADER, True)): - try: - headers[_str(key)] = value # dirty hack for http://bugs.python.org/issue12455 - except UnicodeEncodeError: # don't do the hack on non-ASCII header names (they have to be properly encoded later on) - pass - else: - success = True - if not success: - key = '-'.join(_.capitalize() for _ in key.split('-')) - headers[key] = value - - if conf.cj: - if HTTP_HEADER.COOKIE in headers: - for cookie in conf.cj: - if cookie is None or cookie.domain_specified and not (conf.hostname or "").endswith(cookie.domain): - continue - - if ("%s=" % getUnicode(cookie.name)) in getUnicode(headers[HTTP_HEADER.COOKIE]): - if conf.loadCookies: - conf.httpHeaders = filterNone((item if item[0] != HTTP_HEADER.COOKIE else None) for item in conf.httpHeaders) - elif kb.mergeCookies is None: - message = "you provided a HTTP %s header value, while " % HTTP_HEADER.COOKIE - message += "target URL provides its own cookies within " - message += "HTTP %s header which intersect with yours. " % HTTP_HEADER.SET_COOKIE - message += "Do you want to merge them in further requests? [Y/n] " - - kb.mergeCookies = readInput(message, default='Y', boolean=True) - - if kb.mergeCookies and kb.injection.place != PLACE.COOKIE: - def _(value): - return re.sub(r"(?i)\b%s=[^%s]+" % (re.escape(getUnicode(cookie.name)), conf.cookieDel or DEFAULT_COOKIE_DELIMITER), ("%s=%s" % (getUnicode(cookie.name), getUnicode(cookie.value))).replace('\\', r'\\'), value) - - headers[HTTP_HEADER.COOKIE] = _(headers[HTTP_HEADER.COOKIE]) - - if PLACE.COOKIE in conf.parameters: - conf.parameters[PLACE.COOKIE] = _(conf.parameters[PLACE.COOKIE]) - - conf.httpHeaders = [(item[0], item[1] if item[0] != HTTP_HEADER.COOKIE else _(item[1])) for item in conf.httpHeaders] - - elif not kb.testMode: - headers[HTTP_HEADER.COOKIE] += "%s %s=%s" % (conf.cookieDel or DEFAULT_COOKIE_DELIMITER, getUnicode(cookie.name), getUnicode(cookie.value)) - - if kb.testMode and not any((conf.csrfToken, conf.safeUrl)): - resetCookieJar(conf.cj) - - return headers - -def parseResponse(page, headers, status=None): - """ - @param page: the page to parse to feed the knowledge base htmlFp - (back-end DBMS fingerprint based upon DBMS error messages return - through the web application) list and absFilePaths (absolute file - paths) set. - """ - - if headers: - headersParser(headers) - - if page: - htmlParser(page if not status else "%s\n\n%s" % (status, page)) - -@cachedmethod -def checkCharEncoding(encoding, warn=True): - """ - Checks encoding name, repairs common misspellings and adjusts to - proper namings used in codecs module - - >>> checkCharEncoding('iso-8858', False) - 'iso8859-1' - >>> checkCharEncoding('en_us', False) - 'utf8' - """ - - if isinstance(encoding, six.binary_type): - encoding = getUnicode(encoding) - - if isListLike(encoding): - encoding = unArrayizeValue(encoding) - - if encoding: - encoding = encoding.lower() - else: - return encoding - - # Reference: http://www.destructor.de/charsets/index.htm - translate = {"windows-874": "iso-8859-11", "utf-8859-1": "utf8", "en_us": "utf8", "macintosh": "iso-8859-1", "euc_tw": "big5_tw", "th": "tis-620", "unicode": "utf8", "utc8": "utf8", "ebcdic": "ebcdic-cp-be", "iso-8859": "iso8859-1", "iso-8859-0": "iso8859-1", "ansi": "ascii", "gbk2312": "gbk", "windows-31j": "cp932", "en": "us"} - - for delimiter in (';', ',', '('): - if delimiter in encoding: - encoding = encoding[:encoding.find(delimiter)].strip() - - encoding = encoding.replace(""", "") - - # popular typos/errors - if "8858" in encoding: - encoding = encoding.replace("8858", "8859") # iso-8858 -> iso-8859 - elif "8559" in encoding: - encoding = encoding.replace("8559", "8859") # iso-8559 -> iso-8859 - elif "8895" in encoding: - encoding = encoding.replace("8895", "8859") # iso-8895 -> iso-8859 - elif "5889" in encoding: - encoding = encoding.replace("5889", "8859") # iso-5889 -> iso-8859 - elif "5589" in encoding: - encoding = encoding.replace("5589", "8859") # iso-5589 -> iso-8859 - elif "2313" in encoding: - encoding = encoding.replace("2313", "2312") # gb2313 -> gb2312 - elif encoding.startswith("x-"): - encoding = encoding[len("x-"):] # x-euc-kr -> euc-kr / x-mac-turkish -> mac-turkish - elif "windows-cp" in encoding: - encoding = encoding.replace("windows-cp", "windows") # windows-cp-1254 -> windows-1254 - - # name adjustment for compatibility - if encoding.startswith("8859"): - encoding = "iso-%s" % encoding - elif encoding.startswith("cp-"): - encoding = "cp%s" % encoding[3:] - elif encoding.startswith("euc-"): - encoding = "euc_%s" % encoding[4:] - elif encoding.startswith("windows") and not encoding.startswith("windows-"): - encoding = "windows-%s" % encoding[7:] - elif encoding.find("iso-88") > 0: - encoding = encoding[encoding.find("iso-88"):] - elif encoding.startswith("is0-"): - encoding = "iso%s" % encoding[4:] - elif encoding.find("ascii") > 0: - encoding = "ascii" - elif encoding.find("utf8") > 0: - encoding = "utf8" - elif encoding.find("utf-8") > 0: - encoding = "utf-8" - - # Reference: http://philip.html5.org/data/charsets-2.html - if encoding in translate: - encoding = translate[encoding] - elif encoding in ("null", "{charset}", "charset", "*") or not re.search(r"\w", encoding): - return None - - # Reference: http://www.iana.org/assignments/character-sets - # Reference: http://docs.python.org/library/codecs.html - try: - codecs.lookup(encoding) - except: - encoding = None - - if encoding: - try: - six.text_type(getBytes(randomStr()), encoding) - except: - if warn: - warnMsg = "invalid web page charset '%s'" % encoding - singleTimeLogMessage(warnMsg, logging.WARN, encoding) - encoding = None - - return encoding - -def getHeuristicCharEncoding(page): - """ - Returns page encoding charset detected by usage of heuristics - - Reference: https://chardet.readthedocs.io/en/latest/usage.html - - >>> getHeuristicCharEncoding(b"") - 'ascii' - """ - - key = hash(page) - retVal = kb.cache.encoding[key] if key in kb.cache.encoding else detect(page[:HEURISTIC_PAGE_SIZE_THRESHOLD])["encoding"] - kb.cache.encoding[key] = retVal - - if retVal and retVal.lower().replace('-', "") == UNICODE_ENCODING.lower().replace('-', ""): - infoMsg = "heuristics detected web page charset '%s'" % retVal - singleTimeLogMessage(infoMsg, logging.INFO, retVal) - - return retVal - -def decodePage(page, contentEncoding, contentType, percentDecode=True): - """ - Decode compressed/charset HTTP response - - >>> getText(decodePage(b"foo&bar", None, "text/html; charset=utf-8")) - 'foo&bar' - >>> getText(decodePage(b" ", None, "text/html; charset=utf-8")) - '\\t' - """ - - if not page or (conf.nullConnection and len(page) < 2): - return getUnicode(page) - - contentEncoding = contentEncoding.lower() if hasattr(contentEncoding, "lower") else "" - contentType = contentType.lower() if hasattr(contentType, "lower") else "" - - if contentEncoding in ("gzip", "x-gzip", "deflate"): - if not kb.pageCompress: - return None - - try: - if contentEncoding == "deflate": - data = io.BytesIO(zlib.decompress(page, -15)) # Reference: http://stackoverflow.com/questions/1089662/python-inflate-and-deflate-implementations - else: - data = gzip.GzipFile("", "rb", 9, io.BytesIO(page)) - size = struct.unpack(" MAX_CONNECTION_TOTAL_SIZE: - raise Exception("size too large") - - page = data.read() - except Exception as ex: - if b" 255 else _.group(0), page) - else: - page = getUnicode(page, kb.pageEncoding) - - return page - -def processResponse(page, responseHeaders, code=None, status=None): - kb.processResponseCounter += 1 - page = page or "" - - parseResponse(page, responseHeaders if kb.processResponseCounter < PARSE_HEADERS_LIMIT else None, status) - - if not kb.tableFrom and Backend.getIdentifiedDbms() in (DBMS.ACCESS,): - kb.tableFrom = extractRegexResult(SELECT_FROM_TABLE_REGEX, page) - else: - kb.tableFrom = None - - if conf.parseErrors: - msg = extractErrorMessage(page) - - if msg: - logger.warning("parsed DBMS error message: '%s'" % msg.rstrip('.')) - - if not conf.skipWaf and kb.processResponseCounter < IDENTYWAF_PARSE_LIMIT: - rawResponse = "%s %s %s\n%s\n%s" % (_http_client.HTTPConnection._http_vsn_str, code or "", status or "", "".join(getUnicode(responseHeaders.headers if responseHeaders else [])), page[:HEURISTIC_PAGE_SIZE_THRESHOLD]) - - with kb.locks.identYwaf: - identYwaf.non_blind.clear() - if identYwaf.non_blind_check(rawResponse, silent=True): - for waf in set(identYwaf.non_blind): - if waf not in kb.identifiedWafs: - kb.identifiedWafs.add(waf) - errMsg = "WAF/IPS identified as '%s'" % identYwaf.format_name(waf) - singleTimeLogMessage(errMsg, logging.CRITICAL) - - if kb.originalPage is None: - for regex in (EVENTVALIDATION_REGEX, VIEWSTATE_REGEX): - match = re.search(regex, page) - if match and PLACE.POST in conf.parameters: - name, value = match.groups() - if PLACE.POST in conf.paramDict and name in conf.paramDict[PLACE.POST]: - if conf.paramDict[PLACE.POST][name] in page: - continue - else: - msg = "do you want to automatically adjust the value of '%s'? [y/N]" % name - - if not readInput(msg, default='N', boolean=True): - continue - - conf.paramDict[PLACE.POST][name] = value - conf.parameters[PLACE.POST] = re.sub(r"(?i)(%s=)[^&]+" % re.escape(name), r"\g<1>%s" % value.replace('\\', r'\\'), conf.parameters[PLACE.POST]) - - if not kb.browserVerification and re.search(r"(?i)browser.?verification", page or ""): - kb.browserVerification = True - warnMsg = "potential browser verification protection mechanism detected" - if re.search(r"(?i)CloudFlare", page): - warnMsg += " (CloudFlare)" - singleTimeWarnMessage(warnMsg) - - if not kb.captchaDetected and re.search(r"(?i)captcha", page or ""): - for match in re.finditer(r"(?si)", page): - if re.search(r"(?i)captcha", match.group(0)): - kb.captchaDetected = True - break - - if re.search(r"]+\brefresh\b[^>]+\bcaptcha\b", page): - kb.captchaDetected = True - - if kb.captchaDetected: - warnMsg = "potential CAPTCHA protection mechanism detected" - if re.search(r"(?i)[^<]*CloudFlare", page): - warnMsg += " (CloudFlare)" - singleTimeWarnMessage(warnMsg) - - if re.search(BLOCKED_IP_REGEX, page): - warnMsg = "it appears that you have been blocked by the target server" - singleTimeWarnMessage(warnMsg) diff --git a/src/sqlmap-master/lib/request/basicauthhandler.py b/src/sqlmap-master/lib/request/basicauthhandler.py index a273682..9e93c7b 100644 --- a/src/sqlmap-master/lib/request/basicauthhandler.py +++ b/src/sqlmap-master/lib/request/basicauthhandler.py @@ -5,34 +5,63 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ +# 导入urllib库并重命名为_urllib from thirdparty.six.moves import urllib as _urllib class SmartHTTPBasicAuthHandler(_urllib.request.HTTPBasicAuthHandler): """ - Reference: http://selenic.com/hg/rev/6c51a5056020 - Fix for a: http://bugs.python.org/issue8797 + 参考: http://selenic.com/hg/rev/6c51a5056020 + 修复Bug: http://bugs.python.org/issue8797 + + 这是一个处理HTTP基础认证的智能处理器类,继承自HTTPBasicAuthHandler + 主要用于处理认证重试的逻辑,避免无限循环重试的问题 """ def __init__(self, *args, **kwargs): + # 调用父类的初始化方法 _urllib.request.HTTPBasicAuthHandler.__init__(self, *args, **kwargs) + # 创建一个集合用于存储已重试过的请求 self.retried_req = set() + # 重试计数器初始化为0 self.retried_count = 0 def reset_retry_count(self): - # Python 2.6.5 will call this on 401 or 407 errors and thus loop - # forever. We disable reset_retry_count completely and reset in - # http_error_auth_reqed instead. + """ + 重置重试计数的方法 + Python 2.6.5在遇到401或407错误时会调用此方法,可能导致无限循环 + 因此这里禁用了重置功能,改为在http_error_auth_reqed中进行重置 + """ pass def http_error_auth_reqed(self, auth_header, host, req, headers): - # Reset the retry counter once for each request. + """ + 处理需要认证的HTTP错误 + + 参数说明: + auth_header: 认证头信息 + host: 目标主机 + req: 请求对象 + headers: 请求头 + + 处理逻辑: + 1. 对每个新请求重置重试计数器 + 2. 限制最大重试次数为5次 + 3. 超过重试次数则抛出HTTP 401错误 + """ + # 如果是新的请求(通过hash判断) if hash(req) not in self.retried_req: + # 将请求添加到已重试集合中 self.retried_req.add(hash(req)) + # 重置重试计数 self.retried_count = 0 else: + # 如果重试次数超过5次 if self.retried_count > 5: + # 抛出HTTP 401认证失败错误 raise _urllib.error.HTTPError(req.get_full_url(), 401, "basic auth failed", headers, None) else: + # 增加重试计数 self.retried_count += 1 + # 调用父类的错误处理方法 return _urllib.request.HTTPBasicAuthHandler.http_error_auth_reqed(self, auth_header, host, req, headers) diff --git a/src/sqlmap-master/lib/request/chunkedhandler.py b/src/sqlmap-master/lib/request/chunkedhandler.py index 8477802..1dcca38 100644 --- a/src/sqlmap-master/lib/request/chunkedhandler.py +++ b/src/sqlmap-master/lib/request/chunkedhandler.py @@ -5,37 +5,63 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ -from lib.core.data import conf -from lib.core.enums import HTTP_HEADER -from thirdparty.six.moves import urllib as _urllib +# 导入必要的模块 +from lib.core.data import conf # 导入配置信息 +from lib.core.enums import HTTP_HEADER # 导入HTTP头部常量定义 +from thirdparty.six.moves import urllib as _urllib # 导入urllib模块并重命名为_urllib,用于处理URL和HTTP请求 class ChunkedHandler(_urllib.request.HTTPHandler): """ - Ensures that HTTPHandler is working properly in case of Chunked Transfer-Encoding + 用于确保在使用分块传输编码(Chunked Transfer-Encoding)时HTTPHandler能正常工作的处理器类 + 继承自urllib的HTTPHandler类,主要用于处理HTTP请求的发送 + 分块传输编码允许HTTP消息在不预先知道消息总长度的情况下进行传输 """ def _http_request(self, request): + """ + 处理HTTP请求的核心方法 + 参数: + request: HTTP请求对象,包含请求的所有信息(如URL、头部、数据等) + 返回: + 处理后的request对象 + """ + # 获取请求的主机名,优先使用get_host()方法(新版本),如果不存在则使用host属性(旧版本) host = request.get_host() if hasattr(request, "get_host") else request.host if not host: + # 如果没有指定主机名则抛出异常,因为HTTP请求必须知道发送到哪个主机 raise _urllib.error.URLError("no host given") - if request.data is not None: # POST + if request.data is not None: # 如果是POST请求(包含数据) data = request.data + # 如果没有设置Content-Type头,则设置为默认的表单格式 + # application/x-www-form-urlencoded 是最常见的POST数据格式 if not request.has_header(HTTP_HEADER.CONTENT_TYPE): request.add_unredirected_header(HTTP_HEADER.CONTENT_TYPE, "application/x-www-form-urlencoded") + # 如果没有设置Content-Length头且不使用分块传输,则设置内容长度 + # Content-Length告诉服务器请求体的具体长度 if not request.has_header(HTTP_HEADER.CONTENT_LENGTH) and not conf.chunked: request.add_unredirected_header(HTTP_HEADER.CONTENT_LENGTH, "%d" % len(data)) + # 设置用于选择的主机名 sel_host = host + # 如果使用了代理,则从请求选择器中解析出实际的主机名 + # 代理请求时,请求行中包含完整的URL,需要从中提取出真实的主机名 if request.has_proxy(): sel_host = _urllib.parse.urlsplit(request.get_selector()).netloc + # 如果没有设置Host头,则添加Host头 + # Host头是HTTP/1.1必需的头部,用于指定服务器的域名和端口号 if not request.has_header(HTTP_HEADER.HOST): request.add_unredirected_header(HTTP_HEADER.HOST, sel_host) + # 遍历父类中定义的额外头部信息 for name, value in self.parent.addheaders: - name = name.capitalize() + name = name.capitalize() # 将头部名称首字母大写,符合HTTP协议规范 + # 如果请求中没有该头部,则添加到请求中 + # 这确保了自定义头部不会覆盖已有的头部 if not request.has_header(name): request.add_unredirected_header(name, value) return request + # 将_http_request方法赋值给http_request,使其成为标准的处理方法 + # 这是一种Python的惯用法,允许在保留原始方法的同时提供一个公开的接口 http_request = _http_request diff --git a/src/sqlmap-master/lib/request/comparison.py b/src/sqlmap-master/lib/request/comparison.py index b62b899..7856ced 100644 --- a/src/sqlmap-master/lib/request/comparison.py +++ b/src/sqlmap-master/lib/request/comparison.py @@ -5,10 +5,12 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ +# 导入未来版本的除法运算 from __future__ import division import re +# 导入所需的工具函数 from lib.core.common import extractRegexResult from lib.core.common import getFilteredPageContent from lib.core.common import listToStrValue @@ -35,15 +37,28 @@ from lib.core.threads import getCurrentThreadData from thirdparty import six def comparison(page, headers, code=None, getRatioValue=False, pageLength=None): + """ + 页面比较的主入口函数 + :param page: 要比较的页面内容 + :param headers: HTTP响应头 + :param code: HTTP状态码 + :param getRatioValue: 是否返回相似度值 + :param pageLength: 页面长度 + :return: 比较结果 + """ _ = _adjust(_comparison(page, headers, code, getRatioValue, pageLength), getRatioValue) return _ def _adjust(condition, getRatioValue): + """ + 调整比较结果 + :param condition: 原始比较结果 + :param getRatioValue: 是否返回相似度值 + :return: 调整后的结果 + """ if not any((conf.string, conf.notString, conf.regexp, conf.code)): - # Negative logic approach is used in raw page comparison scheme as that what is "different" than original - # PAYLOAD.WHERE.NEGATIVE response is considered as True; in switch based approach negative logic is not - # applied as that what is by user considered as True is that what is returned by the comparison mechanism - # itself + # 在原始页面比较方案中使用负逻辑方法 + # 与原始PAYLOAD.WHERE.NEGATIVE响应"不同"的内容被认为是True retVal = not condition if kb.negativeLogic and condition is not None and not getRatioValue else condition else: retVal = condition if not getRatioValue else (MAX_RATIO if condition else MIN_RATIO) @@ -51,24 +66,37 @@ def _adjust(condition, getRatioValue): return retVal def _comparison(page, headers, code, getRatioValue, pageLength): + """ + 核心比较函数 + :param page: 要比较的页面内容 + :param headers: HTTP响应头 + :param code: HTTP状态码 + :param getRatioValue: 是否返回相似度值 + :param pageLength: 页面长度 + :return: 比较结果 + """ + # 获取当前线程数据 threadData = getCurrentThreadData() + # 测试模式下保存比较数据 if kb.testMode: threadData.lastComparisonHeaders = listToStrValue(_ for _ in headers.headers if not _.startswith("%s:" % URI_HTTP_HEADER)) if headers else "" threadData.lastComparisonPage = page threadData.lastComparisonCode = code + # 页面内容和长度都为空时返回None if page is None and pageLength is None: return None + # 处理字符串匹配情况 if any((conf.string, conf.notString, conf.regexp)): rawResponse = "%s%s" % (listToStrValue(_ for _ in headers.headers if not _.startswith("%s:" % URI_HTTP_HEADER)) if headers else "", page) - # String to match in page when the query is True + # 检查页面是否包含指定的匹配字符串 if conf.string: return conf.string in rawResponse - # String to match in page when the query is False + # 检查页面是否不包含指定的字符串 if conf.notString: if conf.notString in rawResponse: return False @@ -78,24 +106,25 @@ def _comparison(page, headers, code, getRatioValue, pageLength): else: return True - # Regular expression to match in page when the query is True and/or valid + # 使用正则表达式匹配页面内容 if conf.regexp: return re.search(conf.regexp, rawResponse, re.I | re.M) is not None - # HTTP code to match when the query is valid + # 检查HTTP状态码是否匹配 if conf.code: return conf.code == code + # 初始化序列匹配器 seqMatcher = threadData.seqMatcher seqMatcher.set_seq1(kb.pageTemplate) if page: - # In case of an DBMS error page return None + # 处理数据库错误页面 if kb.errorIsNone and (wasLastResponseDBMSError() or wasLastResponseHTTPError()) and not kb.negativeLogic: if not (wasLastResponseHTTPError() and getLastRequestHTTPError() in (conf.ignoreCode or [])): return None - # Dynamic content lines to be excluded before comparison + # 移除动态内容后再比较 if not kb.nullConnection: page = removeDynamicContent(page) seqMatcher.set_seq1(removeDynamicContent(kb.pageTemplate)) @@ -103,6 +132,7 @@ def _comparison(page, headers, code, getRatioValue, pageLength): if not pageLength: pageLength = len(page) + # 处理空连接情况 if kb.nullConnection and pageLength: if not seqMatcher.a: errMsg = "problem occurred while retrieving original page content " @@ -110,18 +140,19 @@ def _comparison(page, headers, code, getRatioValue, pageLength): errMsg += "and if the problem persists turn off any optimization switches" raise SqlmapNoneDataException(errMsg) + # 计算页面长度比率 ratio = 1. * pageLength / len(seqMatcher.a) if ratio > 1.: ratio = 1. / ratio else: - # Preventing "Unicode equal comparison failed to convert both arguments to Unicode" - # (e.g. if one page is PDF and the other is HTML) + # 处理编码问题,确保可以正确比较 if isinstance(seqMatcher.a, six.binary_type) and isinstance(page, six.text_type): page = getBytes(page, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore") elif isinstance(seqMatcher.a, six.text_type) and isinstance(page, six.binary_type): seqMatcher.a = getBytes(seqMatcher.a, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore") + # 处理各种比较情况 if any(_ is None for _ in (page, seqMatcher.a)): return None elif seqMatcher.a and page and seqMatcher.a == page: @@ -136,6 +167,7 @@ def _comparison(page, headers, code, getRatioValue, pageLength): else: seq1, seq2 = None, None + # 根据配置选择比较内容 if conf.titles: seq1 = extractRegexResult(HTML_TITLE_REGEX, seqMatcher.a) seq2 = extractRegexResult(HTML_TITLE_REGEX, page) @@ -146,13 +178,14 @@ def _comparison(page, headers, code, getRatioValue, pageLength): if seq1 is None or seq2 is None: return None + # 移除反射值标记 seq1 = seq1.replace(REFLECTED_VALUE_MARKER, "") seq2 = seq2.replace(REFLECTED_VALUE_MARKER, "") + # 处理高度动态内容 if kb.heavilyDynamic: seq1 = seq1.split("\n") seq2 = seq2.split("\n") - key = None else: key = (hash(seq1), hash(seq2)) @@ -160,6 +193,7 @@ def _comparison(page, headers, code, getRatioValue, pageLength): seqMatcher.set_seq1(seq1) seqMatcher.set_seq2(seq2) + # 使用缓存提高性能 if key in kb.cache.comparison: ratio = kb.cache.comparison[key] else: @@ -168,8 +202,7 @@ def _comparison(page, headers, code, getRatioValue, pageLength): if key: kb.cache.comparison[key] = ratio - # If the url is stable and we did not set yet the match ratio and the - # current injected value changes the url page content + # 设置匹配比率 if kb.matchRatio is None: if ratio >= LOWER_RATIO_BOUND and ratio <= UPPER_RATIO_BOUND: kb.matchRatio = ratio @@ -178,19 +211,14 @@ def _comparison(page, headers, code, getRatioValue, pageLength): if kb.testMode: threadData.lastComparisonRatio = ratio - # If it has been requested to return the ratio and not a comparison - # response + # 根据不同情况返回结果 if getRatioValue: return ratio - elif ratio > UPPER_RATIO_BOUND: return True - elif ratio < LOWER_RATIO_BOUND: return False - elif kb.matchRatio is None: return None - else: return (ratio - kb.matchRatio) > DIFF_TOLERANCE diff --git a/src/sqlmap-master/lib/request/connect.py b/src/sqlmap-master/lib/request/connect.py index 0651ded..6d9f697 100644 --- a/src/sqlmap-master/lib/request/connect.py +++ b/src/sqlmap-master/lib/request/connect.py @@ -5,6 +5,7 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ +# 导入所需的标准库 import binascii import inspect import logging @@ -18,6 +19,7 @@ import sys import time import traceback +# 尝试导入websocket库,如果不存在则定义一个简单的异常类 try: import websocket from websocket import WebSocketException @@ -25,6 +27,7 @@ except ImportError: class WebSocketException(Exception): pass +# 导入sqlmap自定义的库和工具函数 from lib.core.agent import agent from lib.core.common import asciifyUrl from lib.core.common import calculateDeltaSeconds @@ -146,13 +149,18 @@ from thirdparty.socks.socks import ProxyError class Connect(object): """ - This class defines methods used to perform HTTP requests + 这个类定义了用于执行HTTP请求的方法 """ @staticmethod def _getPageProxy(**kwargs): + """ + 代理方法,用于处理页面请求 + 检查递归深度并调用getPage方法 + """ try: - if (len(inspect.stack()) > sys.getrecursionlimit() // 2): # Note: https://github.com/sqlmapproject/sqlmap/issues/4525 + # 检查调用栈深度是否超过限制 + if (len(inspect.stack()) > sys.getrecursionlimit() // 2): warnMsg = "unable to connect to the target URL" raise SqlmapConnectionException(warnMsg) except (TypeError, UnicodeError): @@ -165,9 +173,15 @@ class Connect(object): @staticmethod def _retryProxy(**kwargs): + """ + 重试代理方法 + 处理请求失败时的重试逻辑 + """ + # 获取当前线程数据 threadData = getCurrentThreadData() threadData.retriesCount += 1 + # 如果配置了代理列表且重试次数达到上限,则更换代理 if conf.proxyList and threadData.retriesCount >= conf.retries and not kb.locks.handlers.locked(): warnMsg = "changing proxy" logger.warning(warnMsg) @@ -177,9 +191,8 @@ class Connect(object): setHTTPHandlers() + # 处理基于时间的测试模式 if kb.testMode and kb.previousMethod == PAYLOAD.METHOD.TIME: - # timed based payloads can cause web server unresponsiveness - # if the injectable piece of code is some kind of JOIN-like query warnMsg = "most likely web server instance hasn't recovered yet " warnMsg += "from previous timed based payload. If the problem " warnMsg += "persists please wait for a few minutes and rerun " @@ -188,6 +201,7 @@ class Connect(object): warnMsg += "lower the value of option '--time-sec' (e.g. '--time-sec=2')" singleTimeWarnMessage(warnMsg) + # 处理原始页面为空的情况 elif kb.originalPage is None: if conf.tor: warnMsg = "please make sure that you have " @@ -214,20 +228,28 @@ class Connect(object): singleTimeWarnMessage(warnMsg) + # 处理多线程情况 elif conf.threads > 1: warnMsg = "if the problem persists please try to lower " warnMsg += "the number of used threads (option '--threads')" singleTimeWarnMessage(warnMsg) + # 重试请求 kwargs['retrying'] = True return Connect._getPageProxy(**kwargs) @staticmethod def _connReadProxy(conn): + """ + 读取连接响应的代理方法 + 处理压缩和大响应的情况 + """ retVal = b"" + # 如果不是DNS模式且连接存在 if not kb.dnsMode and conn: headers = conn.info() + # 处理压缩响应 if kb.pageCompress and headers and hasattr(headers, "getheader") and (headers.getheader(HTTP_HEADER.CONTENT_ENCODING, "").lower() in ("gzip", "deflate") or "text" not in headers.getheader(HTTP_HEADER.CONTENT_TYPE, "").lower()): retVal = conn.read(MAX_CONNECTION_TOTAL_SIZE) if len(retVal) == MAX_CONNECTION_TOTAL_SIZE: @@ -236,6 +258,7 @@ class Connect(object): kb.pageCompress = False raise SqlmapCompressionException else: + # 分块读取大响应 while True: if not conn: break @@ -254,11 +277,13 @@ class Connect(object): retVal += part break + # 检查总响应大小是否超过限制 if len(retVal) > MAX_CONNECTION_TOTAL_SIZE: warnMsg = "too large response detected. Automatically trimming it" singleTimeWarnMessage(warnMsg) break + # 处理特殊的响应放大因子 if conf.yuge: retVal = YUGE_FACTOR * retVal @@ -267,13 +292,14 @@ class Connect(object): @staticmethod def getPage(**kwargs): """ - This method connects to the target URL or proxy and returns - the target URL page content + 这个方法连接到目标URL或代理并返回目标URL页面内容 """ + # 如果是离线模式直接返回 if conf.offline: return None, None, None + # 获取请求参数 url = kwargs.get("url", None) or conf.url get = kwargs.get("get", None) post = kwargs.get("post", None) @@ -297,16 +323,19 @@ class Connect(object): finalCode = kwargs.get("finalCode", False) chunked = kwargs.get("chunked", False) or conf.chunked + # 处理请求延迟 if isinstance(conf.delay, (int, float)) and conf.delay > 0: time.sleep(conf.delay) start = time.time() + # 获取当前线程数据 threadData = getCurrentThreadData() with kb.locks.request: kb.requestCounter += 1 threadData.lastRequestUID = kb.requestCounter + # 处理代理频率 if conf.proxyFreq: if kb.requestCounter % conf.proxyFreq == 0: conf.proxy = None @@ -316,6 +345,7 @@ class Connect(object): setHTTPHandlers() + # 处理测试模式 if conf.dummy or conf.murphyRate and randomInt() % conf.murphyRate == 0: if conf.murphyRate: time.sleep(randomInt() % (MAX_MURPHY_SLEEP_TIME + 1)) @@ -327,6 +357,7 @@ class Connect(object): return page, headers, code + # 处理cookie if conf.liveCookies: with kb.locks.liveCookies: if not checkFile(conf.liveCookies, raiseOnError=False) or os.path.getsize(conf.liveCookies) == 0: @@ -351,6 +382,7 @@ class Connect(object): cookie = openFile(conf.liveCookies).read().strip() cookie = re.sub(r"(?i)\ACookie:\s*", "", cookie) + # 处理multipart请求 if multipart: post = multipart else: @@ -361,20 +393,20 @@ class Connect(object): post = _urllib.parse.unquote(post) post = chunkSplitPostData(post) + # 处理WebSocket请求 webSocket = url.lower().startswith("ws") if not _urllib.parse.urlsplit(url).netloc: url = _urllib.parse.urljoin(conf.url, url) - # flag to know if we are dealing with the same target host + # 检查是否是相同的目标主机 target = checkSameHost(url, conf.url) if not retrying: - # Reset the number of connection retries + # 重置连接重试次数 threadData.retriesCount = 0 - # fix for known issue when urllib2 just skips the other part of provided - # url splitted with space char while urlencoding it in the later phase + # 修复URL中的空格 url = url.replace(" ", "%20") if "://" not in url: @@ -396,8 +428,7 @@ class Connect(object): raise404 = raise404 and not kb.ignoreNotFound - # support for non-latin (e.g. cyrillic) URLs as urllib/urllib2 doesn't - # support those by default + # 支持非拉丁字符的URL url = asciifyUrl(url) try: @@ -440,7 +471,7 @@ class Connect(object): requestMsg += " %s" % _http_client.HTTPConnection._http_vsn_str - # Prepare HTTP headers + # 准备HTTP头 headers = forgeHeaders({HTTP_HEADER.COOKIE: cookie, HTTP_HEADER.USER_AGENT: ua, HTTP_HEADER.REFERER: referer, HTTP_HEADER.HOST: getHeader(dict(conf.httpHeaders), HTTP_HEADER.HOST) or getHostHeader(url)}, base=None if target else {}) if HTTP_HEADER.COOKIE in headers: @@ -624,11 +655,11 @@ class Connect(object): if not kb.proxyAuthHeader and getRequestHeader(req, HTTP_HEADER.PROXY_AUTHORIZATION): kb.proxyAuthHeader = getRequestHeader(req, HTTP_HEADER.PROXY_AUTHORIZATION) - # Return response object + # 返回响应对象 if response: return conn, None, None - # Get HTTP response + # 获取HTTP响应 if hasattr(conn, "redurl"): page = (threadData.lastRedirectMsg[1] if kb.choices.redirect == REDIRECTION.NO else Connect._connReadProxy(conn)) if not skipRead else None skipLogTraffic = kb.choices.redirect == REDIRECTION.NO diff --git a/src/sqlmap-master/lib/request/direct.py b/src/sqlmap-master/lib/request/direct.py index 1c418da..8d0fbaf 100644 --- a/src/sqlmap-master/lib/request/direct.py +++ b/src/sqlmap-master/lib/request/direct.py @@ -5,6 +5,7 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ +# 导入所需的模块 import re import time @@ -30,58 +31,87 @@ from lib.utils.safe2bin import safecharencode from lib.utils.timeout import timeout def direct(query, content=True): + """ + 直接执行SQL查询的主函数 + 参数: + query: 要执行的SQL查询语句 + content: 是否返回查询内容,默认为True + """ + # 标记是否为SELECT查询 select = True + # 处理查询语句,添加必要的payload query = agent.payloadDirect(query) query = agent.adjustLateValues(query) + # 获取当前线程数据 threadData = getCurrentThreadData() + # 针对Oracle数据库的特殊处理:如果是不带FROM的SELECT语句,添加"FROM DUAL" if Backend.isDbms(DBMS.ORACLE) and query.upper().startswith("SELECT ") and " FROM " not in query.upper(): query = "%s FROM DUAL" % query + # 通过遍历SQL语句字典判断是否为SELECT查询 for sqlTitle, sqlStatements in SQL_STATEMENTS.items(): for sqlStatement in sqlStatements: if query.lower().startswith(sqlStatement) and sqlTitle != "SQL SELECT statement": select = False break + # 如果是SELECT查询,进行相应处理 if select: + # 如果查询不以SELECT开头,添加SELECT if re.search(r"(?i)\ASELECT ", query) is None: query = "SELECT %s" % query + # 处理二进制字段 if conf.binaryFields: for field in conf.binaryFields: field = field.strip() if re.search(r"\b%s\b" % re.escape(field), query): query = re.sub(r"\b%s\b" % re.escape(field), agent.hexConvertField(field), query) + # 记录查询语句到日志 logger.log(CUSTOM_LOGGING.PAYLOAD, query) + # 尝试从缓存中获取查询结果 output = hashDBRetrieve(query, True, True) start = time.time() + # 执行查询 if not select and re.search(r"(?i)\bEXEC ", query) is None: + # 非SELECT且非EXEC语句的执行 timeout(func=conf.dbmsConnector.execute, args=(query,), duration=conf.timeout, default=None) elif not (output and ("%soutput" % conf.tablePrefix) not in query and ("%sfile" % conf.tablePrefix) not in query): + # SELECT查询的执行 output, state = timeout(func=conf.dbmsConnector.select, args=(query,), duration=conf.timeout, default=None) if state == TIMEOUT_STATE.NORMAL: + # 正常执行完成,将结果写入缓存 hashDBWrite(query, output, True) elif state == TIMEOUT_STATE.TIMEOUT: + # 超时处理:关闭连接并重新连接 conf.dbmsConnector.close() conf.dbmsConnector.connect() elif output: + # 如果有缓存结果,显示提示信息 infoMsg = "resumed: %s..." % getUnicode(output, UNICODE_ENCODING)[:20] logger.info(infoMsg) + # 记录查询执行时间 threadData.lastQueryDuration = calculateDeltaSeconds(start) + # 处理返回结果 if not output: return output elif content: + # 如果需要返回内容 if output and isListLike(output): if len(output[0]) == 1: + # 如果结果只有一列,简化输出格式 output = [_[0] for _ in output] + # 转换为Unicode格式 retVal = getUnicode(output, noneToNull=True) + # 根据配置决定是否进行安全字符编码 return safecharencode(retVal) if kb.safeCharEncode else retVal else: + # 如果不需要返回内容,提取预期的布尔值 return extractExpectedValue(output, EXPECTED.BOOL) diff --git a/src/sqlmap-master/lib/request/dns.py b/src/sqlmap-master/lib/request/dns.py index 70db51f..2a84946 100644 --- a/src/sqlmap-master/lib/request/dns.py +++ b/src/sqlmap-master/lib/request/dns.py @@ -7,16 +7,17 @@ See the file 'LICENSE' for copying permission from __future__ import print_function -import binascii +import binascii # 用于二进制和ASCII转换 import os import re -import socket -import struct -import threading +import socket # 用于网络通信 +import struct # 用于处理字节串 +import threading # 用于多线程 import time class DNSQuery(object): """ + DNS查询解析类 >>> DNSQuery(b'|K\\x01 \\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x01\\x03www\\x06google\\x03com\\x00\\x00\\x01\\x00\\x01\\x00\\x00)\\x10\\x00\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\n\\x00\\x08O4|Np!\\x1d\\xb3')._query == b"www.google.com." True >>> DNSQuery(b'\\x00')._query == b"" @@ -24,12 +25,15 @@ class DNSQuery(object): """ def __init__(self, raw): - self._raw = raw - self._query = b"" + self._raw = raw # 原始DNS查询数据 + self._query = b"" # 解析后的域名查询字符串 try: - type_ = (ord(raw[2:3]) >> 3) & 15 # Opcode bits + # 从DNS报文中提取操作码(Opcode) + type_ = (ord(raw[2:3]) >> 3) & 15 + if type_ == 0: # 标准查询 + i = 12 # DNS报文头部长度为12字节 if type_ == 0: # Standard query i = 12 j = ord(raw[i:i + 1]) diff --git a/src/sqlmap-master/lib/request/httpshandler.py b/src/sqlmap-master/lib/request/httpshandler.py index c3af58f..ebeaca1 100644 --- a/src/sqlmap-master/lib/request/httpshandler.py +++ b/src/sqlmap-master/lib/request/httpshandler.py @@ -5,6 +5,7 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ +# 导入所需的标准库和第三方库 import re import socket @@ -20,6 +21,7 @@ from lib.core.settings import PYVERSION from thirdparty.six.moves import http_client as _http_client from thirdparty.six.moves import urllib as _urllib +# 尝试导入ssl模块,如果导入失败则ssl为None ssl = None try: import ssl as _ssl @@ -27,31 +29,38 @@ try: except ImportError: pass +# 获取所有可用的SSL/TLS协议版本,并存储在_protocols列表中 _protocols = filterNone(getattr(ssl, _, None) for _ in ("PROTOCOL_TLS_CLIENT", "PROTOCOL_TLSv1_2", "PROTOCOL_TLSv1_1", "PROTOCOL_TLSv1", "PROTOCOL_SSLv3", "PROTOCOL_SSLv23", "PROTOCOL_SSLv2")) +# 创建协议名称和值的映射字典 _lut = dict((getattr(ssl, _), _) for _ in dir(ssl) if _.startswith("PROTOCOL_")) +# 存储SSL上下文的字典 _contexts = {} class HTTPSConnection(_http_client.HTTPSConnection): """ - Connection class that enables usage of newer SSL protocols. + 自定义HTTPS连接类,支持使用较新的SSL协议。 + 继承自标准库的HTTPSConnection类。 - Reference: http://bugs.python.org/msg128686 + 参考: http://bugs.python.org/msg128686 - NOTE: use https://check-tls.akamaized.net/ to check if (e.g.) TLS/SNI is working properly + 注意: 使用 https://check-tls.akamaized.net/ 检查TLS/SNI是否正常工作 """ def __init__(self, *args, **kwargs): - # NOTE: Dirty patch for https://bugs.python.org/issue38251 / https://github.com/sqlmapproject/sqlmap/issues/4158 + # 修复Python bug: https://bugs.python.org/issue38251 if hasattr(ssl, "_create_default_https_context"): if None not in _contexts: _contexts[None] = ssl._create_default_https_context() kwargs["context"] = _contexts[None] + # 重试标志 self.retrying = False + # 调用父类初始化方法 _http_client.HTTPSConnection.__init__(self, *args, **kwargs) def connect(self): + # 创建socket连接的内部函数 def create_sock(): sock = socket.create_connection((self.host, self.port), self.timeout) if getattr(self, "_tunnel_host", None): @@ -61,31 +70,33 @@ class HTTPSConnection(_http_client.HTTPSConnection): success = False - # Reference(s): https://docs.python.org/2/library/ssl.html#ssl.SSLContext - # https://www.mnot.net/blog/2014/12/27/python_2_and_tls_sni + # 使用SSLContext方式建立SSL连接(Python 2.7.9及更高版本支持) if hasattr(ssl, "SSLContext"): for protocol in (_ for _ in _protocols if _ >= ssl.PROTOCOL_TLSv1): try: sock = create_sock() if protocol not in _contexts: + # 创建SSL上下文 _contexts[protocol] = ssl.SSLContext(protocol) - # Disable certificate and hostname validation enabled by default with PROTOCOL_TLS_CLIENT + # 禁用证书和主机名验证 _contexts[protocol].check_hostname = False _contexts[protocol].verify_mode = ssl.CERT_NONE + # 如果提供了证书和密钥文件,则加载它们 if getattr(self, "cert_file", None) and getattr(self, "key_file", None): _contexts[protocol].load_cert_chain(certfile=self.cert_file, keyfile=self.key_file) try: - # Reference(s): https://askubuntu.com/a/1263098 - # https://askubuntu.com/a/1250807 + # 设置加密套件 _contexts[protocol].set_ciphers("DEFAULT@SECLEVEL=1") except (ssl.SSLError, AttributeError): pass + # 包装socket并进行SSL握手 result = _contexts[protocol].wrap_socket(sock, do_handshake_on_connect=True, server_hostname=self.host if re.search(r"\A[\d.]+\Z", self.host or "") is None else None) if result: success = True self.sock = result + # 将成功的协议移到列表开头 _protocols.remove(protocol) _protocols.insert(0, protocol) break @@ -95,6 +106,7 @@ class HTTPSConnection(_http_client.HTTPSConnection): self._tunnel_host = None logger.debug("SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex))) + # 使用旧式ssl.wrap_socket方式建立SSL连接(用于较老版本的Python) elif hasattr(ssl, "wrap_socket"): for protocol in _protocols: try: @@ -112,12 +124,14 @@ class HTTPSConnection(_http_client.HTTPSConnection): self._tunnel_host = None logger.debug("SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex))) + # 如果所有协议都连接失败 if not success: errMsg = "can't establish SSL connection" - # Reference: https://docs.python.org/2/library/ssl.html + # 对于Python 2.7.9之前的版本,建议升级 if LooseVersion(PYVERSION) < LooseVersion("2.7.9"): errMsg += " (please retry with Python >= 2.7.9)" + # 如果之前有成功连接过且未在重试,则进行重试 if kb.sslSuccess and not self.retrying: self.retrying = True @@ -134,5 +148,9 @@ class HTTPSConnection(_http_client.HTTPSConnection): kb.sslSuccess = True class HTTPSHandler(_urllib.request.HTTPSHandler): + """ + HTTPS处理器类,用于处理HTTPS请求 + """ def https_open(self, req): + # 根据是否有ssl模块选择合适的连接类来处理请求 return self.do_open(HTTPSConnection if ssl else _http_client.HTTPSConnection, req) diff --git a/src/sqlmap-master/lib/request/methodrequest.py b/src/sqlmap-master/lib/request/methodrequest.py index f1b97b4..5af2ecf 100644 --- a/src/sqlmap-master/lib/request/methodrequest.py +++ b/src/sqlmap-master/lib/request/methodrequest.py @@ -5,16 +5,27 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ +# 导入getText函数,用于文本转换 from lib.core.convert import getText +# 导入urllib库并重命名为_urllib,用于处理URL请求 from thirdparty.six.moves import urllib as _urllib class MethodRequest(_urllib.request.Request): """ - Used to create HEAD/PUT/DELETE/... requests with urllib + 继承自urllib.request.Request类 + 用于创建HTTP请求(HEAD/PUT/DELETE等)的自定义请求类 """ def set_method(self, method): - self.method = getText(method.upper()) # Dirty hack for Python3 (may it rot in hell!) + # 设置HTTP请求方法 + # 参数method: 字符串类型,表示HTTP方法(如GET、POST、HEAD等) + # getText()将输入转换为文本格式 + # upper()将文本转为大写 + self.method = getText(method.upper()) # Python3兼容性处理 def get_method(self): + # 获取HTTP请求方法 + # getattr用于获取对象的属性 + # 如果self.method存在就返回它 + # 否则返回父类Request默认的get_method()方法的结果 return getattr(self, 'method', _urllib.request.Request.get_method(self)) diff --git a/src/sqlmap-master/lib/request/pkihandler.py b/src/sqlmap-master/lib/request/pkihandler.py index 712e8aa..e7ab4e8 100644 --- a/src/sqlmap-master/lib/request/pkihandler.py +++ b/src/sqlmap-master/lib/request/pkihandler.py @@ -5,25 +5,57 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ -from lib.core.data import conf -from lib.core.common import getSafeExString -from lib.core.exception import SqlmapConnectionException -from thirdparty.six.moves import http_client as _http_client -from thirdparty.six.moves import urllib as _urllib +# 导入所需的模块 +from lib.core.data import conf # 导入配置信息 +from lib.core.common import getSafeExString # 导入安全字符串处理函数 +from lib.core.exception import SqlmapConnectionException # 导入SQL注入连接异常类 +from thirdparty.six.moves import http_client as _http_client # 导入HTTP客户端 +from thirdparty.six.moves import urllib as _urllib # 导入URL处理库 class HTTPSPKIAuthHandler(_urllib.request.HTTPSHandler): + """ + HTTPS PKI认证处理器类 + 继承自urllib的HTTPSHandler类,用于处理带客户端证书的HTTPS请求 + """ def __init__(self, auth_file): - _urllib.request.HTTPSHandler.__init__(self) - self.auth_file = auth_file + """ + 初始化函数 + 参数: + auth_file: 认证文件路径,包含客户端证书和私钥 + """ + _urllib.request.HTTPSHandler.__init__(self) # 调用父类初始化 + self.auth_file = auth_file # 保存认证文件路径 def https_open(self, req): + """ + 处理HTTPS请求的方法 + 参数: + req: HTTPS请求对象 + 返回: + 处理后的HTTPS连接 + """ return self.do_open(self.getConnection, req) def getConnection(self, host, timeout=None): + """ + 建立HTTPS连接的方法 + 参数: + host: 目标主机 + timeout: 超时时间,默认为None + 返回: + HTTPS连接对象 + 异常: + SqlmapConnectionException: 连接异常 + """ try: - # Reference: https://docs.python.org/2/library/ssl.html#ssl.SSLContext.load_cert_chain - return _http_client.HTTPSConnection(host, cert_file=self.auth_file, key_file=self.auth_file, timeout=conf.timeout) + # 创建带客户端证书的HTTPS连接 + # 参考文档: https://docs.python.org/2/library/ssl.html#ssl.SSLContext.load_cert_chain + return _http_client.HTTPSConnection(host, + cert_file=self.auth_file, # 证书文件 + key_file=self.auth_file, # 私钥文件 + timeout=conf.timeout) # 超时设置 except IOError as ex: + # 如果出现IO错误(比如证书文件无法读取等) errMsg = "error occurred while using key " errMsg += "file '%s' ('%s')" % (self.auth_file, getSafeExString(ex)) - raise SqlmapConnectionException(errMsg) + raise SqlmapConnectionException(errMsg) # 抛出SQL注入连接异常 diff --git a/src/sqlmap-master/lib/request/rangehandler.py b/src/sqlmap-master/lib/request/rangehandler.py index eebebc1..0dd8634 100644 --- a/src/sqlmap-master/lib/request/rangehandler.py +++ b/src/sqlmap-master/lib/request/rangehandler.py @@ -5,25 +5,44 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ +# 导入自定义的SQL映射连接异常类 from lib.core.exception import SqlmapConnectionException +# 导入urllib库并重命名为_urllib,用于处理HTTP请求 from thirdparty.six.moves import urllib as _urllib class HTTPRangeHandler(_urllib.request.BaseHandler): """ - Handler that enables HTTP Range headers. - - Reference: http://stackoverflow.com/questions/1971240/python-seek-on-remote-file + 处理HTTP Range头部的处理器类。 + Range头部允许客户端请求资源的部分内容而不是整个资源。 + + 参考文档: http://stackoverflow.com/questions/1971240/python-seek-on-remote-file """ def http_error_206(self, req, fp, code, msg, hdrs): - # 206 Partial Content Response + """ + 处理206状态码(部分内容响应) + 参数说明: + req: 原始请求对象 + fp: 类文件对象,包含响应内容 + code: HTTP状态码(206) + msg: 响应消息 + hdrs: 响应头部信息 + """ + # 创建并返回一个包含部分内容的响应对象 r = _urllib.response.addinfourl(fp, hdrs, req.get_full_url()) - r.code = code - r.msg = msg + r.code = code # 设置响应状态码 + r.msg = msg # 设置响应消息 return r def http_error_416(self, req, fp, code, msg, hdrs): - # HTTP's Range Not Satisfiable error + """ + 处理416状态码(请求范围不满足) + 当请求的范围超出资源的实际范围时会触发此错误 + + 参数说明与http_error_206相同 + """ + # 构建错误信息 errMsg = "there was a problem while connecting " errMsg += "target ('406 - Range Not Satisfiable')" + # 抛出SQL映射连接异常 raise SqlmapConnectionException(errMsg) diff --git a/src/sqlmap-master/lib/request/redirecthandler.py b/src/sqlmap-master/lib/request/redirecthandler.py index 726106b..005f496 100644 --- a/src/sqlmap-master/lib/request/redirecthandler.py +++ b/src/sqlmap-master/lib/request/redirecthandler.py @@ -5,11 +5,13 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ +# 导入所需的标准库和第三方库 import io import re import time import types +# 导入自定义工具函数 from lib.core.common import getHostHeader from lib.core.common import getSafeExString from lib.core.common import logHTTPTraffic @@ -36,7 +38,17 @@ from thirdparty import six from thirdparty.six.moves import urllib as _urllib class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler): + """ + 智能重定向处理器类,继承自urllib的HTTPRedirectHandler + 用于处理HTTP重定向响应(301/302/303/307等) + """ + def _get_header_redirect(self, headers): + """ + 从响应头中获取重定向URL + :param headers: HTTP响应头 + :return: 重定向URL或None + """ retVal = None if headers: @@ -48,13 +60,21 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler): return retVal def _ask_redirect_choice(self, redcode, redurl, method): + """ + 询问用户是否要跟随重定向 + :param redcode: 重定向状态码 + :param redurl: 重定向URL + :param method: HTTP请求方法 + """ with kb.locks.redirect: + # 如果还没有做出重定向选择 if kb.choices.redirect is None: msg = "got a %d redirect to " % redcode msg += "'%s'. Do you want to follow? [Y/n] " % redurl kb.choices.redirect = REDIRECTION.YES if readInput(msg, default='Y', boolean=True) else REDIRECTION.NO + # 如果是POST请求导致的重定向,询问是否要重发POST数据 if kb.choices.redirect == REDIRECTION.YES and method == HTTPMETHOD.POST and kb.resendPostOnRedirect is None: msg = "redirect is a result of a " msg += "POST request. Do you want to " @@ -67,31 +87,49 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler): self.redirect_request = self._redirect_request def _redirect_request(self, req, fp, code, msg, headers, newurl): + """ + 创建重定向请求 + :return: 新的Request对象 + """ return _urllib.request.Request(newurl.replace(' ', '%20'), data=req.data, headers=req.headers, origin_req_host=req.get_origin_req_host() if hasattr(req, "get_origin_req_host") else req.origin_req_host) def http_error_302(self, req, fp, code, msg, headers): + """ + 处理302重定向响应 + :param req: 原始请求 + :param fp: 响应文件对象 + :param code: 状态码 + :param msg: 状态消息 + :param headers: 响应头 + :return: 处理后的响应对象 + """ start = time.time() content = None forceRedirect = False + # 如果未忽略重定向,则获取重定向URL redurl = self._get_header_redirect(headers) if not conf.ignoreRedirects else None + # 读取响应内容 try: content = fp.read(MAX_CONNECTION_TOTAL_SIZE) - except: # e.g. IncompleteRead + except: # 处理不完整读取 content = b"" finally: if content: - try: # try to write it back to the read buffer so we could reuse it in further steps + try: # 尝试将内容写回读取缓冲区以便后续使用 fp.fp._rbuf.truncate(0) fp.fp._rbuf.write(content) except: pass + # 解码响应内容 content = decodePage(content, headers.get(HTTP_HEADER.CONTENT_ENCODING), headers.get(HTTP_HEADER.CONTENT_TYPE)) + # 记录重定向信息 threadData = getCurrentThreadData() threadData.lastRedirectMsg = (threadData.lastRequestUID, content) + # 构建重定向日志消息 redirectMsg = "HTTP redirect " redirectMsg += "[#%d] (%d %s):\r\n" % (threadData.lastRequestUID, code, getUnicode(msg)) @@ -104,31 +142,39 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler): if content: redirectMsg += "\r\n\r\n%s" % getUnicode(content[:MAX_CONNECTION_READ_SIZE]) + # 记录HTTP流量日志 logHTTPTraffic(threadData.lastRequestMsg, redirectMsg, start, time.time()) logger.log(CUSTOM_LOGGING.TRAFFIC_IN, redirectMsg) if redurl: try: + # 如果重定向URL是相对路径,转换为绝对路径 if not _urllib.parse.urlsplit(redurl).netloc: redurl = _urllib.parse.urljoin(req.get_full_url(), redurl) + # 检查是否存在无限重定向循环 self._infinite_loop_check(req) if conf.scope: + # 检查重定向URL是否在指定范围内 if not re.search(conf.scope, redurl, re.I): redurl = None else: forceRedirect = True else: + # 询问用户是否跟随重定向 self._ask_redirect_choice(code, redurl, req.get_method()) except ValueError: redurl = None result = fp if redurl and (kb.choices.redirect == REDIRECTION.YES or forceRedirect): + # 解析响应 parseResponse(content, headers) + # 更新请求头中的Host req.headers[HTTP_HEADER.HOST] = getHostHeader(redurl) if headers and HTTP_HEADER.SET_COOKIE in headers: + # 处理Cookie cookies = dict() delimiter = conf.cookieDel or DEFAULT_COOKIE_DELIMITER last = None @@ -145,11 +191,12 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler): req.headers[HTTP_HEADER.COOKIE] = delimiter.join("%s=%s" % (key, cookies[key]) for key in cookies) try: + # 执行重定向请求 result = _urllib.request.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers) except _urllib.error.HTTPError as ex: result = ex - # Dirty hack for https://github.com/sqlmapproject/sqlmap/issues/4046 + # 处理特殊情况的hack try: hasattr(result, "read") except KeyError: @@ -157,7 +204,6 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler): pass result = _() - # Dirty hack for http://bugs.python.org/issue15701 try: result.info() except AttributeError: @@ -169,7 +215,7 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler): if not hasattr(result, "read"): def _(self, length=None): try: - retVal = getSafeExString(ex) # Note: pyflakes mistakenly marks 'ex' as undefined (NOTE: tested in both Python2 and Python3) + retVal = getSafeExString(ex) except: retVal = "" return getBytes(retVal) @@ -188,15 +234,23 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler): else: result = fp + # 记录最后的重定向URL threadData.lastRedirectURL = (threadData.lastRequestUID, redurl) + # 设置结果属性 result.redcode = code result.redurl = getUnicode(redurl) if six.PY3 else redurl return result + # 其他重定向状态码使用相同的处理方法 http_error_301 = http_error_303 = http_error_307 = http_error_302 def _infinite_loop_check(self, req): + """ + 检查是否存在无限重定向循环 + :param req: 请求对象 + :raises: SqlmapConnectionException 如果检测到无限循环 + """ if hasattr(req, 'redirect_dict') and (req.redirect_dict.get(req.get_full_url(), 0) >= MAX_SINGLE_URL_REDIRECTIONS or len(req.redirect_dict) >= MAX_TOTAL_REDIRECTIONS): errMsg = "infinite redirect loop detected (%s). " % ", ".join(item for item in req.redirect_dict.keys()) errMsg += "Please check all provided parameters and/or provide missing ones" diff --git a/src/sqlmap-master/lib/request/templates.py b/src/sqlmap-master/lib/request/templates.py index 05fecc2..a20313b 100644 --- a/src/sqlmap-master/lib/request/templates.py +++ b/src/sqlmap-master/lib/request/templates.py @@ -5,17 +5,59 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ +# 导入必要的模块 +# kb (Knowledge Base) 是一个全局对象,用于存储程序运行时的各种状态和数据 +# 比如原始页面内容、错误状态、页面模板缓存等信息都存储在这里 from lib.core.data import kb + +# 从connect模块导入Request类并重命名为Request +# Request类用于发送HTTP请求,是与目标网站交互的核心类 from lib.request.connect import Connect as Request def getPageTemplate(payload, place): + """ + 获取页面模板的函数 + + 这个函数的主要作用是: + 1. 根据给定的SQL注入payload和注入位置获取目标页面 + 2. 将页面内容缓存起来避免重复请求 + 3. 返回页面内容和错误状态信息 + + 参数说明: + payload: SQL注入的载荷,即要注入的SQL代码 + place: 注入点的位置,表示在请求中的哪个位置进行注入 + (比如URL参数、POST数据、Cookie等) + + 返回值: + 返回一个包含两个元素的元组: + 1. 页面内容 - 可能是原始页面或注入后的页面 + 2. 错误状态 - 表示页面解析是否出现错误 + """ + # 初始化返回值 + # kb.originalPage 存储了未注入时的原始页面内容 + # kb.errorIsNone 表示错误检查的状态 retVal = (kb.originalPage, kb.errorIsNone) + # 只有当payload和place都不为空时才执行注入操作 if payload and place: + # 检查这个payload和place的组合是否已经在缓存中 + # kb.pageTemplates是一个字典,用于缓存不同注入组合的结果 if (payload, place) not in kb.pageTemplates: + # 如果没有缓存,则发送新的请求 + # Request.queryPage方法用于发送带有注入payload的请求 + # content=True 表示需要返回页面内容 + # raise404=False 表示遇到404错误时不抛出异常 + # 返回值中的page是页面内容,其他两个值用下划线忽略 page, _, _ = Request.queryPage(payload, place, content=True, raise404=False) + + # 将结果存入缓存 + # page 是获取到的页面内容 + # kb.lastParserStatus is None 表示页面解析是否成功 + # (None表示解析成功,非None表示解析出错) kb.pageTemplates[(payload, place)] = (page, kb.lastParserStatus is None) + # 从缓存中获取之前存储的结果 retVal = kb.pageTemplates[(payload, place)] + # 返回页面内容和错误状态 return retVal