add comments to request

pull/3/head
wang 3 months ago
parent 129d83dfba
commit 33945c2dbd

@ -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

@ -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("&quot", "")
# 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"<html></html>")
'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"<html>foo&amp;bar</html>", None, "text/html; charset=utf-8"))
'<html>foo&bar</html>'
>>> getText(decodePage(b"&#x9;", 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("<l", page[-4:])[0] # Reference: http://pydoc.org/get.cgi/usr/local/lib/python2.5/gzip.py
if size > MAX_CONNECTION_TOTAL_SIZE:
raise Exception("size too large")
page = data.read()
except Exception as ex:
if b"<html" not in page: # in some cases, invalid "Content-Encoding" appears for plain HTML (should be ignored)
errMsg = "detected invalid data for declared content "
errMsg += "encoding '%s' ('%s')" % (contentEncoding, getSafeExString(ex))
singleTimeLogMessage(errMsg, logging.ERROR)
warnMsg = "turning off page compression"
singleTimeWarnMessage(warnMsg)
kb.pageCompress = False
raise SqlmapCompressionException
if not conf.encoding:
httpCharset, metaCharset = None, None
# Reference: http://stackoverflow.com/questions/1020892/python-urllib2-read-to-unicode
if contentType.find("charset=") != -1:
httpCharset = checkCharEncoding(contentType.split("charset=")[-1])
metaCharset = checkCharEncoding(extractRegexResult(META_CHARSET_REGEX, page))
if (any((httpCharset, metaCharset)) and (not all((httpCharset, metaCharset)) or isinstance(page, six.binary_type) and all(_ in PRINTABLE_BYTES for _ in page))) or (httpCharset == metaCharset and all((httpCharset, metaCharset))):
kb.pageEncoding = httpCharset or metaCharset # Reference: http://bytes.com/topic/html-css/answers/154758-http-equiv-vs-true-header-has-precedence
debugMsg = "declared web page charset '%s'" % kb.pageEncoding
singleTimeLogMessage(debugMsg, logging.DEBUG, debugMsg)
else:
kb.pageEncoding = None
else:
kb.pageEncoding = conf.encoding
# can't do for all responses because we need to support binary files too
if isinstance(page, six.binary_type) and "text/" in contentType:
if not kb.disableHtmlDecoding:
# e.g. &#x9;&#195;&#235;&#224;&#226;&#224;
if b"&#" in page:
page = re.sub(b"&#x([0-9a-f]{1,2});", lambda _: decodeHex(_.group(1) if len(_.group(1)) == 2 else b"0%s" % _.group(1)), page)
page = re.sub(b"&#(\\d{1,3});", lambda _: six.int2byte(int(_.group(1))) if int(_.group(1)) < 256 else _.group(0), page)
# e.g. %20%28%29
if percentDecode:
if b"%" in page:
page = re.sub(b"%([0-9a-f]{2})", lambda _: decodeHex(_.group(1)), page)
page = re.sub(b"%([0-9A-F]{2})", lambda _: decodeHex(_.group(1)), page) # Note: %DeepSee_SQL in CACHE
# e.g. &amp;
page = re.sub(b"&([^;]+);", lambda _: six.int2byte(HTML_ENTITIES[getText(_.group(1))]) if HTML_ENTITIES.get(getText(_.group(1)), 256) < 256 else _.group(0), page)
kb.pageEncoding = kb.pageEncoding or checkCharEncoding(getHeuristicCharEncoding(page))
if (kb.pageEncoding or "").lower() == "utf-8-sig":
kb.pageEncoding = "utf-8"
if page and page.startswith(b"\xef\xbb\xbf"): # Reference: https://docs.python.org/2/library/codecs.html (Note: noticed problems when "utf-8-sig" is left to Python for handling)
page = page[3:]
page = getUnicode(page, kb.pageEncoding)
# e.g. &#8217;&#8230;&#8482;
if "&#" in page:
def _(match):
retVal = match.group(0)
try:
retVal = _unichr(int(match.group(1)))
except (ValueError, OverflowError):
pass
return retVal
page = re.sub(r"&#(\d+);", _, page)
# e.g. &zeta;
page = re.sub(r"&([^;]+);", lambda _: _unichr(HTML_ENTITIES[_.group(1)]) if HTML_ENTITIES.get(_.group(1), 0) > 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)<form.+?</form>", page):
if re.search(r"(?i)captcha", match.group(0)):
kb.captchaDetected = True
break
if re.search(r"<meta[^>]+\brefresh\b[^>]+\bcaptcha\b", page):
kb.captchaDetected = True
if kb.captchaDetected:
warnMsg = "potential CAPTCHA protection mechanism detected"
if re.search(r"(?i)<title>[^<]*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)

@ -5,34 +5,63 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入urllib库并重命名为_urllib
from thirdparty.six.moves import urllib as _urllib from thirdparty.six.moves import urllib as _urllib
class SmartHTTPBasicAuthHandler(_urllib.request.HTTPBasicAuthHandler): class SmartHTTPBasicAuthHandler(_urllib.request.HTTPBasicAuthHandler):
""" """
Reference: http://selenic.com/hg/rev/6c51a5056020 参考: http://selenic.com/hg/rev/6c51a5056020
Fix for a: http://bugs.python.org/issue8797 修复Bug: http://bugs.python.org/issue8797
这是一个处理HTTP基础认证的智能处理器类,继承自HTTPBasicAuthHandler
主要用于处理认证重试的逻辑,避免无限循环重试的问题
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# 调用父类的初始化方法
_urllib.request.HTTPBasicAuthHandler.__init__(self, *args, **kwargs) _urllib.request.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
# 创建一个集合用于存储已重试过的请求
self.retried_req = set() self.retried_req = set()
# 重试计数器初始化为0
self.retried_count = 0 self.retried_count = 0
def reset_retry_count(self): 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 pass
def http_error_auth_reqed(self, auth_header, host, req, headers): 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: if hash(req) not in self.retried_req:
# 将请求添加到已重试集合中
self.retried_req.add(hash(req)) self.retried_req.add(hash(req))
# 重置重试计数
self.retried_count = 0 self.retried_count = 0
else: else:
# 如果重试次数超过5次
if self.retried_count > 5: if self.retried_count > 5:
# 抛出HTTP 401认证失败错误
raise _urllib.error.HTTPError(req.get_full_url(), 401, "basic auth failed", headers, None) raise _urllib.error.HTTPError(req.get_full_url(), 401, "basic auth failed", headers, None)
else: else:
# 增加重试计数
self.retried_count += 1 self.retried_count += 1
# 调用父类的错误处理方法
return _urllib.request.HTTPBasicAuthHandler.http_error_auth_reqed(self, auth_header, host, req, headers) return _urllib.request.HTTPBasicAuthHandler.http_error_auth_reqed(self, auth_header, host, req, headers)

@ -5,37 +5,63 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
from lib.core.data import conf # 导入必要的模块
from lib.core.enums import HTTP_HEADER from lib.core.data import conf # 导入配置信息
from thirdparty.six.moves import urllib as _urllib 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): 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): 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 host = request.get_host() if hasattr(request, "get_host") else request.host
if not host: if not host:
# 如果没有指定主机名则抛出异常,因为HTTP请求必须知道发送到哪个主机
raise _urllib.error.URLError("no host given") raise _urllib.error.URLError("no host given")
if request.data is not None: # POST if request.data is not None: # 如果是POST请求(包含数据)
data = request.data data = request.data
# 如果没有设置Content-Type头,则设置为默认的表单格式
# application/x-www-form-urlencoded 是最常见的POST数据格式
if not request.has_header(HTTP_HEADER.CONTENT_TYPE): if not request.has_header(HTTP_HEADER.CONTENT_TYPE):
request.add_unredirected_header(HTTP_HEADER.CONTENT_TYPE, "application/x-www-form-urlencoded") 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: if not request.has_header(HTTP_HEADER.CONTENT_LENGTH) and not conf.chunked:
request.add_unredirected_header(HTTP_HEADER.CONTENT_LENGTH, "%d" % len(data)) request.add_unredirected_header(HTTP_HEADER.CONTENT_LENGTH, "%d" % len(data))
# 设置用于选择的主机名
sel_host = host sel_host = host
# 如果使用了代理,则从请求选择器中解析出实际的主机名
# 代理请求时,请求行中包含完整的URL,需要从中提取出真实的主机名
if request.has_proxy(): if request.has_proxy():
sel_host = _urllib.parse.urlsplit(request.get_selector()).netloc sel_host = _urllib.parse.urlsplit(request.get_selector()).netloc
# 如果没有设置Host头,则添加Host头
# Host头是HTTP/1.1必需的头部,用于指定服务器的域名和端口号
if not request.has_header(HTTP_HEADER.HOST): if not request.has_header(HTTP_HEADER.HOST):
request.add_unredirected_header(HTTP_HEADER.HOST, sel_host) request.add_unredirected_header(HTTP_HEADER.HOST, sel_host)
# 遍历父类中定义的额外头部信息
for name, value in self.parent.addheaders: for name, value in self.parent.addheaders:
name = name.capitalize() name = name.capitalize() # 将头部名称首字母大写,符合HTTP协议规范
# 如果请求中没有该头部,则添加到请求中
# 这确保了自定义头部不会覆盖已有的头部
if not request.has_header(name): if not request.has_header(name):
request.add_unredirected_header(name, value) request.add_unredirected_header(name, value)
return request return request
# 将_http_request方法赋值给http_request,使其成为标准的处理方法
# 这是一种Python的惯用法,允许在保留原始方法的同时提供一个公开的接口
http_request = _http_request http_request = _http_request

@ -5,10 +5,12 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入未来版本的除法运算
from __future__ import division from __future__ import division
import re import re
# 导入所需的工具函数
from lib.core.common import extractRegexResult from lib.core.common import extractRegexResult
from lib.core.common import getFilteredPageContent from lib.core.common import getFilteredPageContent
from lib.core.common import listToStrValue from lib.core.common import listToStrValue
@ -35,15 +37,28 @@ from lib.core.threads import getCurrentThreadData
from thirdparty import six from thirdparty import six
def comparison(page, headers, code=None, getRatioValue=False, pageLength=None): 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) _ = _adjust(_comparison(page, headers, code, getRatioValue, pageLength), getRatioValue)
return _ return _
def _adjust(condition, getRatioValue): def _adjust(condition, getRatioValue):
"""
调整比较结果
:param condition: 原始比较结果
:param getRatioValue: 是否返回相似度值
:return: 调整后的结果
"""
if not any((conf.string, conf.notString, conf.regexp, conf.code)): 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 # 与原始PAYLOAD.WHERE.NEGATIVE响应"不同"的内容被认为是True
# applied as that what is by user considered as True is that what is returned by the comparison mechanism
# itself
retVal = not condition if kb.negativeLogic and condition is not None and not getRatioValue else condition retVal = not condition if kb.negativeLogic and condition is not None and not getRatioValue else condition
else: else:
retVal = condition if not getRatioValue else (MAX_RATIO if condition else MIN_RATIO) retVal = condition if not getRatioValue else (MAX_RATIO if condition else MIN_RATIO)
@ -51,24 +66,37 @@ def _adjust(condition, getRatioValue):
return retVal return retVal
def _comparison(page, headers, code, getRatioValue, pageLength): def _comparison(page, headers, code, getRatioValue, pageLength):
"""
核心比较函数
:param page: 要比较的页面内容
:param headers: HTTP响应头
:param code: HTTP状态码
:param getRatioValue: 是否返回相似度值
:param pageLength: 页面长度
:return: 比较结果
"""
# 获取当前线程数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
# 测试模式下保存比较数据
if kb.testMode: if kb.testMode:
threadData.lastComparisonHeaders = listToStrValue(_ for _ in headers.headers if not _.startswith("%s:" % URI_HTTP_HEADER)) if headers else "" threadData.lastComparisonHeaders = listToStrValue(_ for _ in headers.headers if not _.startswith("%s:" % URI_HTTP_HEADER)) if headers else ""
threadData.lastComparisonPage = page threadData.lastComparisonPage = page
threadData.lastComparisonCode = code threadData.lastComparisonCode = code
# 页面内容和长度都为空时返回None
if page is None and pageLength is None: if page is None and pageLength is None:
return None return None
# 处理字符串匹配情况
if any((conf.string, conf.notString, conf.regexp)): 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) 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: if conf.string:
return conf.string in rawResponse return conf.string in rawResponse
# String to match in page when the query is False # 检查页面是否不包含指定的字符串
if conf.notString: if conf.notString:
if conf.notString in rawResponse: if conf.notString in rawResponse:
return False return False
@ -78,24 +106,25 @@ def _comparison(page, headers, code, getRatioValue, pageLength):
else: else:
return True return True
# Regular expression to match in page when the query is True and/or valid # 使用正则表达式匹配页面内容
if conf.regexp: if conf.regexp:
return re.search(conf.regexp, rawResponse, re.I | re.M) is not None 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: if conf.code:
return conf.code == code return conf.code == code
# 初始化序列匹配器
seqMatcher = threadData.seqMatcher seqMatcher = threadData.seqMatcher
seqMatcher.set_seq1(kb.pageTemplate) seqMatcher.set_seq1(kb.pageTemplate)
if page: if page:
# In case of an DBMS error page return None # 处理数据库错误页面
if kb.errorIsNone and (wasLastResponseDBMSError() or wasLastResponseHTTPError()) and not kb.negativeLogic: if kb.errorIsNone and (wasLastResponseDBMSError() or wasLastResponseHTTPError()) and not kb.negativeLogic:
if not (wasLastResponseHTTPError() and getLastRequestHTTPError() in (conf.ignoreCode or [])): if not (wasLastResponseHTTPError() and getLastRequestHTTPError() in (conf.ignoreCode or [])):
return None return None
# Dynamic content lines to be excluded before comparison # 移除动态内容后再比较
if not kb.nullConnection: if not kb.nullConnection:
page = removeDynamicContent(page) page = removeDynamicContent(page)
seqMatcher.set_seq1(removeDynamicContent(kb.pageTemplate)) seqMatcher.set_seq1(removeDynamicContent(kb.pageTemplate))
@ -103,6 +132,7 @@ def _comparison(page, headers, code, getRatioValue, pageLength):
if not pageLength: if not pageLength:
pageLength = len(page) pageLength = len(page)
# 处理空连接情况
if kb.nullConnection and pageLength: if kb.nullConnection and pageLength:
if not seqMatcher.a: if not seqMatcher.a:
errMsg = "problem occurred while retrieving original page content " 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" errMsg += "and if the problem persists turn off any optimization switches"
raise SqlmapNoneDataException(errMsg) raise SqlmapNoneDataException(errMsg)
# 计算页面长度比率
ratio = 1. * pageLength / len(seqMatcher.a) ratio = 1. * pageLength / len(seqMatcher.a)
if ratio > 1.: if ratio > 1.:
ratio = 1. / ratio ratio = 1. / ratio
else: 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): if isinstance(seqMatcher.a, six.binary_type) and isinstance(page, six.text_type):
page = getBytes(page, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore") page = getBytes(page, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore")
elif isinstance(seqMatcher.a, six.text_type) and isinstance(page, six.binary_type): 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") seqMatcher.a = getBytes(seqMatcher.a, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore")
# 处理各种比较情况
if any(_ is None for _ in (page, seqMatcher.a)): if any(_ is None for _ in (page, seqMatcher.a)):
return None return None
elif seqMatcher.a and page and seqMatcher.a == page: elif seqMatcher.a and page and seqMatcher.a == page:
@ -136,6 +167,7 @@ def _comparison(page, headers, code, getRatioValue, pageLength):
else: else:
seq1, seq2 = None, None seq1, seq2 = None, None
# 根据配置选择比较内容
if conf.titles: if conf.titles:
seq1 = extractRegexResult(HTML_TITLE_REGEX, seqMatcher.a) seq1 = extractRegexResult(HTML_TITLE_REGEX, seqMatcher.a)
seq2 = extractRegexResult(HTML_TITLE_REGEX, page) 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: if seq1 is None or seq2 is None:
return None return None
# 移除反射值标记
seq1 = seq1.replace(REFLECTED_VALUE_MARKER, "") seq1 = seq1.replace(REFLECTED_VALUE_MARKER, "")
seq2 = seq2.replace(REFLECTED_VALUE_MARKER, "") seq2 = seq2.replace(REFLECTED_VALUE_MARKER, "")
# 处理高度动态内容
if kb.heavilyDynamic: if kb.heavilyDynamic:
seq1 = seq1.split("\n") seq1 = seq1.split("\n")
seq2 = seq2.split("\n") seq2 = seq2.split("\n")
key = None key = None
else: else:
key = (hash(seq1), hash(seq2)) key = (hash(seq1), hash(seq2))
@ -160,6 +193,7 @@ def _comparison(page, headers, code, getRatioValue, pageLength):
seqMatcher.set_seq1(seq1) seqMatcher.set_seq1(seq1)
seqMatcher.set_seq2(seq2) seqMatcher.set_seq2(seq2)
# 使用缓存提高性能
if key in kb.cache.comparison: if key in kb.cache.comparison:
ratio = kb.cache.comparison[key] ratio = kb.cache.comparison[key]
else: else:
@ -168,8 +202,7 @@ def _comparison(page, headers, code, getRatioValue, pageLength):
if key: if key:
kb.cache.comparison[key] = ratio 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 kb.matchRatio is None:
if ratio >= LOWER_RATIO_BOUND and ratio <= UPPER_RATIO_BOUND: if ratio >= LOWER_RATIO_BOUND and ratio <= UPPER_RATIO_BOUND:
kb.matchRatio = ratio kb.matchRatio = ratio
@ -178,19 +211,14 @@ def _comparison(page, headers, code, getRatioValue, pageLength):
if kb.testMode: if kb.testMode:
threadData.lastComparisonRatio = ratio threadData.lastComparisonRatio = ratio
# If it has been requested to return the ratio and not a comparison # 根据不同情况返回结果
# response
if getRatioValue: if getRatioValue:
return ratio return ratio
elif ratio > UPPER_RATIO_BOUND: elif ratio > UPPER_RATIO_BOUND:
return True return True
elif ratio < LOWER_RATIO_BOUND: elif ratio < LOWER_RATIO_BOUND:
return False return False
elif kb.matchRatio is None: elif kb.matchRatio is None:
return None return None
else: else:
return (ratio - kb.matchRatio) > DIFF_TOLERANCE return (ratio - kb.matchRatio) > DIFF_TOLERANCE

@ -5,6 +5,7 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的标准库
import binascii import binascii
import inspect import inspect
import logging import logging
@ -18,6 +19,7 @@ import sys
import time import time
import traceback import traceback
# 尝试导入websocket库,如果不存在则定义一个简单的异常类
try: try:
import websocket import websocket
from websocket import WebSocketException from websocket import WebSocketException
@ -25,6 +27,7 @@ except ImportError:
class WebSocketException(Exception): class WebSocketException(Exception):
pass pass
# 导入sqlmap自定义的库和工具函数
from lib.core.agent import agent from lib.core.agent import agent
from lib.core.common import asciifyUrl from lib.core.common import asciifyUrl
from lib.core.common import calculateDeltaSeconds from lib.core.common import calculateDeltaSeconds
@ -146,13 +149,18 @@ from thirdparty.socks.socks import ProxyError
class Connect(object): class Connect(object):
""" """
This class defines methods used to perform HTTP requests 这个类定义了用于执行HTTP请求的方法
""" """
@staticmethod @staticmethod
def _getPageProxy(**kwargs): def _getPageProxy(**kwargs):
"""
代理方法,用于处理页面请求
检查递归深度并调用getPage方法
"""
try: 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" warnMsg = "unable to connect to the target URL"
raise SqlmapConnectionException(warnMsg) raise SqlmapConnectionException(warnMsg)
except (TypeError, UnicodeError): except (TypeError, UnicodeError):
@ -165,9 +173,15 @@ class Connect(object):
@staticmethod @staticmethod
def _retryProxy(**kwargs): def _retryProxy(**kwargs):
"""
重试代理方法
处理请求失败时的重试逻辑
"""
# 获取当前线程数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
threadData.retriesCount += 1 threadData.retriesCount += 1
# 如果配置了代理列表且重试次数达到上限,则更换代理
if conf.proxyList and threadData.retriesCount >= conf.retries and not kb.locks.handlers.locked(): if conf.proxyList and threadData.retriesCount >= conf.retries and not kb.locks.handlers.locked():
warnMsg = "changing proxy" warnMsg = "changing proxy"
logger.warning(warnMsg) logger.warning(warnMsg)
@ -177,9 +191,8 @@ class Connect(object):
setHTTPHandlers() setHTTPHandlers()
# 处理基于时间的测试模式
if kb.testMode and kb.previousMethod == PAYLOAD.METHOD.TIME: 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 = "most likely web server instance hasn't recovered yet "
warnMsg += "from previous timed based payload. If the problem " warnMsg += "from previous timed based payload. If the problem "
warnMsg += "persists please wait for a few minutes and rerun " 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')" warnMsg += "lower the value of option '--time-sec' (e.g. '--time-sec=2')"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
# 处理原始页面为空的情况
elif kb.originalPage is None: elif kb.originalPage is None:
if conf.tor: if conf.tor:
warnMsg = "please make sure that you have " warnMsg = "please make sure that you have "
@ -214,20 +228,28 @@ class Connect(object):
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
# 处理多线程情况
elif conf.threads > 1: elif conf.threads > 1:
warnMsg = "if the problem persists please try to lower " warnMsg = "if the problem persists please try to lower "
warnMsg += "the number of used threads (option '--threads')" warnMsg += "the number of used threads (option '--threads')"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
# 重试请求
kwargs['retrying'] = True kwargs['retrying'] = True
return Connect._getPageProxy(**kwargs) return Connect._getPageProxy(**kwargs)
@staticmethod @staticmethod
def _connReadProxy(conn): def _connReadProxy(conn):
"""
读取连接响应的代理方法
处理压缩和大响应的情况
"""
retVal = b"" retVal = b""
# 如果不是DNS模式且连接存在
if not kb.dnsMode and conn: if not kb.dnsMode and conn:
headers = conn.info() 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()): 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) retVal = conn.read(MAX_CONNECTION_TOTAL_SIZE)
if len(retVal) == MAX_CONNECTION_TOTAL_SIZE: if len(retVal) == MAX_CONNECTION_TOTAL_SIZE:
@ -236,6 +258,7 @@ class Connect(object):
kb.pageCompress = False kb.pageCompress = False
raise SqlmapCompressionException raise SqlmapCompressionException
else: else:
# 分块读取大响应
while True: while True:
if not conn: if not conn:
break break
@ -254,11 +277,13 @@ class Connect(object):
retVal += part retVal += part
break break
# 检查总响应大小是否超过限制
if len(retVal) > MAX_CONNECTION_TOTAL_SIZE: if len(retVal) > MAX_CONNECTION_TOTAL_SIZE:
warnMsg = "too large response detected. Automatically trimming it" warnMsg = "too large response detected. Automatically trimming it"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
break break
# 处理特殊的响应放大因子
if conf.yuge: if conf.yuge:
retVal = YUGE_FACTOR * retVal retVal = YUGE_FACTOR * retVal
@ -267,13 +292,14 @@ class Connect(object):
@staticmethod @staticmethod
def getPage(**kwargs): def getPage(**kwargs):
""" """
This method connects to the target URL or proxy and returns 这个方法连接到目标URL或代理并返回目标URL页面内容
the target URL page content
""" """
# 如果是离线模式直接返回
if conf.offline: if conf.offline:
return None, None, None return None, None, None
# 获取请求参数
url = kwargs.get("url", None) or conf.url url = kwargs.get("url", None) or conf.url
get = kwargs.get("get", None) get = kwargs.get("get", None)
post = kwargs.get("post", None) post = kwargs.get("post", None)
@ -297,16 +323,19 @@ class Connect(object):
finalCode = kwargs.get("finalCode", False) finalCode = kwargs.get("finalCode", False)
chunked = kwargs.get("chunked", False) or conf.chunked chunked = kwargs.get("chunked", False) or conf.chunked
# 处理请求延迟
if isinstance(conf.delay, (int, float)) and conf.delay > 0: if isinstance(conf.delay, (int, float)) and conf.delay > 0:
time.sleep(conf.delay) time.sleep(conf.delay)
start = time.time() start = time.time()
# 获取当前线程数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
with kb.locks.request: with kb.locks.request:
kb.requestCounter += 1 kb.requestCounter += 1
threadData.lastRequestUID = kb.requestCounter threadData.lastRequestUID = kb.requestCounter
# 处理代理频率
if conf.proxyFreq: if conf.proxyFreq:
if kb.requestCounter % conf.proxyFreq == 0: if kb.requestCounter % conf.proxyFreq == 0:
conf.proxy = None conf.proxy = None
@ -316,6 +345,7 @@ class Connect(object):
setHTTPHandlers() setHTTPHandlers()
# 处理测试模式
if conf.dummy or conf.murphyRate and randomInt() % conf.murphyRate == 0: if conf.dummy or conf.murphyRate and randomInt() % conf.murphyRate == 0:
if conf.murphyRate: if conf.murphyRate:
time.sleep(randomInt() % (MAX_MURPHY_SLEEP_TIME + 1)) time.sleep(randomInt() % (MAX_MURPHY_SLEEP_TIME + 1))
@ -327,6 +357,7 @@ class Connect(object):
return page, headers, code return page, headers, code
# 处理cookie
if conf.liveCookies: if conf.liveCookies:
with kb.locks.liveCookies: with kb.locks.liveCookies:
if not checkFile(conf.liveCookies, raiseOnError=False) or os.path.getsize(conf.liveCookies) == 0: 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 = openFile(conf.liveCookies).read().strip()
cookie = re.sub(r"(?i)\ACookie:\s*", "", cookie) cookie = re.sub(r"(?i)\ACookie:\s*", "", cookie)
# 处理multipart请求
if multipart: if multipart:
post = multipart post = multipart
else: else:
@ -361,20 +393,20 @@ class Connect(object):
post = _urllib.parse.unquote(post) post = _urllib.parse.unquote(post)
post = chunkSplitPostData(post) post = chunkSplitPostData(post)
# 处理WebSocket请求
webSocket = url.lower().startswith("ws") webSocket = url.lower().startswith("ws")
if not _urllib.parse.urlsplit(url).netloc: if not _urllib.parse.urlsplit(url).netloc:
url = _urllib.parse.urljoin(conf.url, url) url = _urllib.parse.urljoin(conf.url, url)
# flag to know if we are dealing with the same target host # 检查是否是相同的目标主机
target = checkSameHost(url, conf.url) target = checkSameHost(url, conf.url)
if not retrying: if not retrying:
# Reset the number of connection retries # 重置连接重试次数
threadData.retriesCount = 0 threadData.retriesCount = 0
# fix for known issue when urllib2 just skips the other part of provided # 修复URL中的空格
# url splitted with space char while urlencoding it in the later phase
url = url.replace(" ", "%20") url = url.replace(" ", "%20")
if "://" not in url: if "://" not in url:
@ -396,8 +428,7 @@ class Connect(object):
raise404 = raise404 and not kb.ignoreNotFound raise404 = raise404 and not kb.ignoreNotFound
# support for non-latin (e.g. cyrillic) URLs as urllib/urllib2 doesn't # 支持非拉丁字符的URL
# support those by default
url = asciifyUrl(url) url = asciifyUrl(url)
try: try:
@ -440,7 +471,7 @@ class Connect(object):
requestMsg += " %s" % _http_client.HTTPConnection._http_vsn_str 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 {}) 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: if HTTP_HEADER.COOKIE in headers:
@ -624,11 +655,11 @@ class Connect(object):
if not kb.proxyAuthHeader and getRequestHeader(req, HTTP_HEADER.PROXY_AUTHORIZATION): if not kb.proxyAuthHeader and getRequestHeader(req, HTTP_HEADER.PROXY_AUTHORIZATION):
kb.proxyAuthHeader = getRequestHeader(req, HTTP_HEADER.PROXY_AUTHORIZATION) kb.proxyAuthHeader = getRequestHeader(req, HTTP_HEADER.PROXY_AUTHORIZATION)
# Return response object # 返回响应对象
if response: if response:
return conn, None, None return conn, None, None
# Get HTTP response # 获取HTTP响应
if hasattr(conn, "redurl"): if hasattr(conn, "redurl"):
page = (threadData.lastRedirectMsg[1] if kb.choices.redirect == REDIRECTION.NO else Connect._connReadProxy(conn)) if not skipRead else None 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 skipLogTraffic = kb.choices.redirect == REDIRECTION.NO

@ -5,6 +5,7 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的模块
import re import re
import time import time
@ -30,58 +31,87 @@ from lib.utils.safe2bin import safecharencode
from lib.utils.timeout import timeout from lib.utils.timeout import timeout
def direct(query, content=True): def direct(query, content=True):
"""
直接执行SQL查询的主函数
参数:
query: 要执行的SQL查询语句
content: 是否返回查询内容,默认为True
"""
# 标记是否为SELECT查询
select = True select = True
# 处理查询语句,添加必要的payload
query = agent.payloadDirect(query) query = agent.payloadDirect(query)
query = agent.adjustLateValues(query) query = agent.adjustLateValues(query)
# 获取当前线程数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
# 针对Oracle数据库的特殊处理:如果是不带FROM的SELECT语句,添加"FROM DUAL"
if Backend.isDbms(DBMS.ORACLE) and query.upper().startswith("SELECT ") and " FROM " not in query.upper(): if Backend.isDbms(DBMS.ORACLE) and query.upper().startswith("SELECT ") and " FROM " not in query.upper():
query = "%s FROM DUAL" % query query = "%s FROM DUAL" % query
# 通过遍历SQL语句字典判断是否为SELECT查询
for sqlTitle, sqlStatements in SQL_STATEMENTS.items(): for sqlTitle, sqlStatements in SQL_STATEMENTS.items():
for sqlStatement in sqlStatements: for sqlStatement in sqlStatements:
if query.lower().startswith(sqlStatement) and sqlTitle != "SQL SELECT statement": if query.lower().startswith(sqlStatement) and sqlTitle != "SQL SELECT statement":
select = False select = False
break break
# 如果是SELECT查询,进行相应处理
if select: if select:
# 如果查询不以SELECT开头,添加SELECT
if re.search(r"(?i)\ASELECT ", query) is None: if re.search(r"(?i)\ASELECT ", query) is None:
query = "SELECT %s" % query query = "SELECT %s" % query
# 处理二进制字段
if conf.binaryFields: if conf.binaryFields:
for field in conf.binaryFields: for field in conf.binaryFields:
field = field.strip() field = field.strip()
if re.search(r"\b%s\b" % re.escape(field), query): 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) query = re.sub(r"\b%s\b" % re.escape(field), agent.hexConvertField(field), query)
# 记录查询语句到日志
logger.log(CUSTOM_LOGGING.PAYLOAD, query) logger.log(CUSTOM_LOGGING.PAYLOAD, query)
# 尝试从缓存中获取查询结果
output = hashDBRetrieve(query, True, True) output = hashDBRetrieve(query, True, True)
start = time.time() start = time.time()
# 执行查询
if not select and re.search(r"(?i)\bEXEC ", query) is None: 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) 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): 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) output, state = timeout(func=conf.dbmsConnector.select, args=(query,), duration=conf.timeout, default=None)
if state == TIMEOUT_STATE.NORMAL: if state == TIMEOUT_STATE.NORMAL:
# 正常执行完成,将结果写入缓存
hashDBWrite(query, output, True) hashDBWrite(query, output, True)
elif state == TIMEOUT_STATE.TIMEOUT: elif state == TIMEOUT_STATE.TIMEOUT:
# 超时处理:关闭连接并重新连接
conf.dbmsConnector.close() conf.dbmsConnector.close()
conf.dbmsConnector.connect() conf.dbmsConnector.connect()
elif output: elif output:
# 如果有缓存结果,显示提示信息
infoMsg = "resumed: %s..." % getUnicode(output, UNICODE_ENCODING)[:20] infoMsg = "resumed: %s..." % getUnicode(output, UNICODE_ENCODING)[:20]
logger.info(infoMsg) logger.info(infoMsg)
# 记录查询执行时间
threadData.lastQueryDuration = calculateDeltaSeconds(start) threadData.lastQueryDuration = calculateDeltaSeconds(start)
# 处理返回结果
if not output: if not output:
return output return output
elif content: elif content:
# 如果需要返回内容
if output and isListLike(output): if output and isListLike(output):
if len(output[0]) == 1: if len(output[0]) == 1:
# 如果结果只有一列,简化输出格式
output = [_[0] for _ in output] output = [_[0] for _ in output]
# 转换为Unicode格式
retVal = getUnicode(output, noneToNull=True) retVal = getUnicode(output, noneToNull=True)
# 根据配置决定是否进行安全字符编码
return safecharencode(retVal) if kb.safeCharEncode else retVal return safecharencode(retVal) if kb.safeCharEncode else retVal
else: else:
# 如果不需要返回内容,提取预期的布尔值
return extractExpectedValue(output, EXPECTED.BOOL) return extractExpectedValue(output, EXPECTED.BOOL)

@ -7,16 +7,17 @@ See the file 'LICENSE' for copying permission
from __future__ import print_function from __future__ import print_function
import binascii import binascii # 用于二进制和ASCII转换
import os import os
import re import re
import socket import socket # 用于网络通信
import struct import struct # 用于处理字节串
import threading import threading # 用于多线程
import time import time
class DNSQuery(object): 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." >>> 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 True
>>> DNSQuery(b'\\x00')._query == b"" >>> DNSQuery(b'\\x00')._query == b""
@ -24,12 +25,15 @@ class DNSQuery(object):
""" """
def __init__(self, raw): def __init__(self, raw):
self._raw = raw self._raw = raw # 原始DNS查询数据
self._query = b"" self._query = b"" # 解析后的域名查询字符串
try: 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 if type_ == 0: # Standard query
i = 12 i = 12
j = ord(raw[i:i + 1]) j = ord(raw[i:i + 1])

@ -5,6 +5,7 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的标准库和第三方库
import re import re
import socket 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 http_client as _http_client
from thirdparty.six.moves import urllib as _urllib from thirdparty.six.moves import urllib as _urllib
# 尝试导入ssl模块,如果导入失败则ssl为None
ssl = None ssl = None
try: try:
import ssl as _ssl import ssl as _ssl
@ -27,31 +29,38 @@ try:
except ImportError: except ImportError:
pass 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")) _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_")) _lut = dict((getattr(ssl, _), _) for _ in dir(ssl) if _.startswith("PROTOCOL_"))
# 存储SSL上下文的字典
_contexts = {} _contexts = {}
class HTTPSConnection(_http_client.HTTPSConnection): 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): 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 hasattr(ssl, "_create_default_https_context"):
if None not in _contexts: if None not in _contexts:
_contexts[None] = ssl._create_default_https_context() _contexts[None] = ssl._create_default_https_context()
kwargs["context"] = _contexts[None] kwargs["context"] = _contexts[None]
# 重试标志
self.retrying = False self.retrying = False
# 调用父类初始化方法
_http_client.HTTPSConnection.__init__(self, *args, **kwargs) _http_client.HTTPSConnection.__init__(self, *args, **kwargs)
def connect(self): def connect(self):
# 创建socket连接的内部函数
def create_sock(): def create_sock():
sock = socket.create_connection((self.host, self.port), self.timeout) sock = socket.create_connection((self.host, self.port), self.timeout)
if getattr(self, "_tunnel_host", None): if getattr(self, "_tunnel_host", None):
@ -61,31 +70,33 @@ class HTTPSConnection(_http_client.HTTPSConnection):
success = False success = False
# Reference(s): https://docs.python.org/2/library/ssl.html#ssl.SSLContext # 使用SSLContext方式建立SSL连接(Python 2.7.9及更高版本支持)
# https://www.mnot.net/blog/2014/12/27/python_2_and_tls_sni
if hasattr(ssl, "SSLContext"): if hasattr(ssl, "SSLContext"):
for protocol in (_ for _ in _protocols if _ >= ssl.PROTOCOL_TLSv1): for protocol in (_ for _ in _protocols if _ >= ssl.PROTOCOL_TLSv1):
try: try:
sock = create_sock() sock = create_sock()
if protocol not in _contexts: if protocol not in _contexts:
# 创建SSL上下文
_contexts[protocol] = ssl.SSLContext(protocol) _contexts[protocol] = ssl.SSLContext(protocol)
# Disable certificate and hostname validation enabled by default with PROTOCOL_TLS_CLIENT # 禁用证书和主机名验证
_contexts[protocol].check_hostname = False _contexts[protocol].check_hostname = False
_contexts[protocol].verify_mode = ssl.CERT_NONE _contexts[protocol].verify_mode = ssl.CERT_NONE
# 如果提供了证书和密钥文件,则加载它们
if getattr(self, "cert_file", None) and getattr(self, "key_file", 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) _contexts[protocol].load_cert_chain(certfile=self.cert_file, keyfile=self.key_file)
try: try:
# Reference(s): https://askubuntu.com/a/1263098 # 设置加密套件
# https://askubuntu.com/a/1250807
_contexts[protocol].set_ciphers("DEFAULT@SECLEVEL=1") _contexts[protocol].set_ciphers("DEFAULT@SECLEVEL=1")
except (ssl.SSLError, AttributeError): except (ssl.SSLError, AttributeError):
pass 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) 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: if result:
success = True success = True
self.sock = result self.sock = result
# 将成功的协议移到列表开头
_protocols.remove(protocol) _protocols.remove(protocol)
_protocols.insert(0, protocol) _protocols.insert(0, protocol)
break break
@ -95,6 +106,7 @@ class HTTPSConnection(_http_client.HTTPSConnection):
self._tunnel_host = None self._tunnel_host = None
logger.debug("SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex))) logger.debug("SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex)))
# 使用旧式ssl.wrap_socket方式建立SSL连接(用于较老版本的Python)
elif hasattr(ssl, "wrap_socket"): elif hasattr(ssl, "wrap_socket"):
for protocol in _protocols: for protocol in _protocols:
try: try:
@ -112,12 +124,14 @@ class HTTPSConnection(_http_client.HTTPSConnection):
self._tunnel_host = None self._tunnel_host = None
logger.debug("SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex))) logger.debug("SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex)))
# 如果所有协议都连接失败
if not success: if not success:
errMsg = "can't establish SSL connection" 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"): if LooseVersion(PYVERSION) < LooseVersion("2.7.9"):
errMsg += " (please retry with Python >= 2.7.9)" errMsg += " (please retry with Python >= 2.7.9)"
# 如果之前有成功连接过且未在重试,则进行重试
if kb.sslSuccess and not self.retrying: if kb.sslSuccess and not self.retrying:
self.retrying = True self.retrying = True
@ -134,5 +148,9 @@ class HTTPSConnection(_http_client.HTTPSConnection):
kb.sslSuccess = True kb.sslSuccess = True
class HTTPSHandler(_urllib.request.HTTPSHandler): class HTTPSHandler(_urllib.request.HTTPSHandler):
"""
HTTPS处理器类,用于处理HTTPS请求
"""
def https_open(self, req): def https_open(self, req):
# 根据是否有ssl模块选择合适的连接类来处理请求
return self.do_open(HTTPSConnection if ssl else _http_client.HTTPSConnection, req) return self.do_open(HTTPSConnection if ssl else _http_client.HTTPSConnection, req)

@ -5,16 +5,27 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入getText函数,用于文本转换
from lib.core.convert import getText from lib.core.convert import getText
# 导入urllib库并重命名为_urllib,用于处理URL请求
from thirdparty.six.moves import urllib as _urllib from thirdparty.six.moves import urllib as _urllib
class MethodRequest(_urllib.request.Request): 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): 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): def get_method(self):
# 获取HTTP请求方法
# getattr用于获取对象的属性
# 如果self.method存在就返回它
# 否则返回父类Request默认的get_method()方法的结果
return getattr(self, 'method', _urllib.request.Request.get_method(self)) return getattr(self, 'method', _urllib.request.Request.get_method(self))

@ -5,25 +5,57 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
from lib.core.data import conf # 导入所需的模块
from lib.core.common import getSafeExString from lib.core.data import conf # 导入配置信息
from lib.core.exception import SqlmapConnectionException from lib.core.common import getSafeExString # 导入安全字符串处理函数
from thirdparty.six.moves import http_client as _http_client from lib.core.exception import SqlmapConnectionException # 导入SQL注入连接异常类
from thirdparty.six.moves import urllib as _urllib 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): class HTTPSPKIAuthHandler(_urllib.request.HTTPSHandler):
"""
HTTPS PKI认证处理器类
继承自urllib的HTTPSHandler类,用于处理带客户端证书的HTTPS请求
"""
def __init__(self, auth_file): 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): def https_open(self, req):
"""
处理HTTPS请求的方法
参数:
req: HTTPS请求对象
返回:
处理后的HTTPS连接
"""
return self.do_open(self.getConnection, req) return self.do_open(self.getConnection, req)
def getConnection(self, host, timeout=None): def getConnection(self, host, timeout=None):
"""
建立HTTPS连接的方法
参数:
host: 目标主机
timeout: 超时时间,默认为None
返回:
HTTPS连接对象
异常:
SqlmapConnectionException: 连接异常
"""
try: try:
# Reference: https://docs.python.org/2/library/ssl.html#ssl.SSLContext.load_cert_chain # 创建带客户端证书的HTTPS连接
return _http_client.HTTPSConnection(host, cert_file=self.auth_file, key_file=self.auth_file, timeout=conf.timeout) # 参考文档: 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: except IOError as ex:
# 如果出现IO错误(比如证书文件无法读取等)
errMsg = "error occurred while using key " errMsg = "error occurred while using key "
errMsg += "file '%s' ('%s')" % (self.auth_file, getSafeExString(ex)) errMsg += "file '%s' ('%s')" % (self.auth_file, getSafeExString(ex))
raise SqlmapConnectionException(errMsg) raise SqlmapConnectionException(errMsg) # 抛出SQL注入连接异常

@ -5,25 +5,44 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入自定义的SQL映射连接异常类
from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapConnectionException
# 导入urllib库并重命名为_urllib,用于处理HTTP请求
from thirdparty.six.moves import urllib as _urllib from thirdparty.six.moves import urllib as _urllib
class HTTPRangeHandler(_urllib.request.BaseHandler): class HTTPRangeHandler(_urllib.request.BaseHandler):
""" """
Handler that enables HTTP Range headers. 处理HTTP Range头部的处理器类
Range头部允许客户端请求资源的部分内容而不是整个资源
Reference: http://stackoverflow.com/questions/1971240/python-seek-on-remote-file
参考文档: http://stackoverflow.com/questions/1971240/python-seek-on-remote-file
""" """
def http_error_206(self, req, fp, code, msg, hdrs): 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 = _urllib.response.addinfourl(fp, hdrs, req.get_full_url())
r.code = code r.code = code # 设置响应状态码
r.msg = msg r.msg = msg # 设置响应消息
return r return r
def http_error_416(self, req, fp, code, msg, hdrs): 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 = "there was a problem while connecting "
errMsg += "target ('406 - Range Not Satisfiable')" errMsg += "target ('406 - Range Not Satisfiable')"
# 抛出SQL映射连接异常
raise SqlmapConnectionException(errMsg) raise SqlmapConnectionException(errMsg)

@ -5,11 +5,13 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的标准库和第三方库
import io import io
import re import re
import time import time
import types import types
# 导入自定义工具函数
from lib.core.common import getHostHeader from lib.core.common import getHostHeader
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
from lib.core.common import logHTTPTraffic from lib.core.common import logHTTPTraffic
@ -36,7 +38,17 @@ from thirdparty import six
from thirdparty.six.moves import urllib as _urllib from thirdparty.six.moves import urllib as _urllib
class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler): class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
"""
智能重定向处理器类,继承自urllib的HTTPRedirectHandler
用于处理HTTP重定向响应(301/302/303/307)
"""
def _get_header_redirect(self, headers): def _get_header_redirect(self, headers):
"""
从响应头中获取重定向URL
:param headers: HTTP响应头
:return: 重定向URL或None
"""
retVal = None retVal = None
if headers: if headers:
@ -48,13 +60,21 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
return retVal return retVal
def _ask_redirect_choice(self, redcode, redurl, method): def _ask_redirect_choice(self, redcode, redurl, method):
"""
询问用户是否要跟随重定向
:param redcode: 重定向状态码
:param redurl: 重定向URL
:param method: HTTP请求方法
"""
with kb.locks.redirect: with kb.locks.redirect:
# 如果还没有做出重定向选择
if kb.choices.redirect is None: if kb.choices.redirect is None:
msg = "got a %d redirect to " % redcode msg = "got a %d redirect to " % redcode
msg += "'%s'. Do you want to follow? [Y/n] " % redurl 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 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: if kb.choices.redirect == REDIRECTION.YES and method == HTTPMETHOD.POST and kb.resendPostOnRedirect is None:
msg = "redirect is a result of a " msg = "redirect is a result of a "
msg += "POST request. Do you want to " msg += "POST request. Do you want to "
@ -67,31 +87,49 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
self.redirect_request = self._redirect_request self.redirect_request = self._redirect_request
def _redirect_request(self, req, fp, code, msg, headers, newurl): 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) 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): 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() start = time.time()
content = None content = None
forceRedirect = False forceRedirect = False
# 如果未忽略重定向,则获取重定向URL
redurl = self._get_header_redirect(headers) if not conf.ignoreRedirects else None redurl = self._get_header_redirect(headers) if not conf.ignoreRedirects else None
# 读取响应内容
try: try:
content = fp.read(MAX_CONNECTION_TOTAL_SIZE) content = fp.read(MAX_CONNECTION_TOTAL_SIZE)
except: # e.g. IncompleteRead except: # 处理不完整读取
content = b"" content = b""
finally: finally:
if content: 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.truncate(0)
fp.fp._rbuf.write(content) fp.fp._rbuf.write(content)
except: except:
pass pass
# 解码响应内容
content = decodePage(content, headers.get(HTTP_HEADER.CONTENT_ENCODING), headers.get(HTTP_HEADER.CONTENT_TYPE)) content = decodePage(content, headers.get(HTTP_HEADER.CONTENT_ENCODING), headers.get(HTTP_HEADER.CONTENT_TYPE))
# 记录重定向信息
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
threadData.lastRedirectMsg = (threadData.lastRequestUID, content) threadData.lastRedirectMsg = (threadData.lastRequestUID, content)
# 构建重定向日志消息
redirectMsg = "HTTP redirect " redirectMsg = "HTTP redirect "
redirectMsg += "[#%d] (%d %s):\r\n" % (threadData.lastRequestUID, code, getUnicode(msg)) redirectMsg += "[#%d] (%d %s):\r\n" % (threadData.lastRequestUID, code, getUnicode(msg))
@ -104,31 +142,39 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
if content: if content:
redirectMsg += "\r\n\r\n%s" % getUnicode(content[:MAX_CONNECTION_READ_SIZE]) redirectMsg += "\r\n\r\n%s" % getUnicode(content[:MAX_CONNECTION_READ_SIZE])
# 记录HTTP流量日志
logHTTPTraffic(threadData.lastRequestMsg, redirectMsg, start, time.time()) logHTTPTraffic(threadData.lastRequestMsg, redirectMsg, start, time.time())
logger.log(CUSTOM_LOGGING.TRAFFIC_IN, redirectMsg) logger.log(CUSTOM_LOGGING.TRAFFIC_IN, redirectMsg)
if redurl: if redurl:
try: try:
# 如果重定向URL是相对路径,转换为绝对路径
if not _urllib.parse.urlsplit(redurl).netloc: if not _urllib.parse.urlsplit(redurl).netloc:
redurl = _urllib.parse.urljoin(req.get_full_url(), redurl) redurl = _urllib.parse.urljoin(req.get_full_url(), redurl)
# 检查是否存在无限重定向循环
self._infinite_loop_check(req) self._infinite_loop_check(req)
if conf.scope: if conf.scope:
# 检查重定向URL是否在指定范围内
if not re.search(conf.scope, redurl, re.I): if not re.search(conf.scope, redurl, re.I):
redurl = None redurl = None
else: else:
forceRedirect = True forceRedirect = True
else: else:
# 询问用户是否跟随重定向
self._ask_redirect_choice(code, redurl, req.get_method()) self._ask_redirect_choice(code, redurl, req.get_method())
except ValueError: except ValueError:
redurl = None redurl = None
result = fp result = fp
if redurl and (kb.choices.redirect == REDIRECTION.YES or forceRedirect): if redurl and (kb.choices.redirect == REDIRECTION.YES or forceRedirect):
# 解析响应
parseResponse(content, headers) parseResponse(content, headers)
# 更新请求头中的Host
req.headers[HTTP_HEADER.HOST] = getHostHeader(redurl) req.headers[HTTP_HEADER.HOST] = getHostHeader(redurl)
if headers and HTTP_HEADER.SET_COOKIE in headers: if headers and HTTP_HEADER.SET_COOKIE in headers:
# 处理Cookie
cookies = dict() cookies = dict()
delimiter = conf.cookieDel or DEFAULT_COOKIE_DELIMITER delimiter = conf.cookieDel or DEFAULT_COOKIE_DELIMITER
last = None 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) req.headers[HTTP_HEADER.COOKIE] = delimiter.join("%s=%s" % (key, cookies[key]) for key in cookies)
try: try:
# 执行重定向请求
result = _urllib.request.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers) result = _urllib.request.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
except _urllib.error.HTTPError as ex: except _urllib.error.HTTPError as ex:
result = ex result = ex
# Dirty hack for https://github.com/sqlmapproject/sqlmap/issues/4046 # 处理特殊情况的hack
try: try:
hasattr(result, "read") hasattr(result, "read")
except KeyError: except KeyError:
@ -157,7 +204,6 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
pass pass
result = _() result = _()
# Dirty hack for http://bugs.python.org/issue15701
try: try:
result.info() result.info()
except AttributeError: except AttributeError:
@ -169,7 +215,7 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
if not hasattr(result, "read"): if not hasattr(result, "read"):
def _(self, length=None): def _(self, length=None):
try: try:
retVal = getSafeExString(ex) # Note: pyflakes mistakenly marks 'ex' as undefined (NOTE: tested in both Python2 and Python3) retVal = getSafeExString(ex)
except: except:
retVal = "" retVal = ""
return getBytes(retVal) return getBytes(retVal)
@ -188,15 +234,23 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
else: else:
result = fp result = fp
# 记录最后的重定向URL
threadData.lastRedirectURL = (threadData.lastRequestUID, redurl) threadData.lastRedirectURL = (threadData.lastRequestUID, redurl)
# 设置结果属性
result.redcode = code result.redcode = code
result.redurl = getUnicode(redurl) if six.PY3 else redurl result.redurl = getUnicode(redurl) if six.PY3 else redurl
return result return result
# 其他重定向状态码使用相同的处理方法
http_error_301 = http_error_303 = http_error_307 = http_error_302 http_error_301 = http_error_303 = http_error_307 = http_error_302
def _infinite_loop_check(self, req): 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): 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 = "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" errMsg += "Please check all provided parameters and/or provide missing ones"

@ -5,17 +5,59 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入必要的模块
# kb (Knowledge Base) 是一个全局对象,用于存储程序运行时的各种状态和数据
# 比如原始页面内容、错误状态、页面模板缓存等信息都存储在这里
from lib.core.data import kb from lib.core.data import kb
# 从connect模块导入Request类并重命名为Request
# Request类用于发送HTTP请求,是与目标网站交互的核心类
from lib.request.connect import Connect as Request from lib.request.connect import Connect as Request
def getPageTemplate(payload, place): 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) retVal = (kb.originalPage, kb.errorIsNone)
# 只有当payload和place都不为空时才执行注入操作
if payload and place: if payload and place:
# 检查这个payload和place的组合是否已经在缓存中
# kb.pageTemplates是一个字典,用于缓存不同注入组合的结果
if (payload, place) not in 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, _, _ = Request.queryPage(payload, place, content=True, raise404=False)
# 将结果存入缓存
# page 是获取到的页面内容
# kb.lastParserStatus is None 表示页面解析是否成功
# (None表示解析成功,非None表示解析出错)
kb.pageTemplates[(payload, place)] = (page, kb.lastParserStatus is None) kb.pageTemplates[(payload, place)] = (page, kb.lastParserStatus is None)
# 从缓存中获取之前存储的结果
retVal = kb.pageTemplates[(payload, place)] retVal = kb.pageTemplates[(payload, place)]
# 返回页面内容和错误状态
return retVal return retVal

Loading…
Cancel
Save