Compare commits

...
11 Commits
Author SHA1 Message Date
Miroslav Štampar 397a3bde2e Implements #6177 2026-11-06 15:31:35 +01:00
Miroslav Štampar 097f4d2c37 Fixes #6176 2026-11-06 15:03:08 +01:00
Miroslav Štampar 202d8cc1c4 Adding some more entries into common files list 2026-11-06 09:15:14 +01:00
Miroslav Štampar f65de0e890 Minor update 2026-11-04 10:20:01 +01:00
cuteNBDandcuteNBD b333a14d8b Keep WebSocket responses on normal closure (#6173)
Co-authored-by: cuteNBD <[email protected]>
2026-11-04 10:18:29 +01:00
cuteNBDandcuteNBD 74f50122fb Preserve custom Host headers for WebSocket requests (#6172)
Co-authored-by: cuteNBD <[email protected]>
2026-11-04 10:18:06 +01:00
Miroslav Štampar bf8b9956e0 Fixes #6170 2026-11-03 22:04:29 +01:00
Miroslav Štampar 8476cf4d09 Fix related to the #6171 (WebSocket Host port) 2026-11-03 22:04:01 +01:00
cuteNBDandcuteNBD 3130623afc Preserve non-default ports in Host headers (#6171)
Co-authored-by: cuteNBD <[email protected]>
2026-11-03 22:03:38 +01:00
Miroslav Štampar c15f4bf055 Fixes #6168 2026-11-02 11:13:40 +01:00
Miroslav Štampar 9cd0619e2b Dummy update 2026-10-30 17:31:39 +01:00
15 changed files with 426 additions and 29 deletions

No files matched your search

+65
View File
@@ -1818,6 +1818,8 @@
/run/secrets/kubernetes.io/serviceaccount/namespace /run/secrets/kubernetes.io/serviceaccount/namespace
/var/run/secrets/kubernetes.io/serviceaccount/namespace /var/run/secrets/kubernetes.io/serviceaccount/namespace
/run/secrets/kubernetes.io/serviceaccount/ca.crt /run/secrets/kubernetes.io/serviceaccount/ca.crt
/etc/kubernetes/admin.conf
/etc/kubernetes/kubelet.conf
# Cloud provider credentials # Cloud provider credentials
@@ -1825,7 +1827,9 @@
/root/.aws/config /root/.aws/config
/root/.config/gcloud/application_default_credentials.json /root/.config/gcloud/application_default_credentials.json
/root/.config/gcloud/credentials.db /root/.config/gcloud/credentials.db
/root/.config/gcloud/access_tokens.db
/root/.azure/accessTokens.json /root/.azure/accessTokens.json
/root/.azure/credentials
/root/.kube/config /root/.kube/config
# SSH keys and DB/tool history # SSH keys and DB/tool history
@@ -1834,6 +1838,10 @@
/root/.ssh/id_ed25519 /root/.ssh/id_ed25519
/root/.ssh/id_ecdsa /root/.ssh/id_ecdsa
/root/.ssh/id_dsa /root/.ssh/id_dsa
/root/.ssh/config
/etc/ssh/ssh_host_rsa_key
/etc/ssh/ssh_host_ed25519_key
/etc/ssh/ssh_host_ecdsa_key
/root/.mysql_history /root/.mysql_history
/root/.psql_history /root/.psql_history
/root/.rediscli_history /root/.rediscli_history
@@ -1852,3 +1860,60 @@
/app/application.properties /app/application.properties
/app/application.yml /app/application.yml
/app/docker-compose.yml /app/docker-compose.yml
# Developer, package manager and DBMS credential files
/root/.env
/root/.netrc
/root/.git-credentials
/root/.npmrc
/root/.pypirc
/root/.docker/config.json
/root/.config/gh/hosts.yml
/root/.config/op/config
/root/.my.cnf
/etc/mysql/debian.cnf
# Hosting control panels
/etc/psa/.psa.shadow
/etc/psa/private/secret_key
# AI agent credentials
/root/.claude/.credentials.json
/root/.claude.json
/root/.codex/auth.json
/root/.config/anthropic/config.json
# Secrets in the default user's home on common cloud images
/home/ubuntu/.ssh/id_rsa
/home/ubuntu/.aws/credentials
/home/ubuntu/.kube/config
/home/ubuntu/.git-credentials
/home/ubuntu/.claude/.credentials.json
/home/debian/.ssh/id_rsa
/home/debian/.aws/credentials
/home/debian/.kube/config
/home/debian/.git-credentials
/home/admin/.ssh/id_rsa
/home/admin/.aws/credentials
/home/admin/.kube/config
/home/admin/.git-credentials
/home/ec2-user/.ssh/id_rsa
/home/ec2-user/.aws/credentials
/home/ec2-user/.kube/config
/home/ec2-user/.git-credentials
/home/centos/.ssh/id_rsa
/home/centos/.aws/credentials
/home/centos/.kube/config
/home/opc/.ssh/id_rsa
/home/opc/.aws/credentials
/home/opc/.kube/config
# Appliance and LFI-to-RCE gadget targets
/dev/cmdb/sslvpn_websession
/usr/share/php/pearcmd.php
/usr/local/lib/php/pearcmd.php
+25 -9
View File
@@ -3236,7 +3236,7 @@ def logHTTPTraffic(requestLogMsg, responseLogMsg, startTime=None, endTime=None):
""" """
if conf.harFile: if conf.harFile:
conf.httpCollector.collectRequest(requestLogMsg, responseLogMsg, startTime, endTime) conf.httpCollector.collectRequest(requestLogMsg, responseLogMsg, startTime, endTime, scheme=conf.scheme)
if conf.trafficFile: if conf.trafficFile:
with kb.locks.log: with kb.locks.log:
@@ -4066,7 +4066,7 @@ def decodeIntToUnicode(value):
""" """
retVal = value retVal = value
if isinstance(value, int): if isinstance(value, six.integer_types):
try: try:
if value > 255: if value > 255:
_ = "%x" % value _ = "%x" % value
@@ -5138,12 +5138,12 @@ def getHostHeader(url):
retVal = url retVal = url
if url: if url:
retVal = _urllib.parse.urlparse(url).netloc parsed = _urllib.parse.urlparse(url)
retVal = parsed.netloc
defaultPort = {"http": 80, "https": 443, "ws": 80, "wss": 443}.get(parsed.scheme.lower())
# Note: netloc keeps the IPv6 brackets (and any port), so only the default ports are # Keep non-default ports, including 80 on HTTPS and 443 on HTTP.
# stripped here - mirroring the hostname/IPv4 branch and preserving non-default ports if defaultPort is not None and retVal.endswith(':%d' % defaultPort):
# (e.g. '[::1]:8080') as required by RFC 7230
if any(retVal.endswith(':%d' % _) for _ in (80, 443)):
retVal = retVal[:retVal.rfind(':')] retVal = retVal[:retVal.rfind(':')]
if retVal and retVal.count(':') > 1 and not any(_ in retVal for _ in ('[', ']')): if retVal and retVal.count(':') > 1 and not any(_ in retVal for _ in ('[', ']')):
@@ -5645,7 +5645,7 @@ def pollProcess(process, suppress_errors=False):
def parseRequestFile(reqFile, checkParams=True): def parseRequestFile(reqFile, checkParams=True):
""" """
Parses WebScarab and Burp logs and adds results to the target URL list Parses WebScarab, Burp and HAR logs and adds results to the target URL list
>>> handle, reqFile = tempfile.mkstemp(suffix=".req") >>> handle, reqFile = tempfile.mkstemp(suffix=".req")
>>> content = b"POST / HTTP/1.0\\nUser-agent: foobar\\nHost: www.example.com\\n\\nid=1\\n" >>> content = b"POST / HTTP/1.0\\nUser-agent: foobar\\nHost: www.example.com\\n\\nid=1\\n"
@@ -5689,7 +5689,23 @@ def parseRequestFile(reqFile, checkParams=True):
Parses Burp logs Parses Burp logs
""" """
if not re.search(BURP_REQUEST_REGEX, content, re.I | re.S): if re.search(r"\A\s*\{.*\"entries\"", content[:1000], re.S):
reqResList = []
try:
entries = json.loads(content)["log"]["entries"]
except (ValueError, KeyError, TypeError):
entries = []
for entry in entries: # Note: HAR (HTTP Archive) logs (e.g. ZAP, browsers) - re-serialized into raw requests
try:
request = entry["request"]
url = request["url"]
headers = [(_["name"], _["value"]) for _ in request.get("headers", []) if not _["name"].startswith(':')]
if not any(_[0].upper() == HTTP_HEADER.HOST.upper() for _ in headers):
headers.insert(0, (HTTP_HEADER.HOST, _urllib.parse.urlsplit(url).netloc))
reqResList.append("%s %s HTTP/1.1\n%s\n\n%s" % (request["method"], url, "\n".join("%s: %s" % _ for _ in headers), (request.get("postData") or {}).get("text") or ""))
except (KeyError, TypeError, AttributeError):
continue
elif not re.search(BURP_REQUEST_REGEX, content, re.I | re.S):
if re.search(BURP_XML_HISTORY_REGEX, content, re.I | re.S): if re.search(BURP_XML_HISTORY_REGEX, content, re.I | re.S):
reqResList = [] reqResList = []
for match in re.finditer(BURP_XML_HISTORY_REGEX, content, re.I | re.S): for match in re.finditer(BURP_XML_HISTORY_REGEX, content, re.I | re.S):
+3
View File
@@ -125,6 +125,9 @@ def _serializeEncode(value):
if isinstance(value, decimal.Decimal): if isinstance(value, decimal.Decimal):
return {_SERIALIZE_TAG: "dec", "v": getUnicode(value)} return {_SERIALIZE_TAG: "dec", "v": getUnicode(value)}
if isinstance(value, (datetime.datetime, datetime.time)) and value.tzinfo is not None:
return getUnicode(value) # Note: time zone aware value (e.g. from '-d' connector) kept as displayed, as an arbitrary tzinfo can't be rebuilt
if isinstance(value, datetime.datetime): if isinstance(value, datetime.datetime):
return {_SERIALIZE_TAG: "dt", "v": [value.year, value.month, value.day, value.hour, value.minute, value.second, value.microsecond]} return {_SERIALIZE_TAG: "dt", "v": [value.year, value.month, value.day, value.hour, value.minute, value.second, value.microsecond]}
+2 -2
View File
@@ -19,7 +19,7 @@ from lib.core.enums import OS
from thirdparty import six from thirdparty import six
# sqlmap version (<major>.<minor>.<month>.<monthly commit>) # sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.10.11.0" VERSION = "1.10.11.8"
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)
@@ -114,7 +114,7 @@ BOOLEAN_MODEL_MATCH_RATIO = 0.9
PARAMETER_MINING_BUCKET_SIZE = 25 PARAMETER_MINING_BUCKET_SIZE = 25
# For filling in case of dumb push updates # For filling in case of dumb push updates
DUMMY_JUNK = "Phah5jue" DUMMY_JUNK = "rie8kooZ"
# Markers for special cases when parameter values contain html encoded characters # Markers for special cases when parameter values contain html encoded characters
PARAMETER_AMP_MARKER = "__PARAMETER_AMP__" PARAMETER_AMP_MARKER = "__PARAMETER_AMP__"
+1 -1
View File
@@ -130,7 +130,7 @@ def cmdLineParser(argv=None):
help="Connection string for direct database connection") help="Connection string for direct database connection")
target.add_argument("-l", dest="logFile", target.add_argument("-l", dest="logFile",
help="Parse target(s) from Burp or WebScarab proxy log file") help="Parse target(s) from Burp, WebScarab or HAR log file")
target.add_argument("-m", dest="bulkFile", target.add_argument("-m", dest="bulkFile",
help="Scan multiple targets given in a textual file") help="Scan multiple targets given in a textual file")
+11 -5
View File
@@ -521,6 +521,8 @@ class Connect(object):
# Prepare HTTP headers # Prepare HTTP headers
headers = forgeHeaders({HTTP_HEADER.COOKIE: cookie, HTTP_HEADER.USER_AGENT: ua, HTTP_HEADER.REFERER: referer, HTTP_HEADER.HOST: host or 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: host or getHeader(dict(conf.httpHeaders), HTTP_HEADER.HOST) or getHostHeader(url)}, base=None if target else {})
# WebSocket builds its own default Host, but must preserve explicit overrides.
customWsHost = webSocket and bool(host or getHeader(dict(conf.httpHeaders), HTTP_HEADER.HOST) or getHeader(auxHeaders, HTTP_HEADER.HOST) is not None)
if HTTP_HEADER.COOKIE in headers: if HTTP_HEADER.COOKIE in headers:
cookie = headers[HTTP_HEADER.COOKIE] cookie = headers[HTTP_HEADER.COOKIE]
@@ -573,9 +575,13 @@ class Connect(object):
if ':' in line: if ':' in line:
header, value = line.split(':', 1) header, value = line.split(':', 1)
headers[header] = value headers[header] = value
if webSocket and header.upper() == HTTP_HEADER.HOST.upper():
headers[HTTP_HEADER.HOST] = value
customWsHost = True
if conf.localhost: if conf.localhost:
headers[HTTP_HEADER.HOST] = "localhost" headers[HTTP_HEADER.HOST] = "localhost"
customWsHost = True
for key, value in list(headers.items()): for key, value in list(headers.items()):
if key.upper() == HTTP_HEADER.ACCEPT_ENCODING.upper(): if key.upper() == HTTP_HEADER.ACCEPT_ENCODING.upper():
@@ -600,7 +606,7 @@ class Connect(object):
try: try:
ws.settimeout(WEBSOCKET_INITIAL_TIMEOUT if kb.webSocketRecvCount is None else timeout) ws.settimeout(WEBSOCKET_INITIAL_TIMEOUT if kb.webSocketRecvCount is None else timeout)
wsHeaders = tuple("%s: %s" % (getUnicode(key), getUnicode(value)) for key, value in headers.items() if getUnicode(key).upper() != HTTP_HEADER.HOST.upper()) wsHeaders = tuple("%s: %s" % (getUnicode(key), getUnicode(value)) for key, value in headers.items() if getUnicode(key).upper() != HTTP_HEADER.HOST.upper())
ws.connect(url, header=wsHeaders, cookie=cookie) # WebSocket will add Host field of headers automatically ws.connect(url, header=wsHeaders, cookie=cookie, hostHeader=getHeader(headers, HTTP_HEADER.HOST) if customWsHost else None)
ws.send(urldecode(post or "")) ws.send(urldecode(post or ""))
_page = [] _page = []
@@ -613,17 +619,17 @@ class Connect(object):
warnMsg = "too large websocket response detected. Automatically trimming it" warnMsg = "too large websocket response detected. Automatically trimming it"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
break break
except websocket.WebSocketTimeoutException: except (websocket.WebSocketTimeoutException, websocket.WebSocketNormalClosureException):
kb.webSocketRecvCount = len(_page) kb.webSocketRecvCount = len(_page)
break break
else: else:
for i in xrange(max(1, kb.webSocketRecvCount)): for i in xrange(max(1, kb.webSocketRecvCount)):
# Note: a later response may carry fewer frames than the calibration one # Note: a later response may carry fewer frames than the calibration one
# (e.g. a FALSE boolean-blind reply); stop on timeout instead of letting it # (e.g. a FALSE boolean-blind reply); stop on timeout or normal closure,
# bubble up as a failed request, mirroring the initial recv loop above # mirroring the initial recv loop above
try: try:
_page.append(ws.recv()) _page.append(ws.recv())
except websocket.WebSocketTimeoutException: except (websocket.WebSocketTimeoutException, websocket.WebSocketNormalClosureException):
break break
page = "\n".join(_page) page = "\n".join(_page)
+12 -2
View File
@@ -39,6 +39,9 @@ class WebSocketTimeoutException(WebSocketException):
class WebSocketConnectionClosedException(WebSocketException): class WebSocketConnectionClosedException(WebSocketException):
pass pass
class WebSocketNormalClosureException(WebSocketConnectionClosedException):
pass
class WebSocket(object): class WebSocket(object):
""" """
Minimal RFC 6455 client exposing the websocket-client subset used by sqlmap: settimeout(), connect(), Minimal RFC 6455 client exposing the websocket-client subset used by sqlmap: settimeout(), connect(),
@@ -58,7 +61,7 @@ class WebSocket(object):
if self.sock is not None: if self.sock is not None:
self.sock.settimeout(timeout) self.sock.settimeout(timeout)
def connect(self, url, header=None, cookie=None): def connect(self, url, header=None, cookie=None, hostHeader=None):
parts = _urllib.parse.urlsplit(url) parts = _urllib.parse.urlsplit(url)
secure = parts.scheme == "wss" secure = parts.scheme == "wss"
host = parts.hostname host = parts.hostname
@@ -73,8 +76,10 @@ class WebSocket(object):
self.sock.settimeout(self._timeout) self.sock.settimeout(self._timeout)
hostport = "[%s]" % host if ":" in host else host # bracket IPv6 literals hostport = "[%s]" % host if ":" in host else host # bracket IPv6 literals
if port not in (80, 443): if port != (443 if secure else 80): # Note: only the scheme's own default port gets omitted
hostport = "%s:%d" % (hostport, port) hostport = "%s:%d" % (hostport, port)
if hostHeader is not None:
hostport = getText(hostHeader)
key = getText(base64.b64encode(os.urandom(16))) key = getText(base64.b64encode(os.urandom(16)))
lines = ["GET %s HTTP/1.1" % resource, lines = ["GET %s HTTP/1.1" % resource,
@@ -157,10 +162,14 @@ class WebSocket(object):
def recv(self): def recv(self):
data = bytearray() data = bytearray()
receivedFragment = False
while True: while True:
fin, opcode, payload = self._recvFrame() fin, opcode, payload = self._recvFrame()
if opcode == OPCODE_CLOSE: if opcode == OPCODE_CLOSE:
self._closed = True self._closed = True
normal = not payload or (len(payload) >= 2 and struct.unpack("!H", bytes(payload[:2]))[0] in (1000, 1001))
if normal and not receivedFragment:
raise WebSocketNormalClosureException("WebSocket connection closed")
raise WebSocketConnectionClosedException("WebSocket connection closed") raise WebSocketConnectionClosedException("WebSocket connection closed")
elif opcode == OPCODE_PING: elif opcode == OPCODE_PING:
self._sendFrame(bytes(payload), OPCODE_PONG) self._sendFrame(bytes(payload), OPCODE_PONG)
@@ -169,6 +178,7 @@ class WebSocket(object):
continue continue
data += payload data += payload
receivedFragment = True
if fin: if fin:
break break
+14 -8
View File
@@ -36,10 +36,10 @@ class HTTPCollector(object):
def setExtendedArguments(self, arguments): def setExtendedArguments(self, arguments):
self.extendedArguments = arguments self.extendedArguments = arguments
def collectRequest(self, requestMessage, responseMessage, startTime=None, endTime=None): def collectRequest(self, requestMessage, responseMessage, startTime=None, endTime=None, scheme=None):
self.messages.append(RawPair(requestMessage, responseMessage, self.messages.append(RawPair(requestMessage, responseMessage,
startTime=startTime, endTime=endTime, startTime=startTime, endTime=endTime,
extendedArguments=self.extendedArguments)) extendedArguments=self.extendedArguments, scheme=scheme))
def obtain(self): def obtain(self):
return {"log": { return {"log": {
@@ -49,15 +49,16 @@ class HTTPCollector(object):
}} }}
class RawPair(object): class RawPair(object):
def __init__(self, request, response, startTime=None, endTime=None, extendedArguments=None): def __init__(self, request, response, startTime=None, endTime=None, extendedArguments=None, scheme=None):
self.request = getBytes(request) self.request = getBytes(request)
self.response = getBytes(response) self.response = getBytes(response)
self.startTime = startTime self.startTime = startTime
self.endTime = endTime self.endTime = endTime
self.extendedArguments = extendedArguments or {} self.extendedArguments = extendedArguments or {}
self.scheme = scheme # Note: of the target at the time of the request (as origin-form request lines don't carry one)
def toEntry(self): def toEntry(self):
return Entry(request=Request.parse(self.request), response=Response.parse(self.response), return Entry(request=Request.parse(self.request, self.scheme), response=Response.parse(self.response),
startTime=self.startTime, endTime=self.endTime, startTime=self.startTime, endTime=self.endTime,
extendedArguments=self.extendedArguments) extendedArguments=self.extendedArguments)
@@ -86,7 +87,7 @@ class Entry(object):
return out return out
class Request(object): class Request(object):
def __init__(self, method, path, httpVersion, headers, postBody=None, raw=None, comment=None): def __init__(self, method, path, httpVersion, headers, postBody=None, raw=None, comment=None, scheme=None):
self.method = method self.method = method
self.path = path self.path = path
self.httpVersion = httpVersion self.httpVersion = httpVersion
@@ -94,9 +95,10 @@ class Request(object):
self.postBody = postBody self.postBody = postBody
self.comment = comment.strip() if comment else comment self.comment = comment.strip() if comment else comment
self.raw = raw self.raw = raw
self.scheme = scheme
@classmethod @classmethod
def parse(cls, raw): def parse(cls, raw, scheme=None):
request = HTTPRequest(raw) request = HTTPRequest(raw)
return cls(method=request.command, return cls(method=request.command,
path=request.path, path=request.path,
@@ -104,12 +106,16 @@ class Request(object):
headers=request.headers, headers=request.headers,
postBody=request.rfile.read(), postBody=request.rfile.read(),
comment=request.comment, comment=request.comment,
raw=raw) raw=raw,
scheme=scheme)
@property @property
def url(self): def url(self):
if re.search(r"(?i)\Ahttps?://", self.path or ""): # Note: absolute-form request target (e.g. refreshing/crawling/checking requests)
return self.path
host = self.headers.get("Host", "unknown") host = self.headers.get("Host", "unknown")
return "http://%s%s" % (host, self.path) return "%s://%s%s" % (self.scheme or "http", host, self.path)
def toDict(self): def toDict(self):
out = { out = {
+2 -2
View File
@@ -12,10 +12,10 @@ url =
# oracle://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_SID # oracle://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_SID
direct = direct =
# Parse targets from Burp or WebScarab logs # Parse targets from Burp, WebScarab or HAR logs
# Valid: Burp proxy (http://portswigger.net/suite/) requests log file path # Valid: Burp proxy (http://portswigger.net/suite/) requests log file path
# or WebScarab proxy (http://www.owasp.org/index.php/Category:OWASP_WebScarab_Project) # or WebScarab proxy (http://www.owasp.org/index.php/Category:OWASP_WebScarab_Project)
# 'conversations/' folder path # 'conversations/' folder path or HAR (HTTP Archive) file path
logFile = logFile =
# Scan multiple targets enlisted in a given textual file # Scan multiple targets enlisted in a given textual file
+17
View File
@@ -21,6 +21,7 @@ stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x.
import atexit import atexit
import base64 import base64
import json
import os import os
import shutil import shutil
import sys import sys
@@ -537,6 +538,22 @@ class TestParseRequestFileBurp(unittest.TestCase):
self.assertEqual([_[0] for _ in targets], ["https://www.target.com:8443/vuln.php?id=1", "http://www.target.com:443/vuln.php?id=1", "https://www.target.com:443/vuln.php?id=1", "http://www.target.com:8080/vuln.php?id=1"]) self.assertEqual([_[0] for _ in targets], ["https://www.target.com:8443/vuln.php?id=1", "http://www.target.com:443/vuln.php?id=1", "https://www.target.com:443/vuln.php?id=1", "http://www.target.com:8080/vuln.php?id=1"])
self.assertTrue(all("://" not in value for _ in targets for key, value in _[4] if key == "Host")) self.assertTrue(all("://" not in value for _ in targets for key, value in _[4] if key == "Host"))
def test_har_log(self):
# Note: HAR (HTTP Archive) logs (e.g. ZAP, browsers), including HTTP/2 entries (pseudo-headers, no Host)
entries = [
{"request": {"method": "GET", "url": "https://www.target.com:8443/vuln.php?id=1", "headers": [{"name": "Host", "value": "www.target.com:8443"}, {"name": "Cookie", "value": "SID=xyz"}]}},
{"request": {"method": "POST", "url": "https://www.target.com/api", "headers": [{"name": ":authority", "value": "www.target.com"}, {"name": "content-type", "value": "application/json"}], "postData": {"text": '{"id": 1}'}}},
{"request": {"method": "GET", "url": "https://www.target.com/logo.png", "headers": []}},
]
path = _write_temp(json.dumps({"log": {"version": "1.2", "entries": entries}}), ".har")
try:
targets = list(parseRequestFile(path))
finally:
os.unlink(path)
self.assertEqual([_[:4] for _ in targets], [("https://www.target.com:8443/vuln.php?id=1", HTTPMETHOD.GET, None, "SID=xyz"), ("https://www.target.com/api", HTTPMETHOD.POST, '{"id": 1}', None)])
self.assertEqual(targets[1][4], (("Host", "www.target.com"), ("content-type", "application/json")))
def test_post_body_captured(self): def test_post_body_captured(self):
content = ( content = (
"======================================================\n" "======================================================\n"
+15
View File
@@ -70,6 +70,21 @@ class TestGetHostHeader(unittest.TestCase):
def test_without_port(self): def test_without_port(self):
self.assertEqual(getHostHeader("http://example.com/path"), "example.com") self.assertEqual(getHostHeader("http://example.com/path"), "example.com")
def test_default_ports_depend_on_scheme(self):
cases = (
("http://example.com:80/path", "example.com"),
("https://example.com:443/path", "example.com"),
("http://example.com:443/path", "example.com:443"),
("https://example.com:80/path", "example.com:80"),
("HTTP://example.com:443/path", "example.com:443"),
("http://[::1]:80/path", "[::1]"),
("https://[::1]:443/path", "[::1]"),
("http://[::1]:443/path", "[::1]:443"),
("https://[::1]:80/path", "[::1]:80"),
)
for url, expected in cases:
self.assertEqual(getHostHeader(url), expected, msg=url)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main(verbosity=2) unittest.main(verbosity=2)
+8
View File
@@ -52,6 +52,14 @@ class TestHTTPCollector(unittest.TestCase):
self.assertEqual(entries[0]["request"]["method"], "GET") self.assertEqual(entries[0]["request"]["method"], "GET")
self.assertEqual(entries[0]["response"]["status"], 200) self.assertEqual(entries[0]["response"]["status"], 200)
def test_request_url_scheme(self):
# Note: origin-form request lines carry no scheme (HTTPS targets used to be logged as 'http://'), while
# absolute-form ones (refreshing/crawling/checking requests) used to get the Host prepended to them
c = H.HTTPCollector()
for line, scheme in (("GET /?id=1", "https"), ("GET /?id=1", None), ("GET https://other.com:8443/x?id=1", "http")):
c.collectRequest(("%s HTTP/1.1\r\nHost: example.com\r\n\r\n" % line).encode(), b"HTTP/1.1 200 OK\r\n\r\n", startTime=1000, endTime=2000, scheme=scheme)
self.assertEqual([_["request"]["url"] for _ in c.obtain()["log"]["entries"]], ["https://example.com/?id=1", "http://example.com/?id=1", "https://other.com:8443/x?id=1"])
class TestHTTPCollectorFactory(unittest.TestCase): class TestHTTPCollectorFactory(unittest.TestCase):
def test_create_returns_collector(self): def test_create_returns_collector(self):
+13
View File
@@ -259,6 +259,19 @@ class TestDbmsScalars(unittest.TestCase):
self.assertEqual(restored, value) self.assertEqual(restored, value)
self.assertIs(type(restored), type(value)) self.assertIs(type(restored), type(value))
def test_timezone_aware_kept_as_displayed(self):
# Note: a resumed '-d' session used to drop the offset (e.g. '+05:30'), as only naive fields got stored
class _Offset(datetime.tzinfo):
def utcoffset(self, dt):
return datetime.timedelta(hours=5, minutes=30)
def dst(self, dt):
return datetime.timedelta(0)
for value in (datetime.datetime(2024, 1, 2, 3, 4, 0, tzinfo=_Offset()), datetime.time(3, 4, 0, tzinfo=_Offset())):
self.assertEqual(rt(value), getUnicode(value))
self.assertTrue(rt(value).endswith("+05:30"))
class TestRealSessionObjects(unittest.TestCase): class TestRealSessionObjects(unittest.TestCase):
# the exact shapes written with serialize=True across the codebase # the exact shapes written with serialize=True across the codebase
+114
View File
@@ -33,6 +33,9 @@ from lib.request.websocket import (
OPCODE_PING, OPCODE_PING,
OPCODE_CLOSE, OPCODE_CLOSE,
) )
from lib.request.connect import Connect
_GET_PAGE = Connect.__dict__["getPage"]
class _FakeSock(object): class _FakeSock(object):
@@ -133,6 +136,117 @@ class TestWebSocket(unittest.TestCase):
ws = _client(); ws.sock = _RaisingSock(_ssl.SSLError("decryption failed")) ws = _client(); ws.sock = _RaisingSock(_ssl.SSLError("decryption failed"))
self.assertRaises(_ssl.SSLError, ws.recv) self.assertRaises(_ssl.SSLError, ws.recv)
def test_handshake_host_port(self):
# Note: only the scheme's own default port is omitted (port 443 used to be dropped for 'ws://' too)
import lib.request.websocket as W
class _Sent(Exception):
pass
class _Capture(_FakeSock):
def settimeout(self, value):
pass
def sendall(self, data):
raise _Sent(data)
original = W.socket.create_connection
try:
W.socket.create_connection = lambda *args, **kwargs: _Capture()
for url, host in (("ws://example.com:443/x", "example.com:443"), ("ws://example.com:80/x", "example.com"), ("ws://example.com:8080/x", "example.com:8080")):
ws = WebSocket.__new__(WebSocket)
ws._timeout = None
try:
ws.connect(url)
except _Sent as ex:
self.assertIn(("Host: %s\r\n" % host).encode(), ex.args[0])
else:
self.fail("handshake was not sent")
finally:
W.socket.create_connection = original
def test_getpage_preserves_host_header(self):
import socket
import tempfile
import lib.request.websocket as W
from lib.core.data import conf, kb
from lib.core.option import _setKnowledgeBaseAttributes
from lib.core.threads import getCurrentThreadData
class _HandshakeSock(_FakeSock):
def __init__(self):
_FakeSock.__init__(self)
self.handshake = None
self.response = b""
def settimeout(self, value):
pass
def sendall(self, data):
if self.handshake is None:
self.handshake = data
key = next(line.split(b":", 1)[1].strip() for line in data.split(b"\r\n") if line.lower().startswith(b"sec-websocket-key:"))
accept = base64.b64encode(hashlib.sha1(key + _GUID.encode("ascii")).digest())
self.response = b"HTTP/1.1 101 Switching Protocols\r\nSec-WebSocket-Accept: " + accept + b"\r\n\r\n" + _serverFrame("ok")
def recv(self, size):
result, self.response = self.response[:size], self.response[size:]
return result
sockets = []
def create_socket(*args, **kwargs):
sock = _HandshakeSock()
sockets.append(sock)
return sock
oldConf, oldKb = dict(conf), dict(kb)
threadData = getCurrentThreadData()
oldThreadData = dict(threadData.__dict__)
oldTimeout = socket.getdefaulttimeout()
original = W.socket.create_connection
oldGetPage = Connect.__dict__["getPage"]
fd, headersFile = tempfile.mkstemp(prefix="sqlmap-ws-headers-")
try:
with os.fdopen(fd, "w") as f:
f.write("hOsT: file.example\n")
threadData.__dict__.clear()
threadData.reset()
_setKnowledgeBaseAttributes(False)
kb.wizardMode, kb.webSocketRecvCount = True, 1
conf.url = "ws://example.test:8080/test"
conf.hostname, conf.scheme, conf.port, conf.path = "example.test", "ws", 8080, "/test"
conf.parameters, conf.data = {}, None
conf.abortCode, conf.ignoreCode = [], []
conf.timeout, conf.batch, conf.keepAlive = 3, True, False
conf.retryOn, conf.ignoreRedirects, conf.scope = None, False, None
W.socket.create_connection = create_socket
Connect.getPage = _GET_PAGE
cases = (
("ws://example.test:8080/test", [], None, None, None, False, "example.test:8080"),
("ws://user:[email protected]:8080/test", [], None, None, None, False, "example.test:8080"),
("ws://example.test:8080/test", [('hOsT', 'configured.example')], None, None, None, False, "configured.example"),
("ws://example.test:8080/test", [('Host', 'configured.example')], "override.example:8443", None, None, False, "override.example:8443"),
("ws://example.test:8080/test", [], None, {'Host': 'aux.example'}, None, False, "aux.example"),
("ws://example.test:8080/test", [], None, None, headersFile, False, "file.example"),
("ws://example.test:8080/test", [], None, None, None, True, "localhost"),
)
for url, headers, host, aux, path, localhost, expected in cases:
conf.url = url
conf.httpHeaders = headers
conf.localhost, kb.headersFile = localhost, path
self.assertEqual(Connect.getPage(url=url, host=host, auxHeaders=aux)[0], "ok")
hostLines = [line for line in sockets[-1].handshake.split(b"\r\n") if line.lower().startswith(b"host:")]
self.assertEqual(len(hostLines), 1)
self.assertEqual(hostLines[0].split(b":", 1)[1].strip(), expected.encode("ascii"))
finally:
Connect.getPage = oldGetPage
W.socket.create_connection = original
socket.setdefaulttimeout(oldTimeout)
conf.clear(); conf.update(oldConf)
kb.clear(); kb.update(oldKb)
threadData.__dict__.clear(); threadData.__dict__.update(oldThreadData)
os.unlink(headersFile)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env python
"""
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
See the file 'LICENSE' for copying permission
"""
import base64
import hashlib
import os
import socket
import struct
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _testutils import bootstrap
bootstrap()
from lib.core.data import conf, kb
from lib.core.exception import SqlmapConnectionException
from lib.core.option import _setKnowledgeBaseAttributes
from lib.core.threads import getCurrentThreadData
from lib.request.connect import Connect
import lib.request.websocket as websocket
_GET_PAGE = Connect.__dict__["getPage"]
def _frame(payload, opcode=1, fin=1):
return bytes(bytearray([(fin << 7) | opcode, len(payload)])) + payload
class _Socket(object):
def __init__(self, frames):
self.frames = frames
self.response = b""
self.handshake = None
def settimeout(self, value):
pass
def sendall(self, data):
if self.handshake is None:
self.handshake = data
key = next(line.split(b":", 1)[1].strip() for line in data.split(b"\r\n") if line.lower().startswith(b"sec-websocket-key:"))
accept = base64.b64encode(hashlib.sha1(key + websocket._GUID.encode("ascii")).digest())
self.response = b"HTTP/1.1 101 Switching Protocols\r\nSec-WebSocket-Accept: " + accept + b"\r\n\r\n" + self.frames
def recv(self, size):
result, self.response = self.response[:size], self.response[size:]
return result
def close(self):
pass
class TestWebSocketResponseClose(unittest.TestCase):
def setUp(self):
self.oldConf, self.oldKb = dict(conf), dict(kb)
self.threadData = getCurrentThreadData()
self.oldThreadData = dict(self.threadData.__dict__)
self.oldTimeout = socket.getdefaulttimeout()
self.oldSocket = websocket.socket.create_connection
self.oldGetPage = Connect.__dict__["getPage"]
self.threadData.__dict__.clear()
self.threadData.reset()
_setKnowledgeBaseAttributes(False)
kb.wizardMode = True
conf.url = "ws://example.test:8080/test"
conf.hostname, conf.scheme, conf.port, conf.path = "example.test", "ws", 8080, "/test"
conf.httpHeaders, conf.parameters, conf.data = [], {}, None
conf.abortCode, conf.ignoreCode = [], []
conf.timeout, conf.batch, conf.keepAlive, conf.retries = 3, True, False, 0
conf.retryOn, conf.ignoreRedirects, conf.scope = None, False, None
Connect.getPage = _GET_PAGE
websocket.socket.create_connection = lambda *args, **kwargs: _Socket(self.frames)
def tearDown(self):
Connect.getPage = self.oldGetPage
websocket.socket.create_connection = self.oldSocket
socket.setdefaulttimeout(self.oldTimeout)
conf.clear(); conf.update(self.oldConf)
kb.clear(); kb.update(self.oldKb)
self.threadData.__dict__.clear(); self.threadData.__dict__.update(self.oldThreadData)
def test_normal_close_after_response(self):
for status in (b"", struct.pack("!H", 1000), struct.pack("!H", 1001)):
for count in (None, 3):
self.frames = _frame(b"one") + _frame(b"two") + _frame(status, opcode=8)
kb.webSocketRecvCount = count
self.assertEqual(Connect.getPage(url=conf.url)[0], "one\ntwo")
if count is None:
self.assertEqual(kb.webSocketRecvCount, 2)
def test_normal_close_without_response(self):
self.frames = _frame(struct.pack("!H", 1000), opcode=8)
kb.webSocketRecvCount = None
self.assertEqual(Connect.getPage(url=conf.url)[0], "")
self.assertEqual(kb.webSocketRecvCount, 0)
def test_abnormal_close_remains_an_error(self):
for status in (b"\x03", struct.pack("!H", 1002), struct.pack("!H", 1011)):
self.frames = _frame(b"one") + _frame(status, opcode=8)
for count in (None, 3):
kb.webSocketRecvCount = count
self.assertRaises(SqlmapConnectionException, Connect.getPage, url=conf.url)
def test_truncated_frame_remains_an_error(self):
for frames in (_frame(b"one"), _frame(b"one") + b"\x81\x05ab"):
self.frames = frames
for count in (None, 3):
kb.webSocketRecvCount = count
self.assertRaises(SqlmapConnectionException, Connect.getPage, url=conf.url)
def test_close_during_fragmented_message_remains_an_error(self):
for fragment in (b"", b"partial"):
self.frames = _frame(fragment, fin=0) + _frame(struct.pack("!H", 1000), opcode=8)
kb.webSocketRecvCount = None
self.assertRaises(SqlmapConnectionException, Connect.getPage, url=conf.url)
if __name__ == "__main__":
unittest.main()