Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from thirdparty import six

# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.10.7.46"
VERSION = "1.10.7.51"
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
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)
Expand Down
2 changes: 1 addition & 1 deletion lib/core/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def vulnTest(tests=None, label="vuln"):
("-u <base> --data=\"aWQ9MQ==\" --flush-session --base64=POST -v 6", ("aWQ9MTtXQUlURk9SIERFTEFZICcwOjA",)),
("-u <url> --flush-session --parse-errors --test-filter=\"subquery\" --eval=\"import hashlib; id2=2; id3=hashlib.md5(id.encode()).hexdigest()\" --referer=\"localhost\"", ("might be injectable", ": syntax error", "back-end DBMS: SQLite", "WHERE or HAVING clause (subquery")),
("-u <url> --technique=BU --banner --schema --dump -T users --binary-fields=surname --where \"id>3\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "27 entries", "6E616D6569736E756C6C")),
("-u <url> --technique=U --fresh-queries --force-partial --dump -T users --dump-format=HTML --answers=\"crack=n\" -v 3", ("performed 31 queries", "nameisnull", "~using default dictionary", "dumped to HTML file")),
("-u <url> --technique=U --fresh-queries --force-partial --disable-json --dump -T users --dump-format=HTML --answers=\"crack=n\" -v 3", ("LIMIT 0,1)", "nameisnull", "~using default dictionary", "dumped to HTML file")),
("-u <url> --flush-session --technique=BU --all", ("30 entries", "Type: boolean-based blind", "Type: UNION query", "luther", "blisset", "fluffy", "179ad45c6ce2cb97cf1029e212046e81", "NULL", "nameisnull", "testpass")),
("-u <url> --flush-session --technique=B --keyset --dump -T users", ("using keyset (seek) pagination", "30 entries", "luther", "nameisnull")), # keyset/seek dump via the SQLite rowid cursor
("-u <url> -z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT * FROM users\"", ("SELECT * FROM users [30]", "nameisnull")),
Expand Down
18 changes: 18 additions & 0 deletions lib/request/http2.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,12 @@ def encode(self, headers):
SETTINGS_INITIAL_WINDOW_SIZE = 0x4
BIG_WINDOW = (1 << 31) - 1

# Upper bound on the response bytes (body or header block) buffered per stream. The client advertises a
# ~2GB flow-control window, so without this a large (or hostile) server would drive the whole body into
# memory and OOM the process. Mirrors the HTTP/1.1 path's MAX_CONNECTION_TOTAL_SIZE (100MB) cap in
# connect.py; a stream that exceeds it is truncated (body) or abandoned (headers) and its connection retired.
MAX_RESPONSE_SIZE = 100 * 1024 * 1024

def _recv_exact(sock, n):
buf = b""
while len(buf) < n:
Expand Down Expand Up @@ -524,6 +530,9 @@ def exchange(self, method, path, authority, headers, body, timeout):
if flags & FLAG_PRIORITY:
p = p[5:]
header_block += p
if len(header_block) > MAX_RESPONSE_SIZE: # hostile/endless header block -> bail rather than OOM
self.usable = False
raise IOError("oversized HTTP/2 header block")
if flags & FLAG_END_HEADERS:
resp_headers = self.dec.decode(header_block)
if flags & FLAG_END_STREAM:
Expand All @@ -533,6 +542,9 @@ def exchange(self, method, path, authority, headers, body, timeout):
if flags & FLAG_PADDED:
p = p[1:len(p) - bytearray(payload)[0]]
resp_body += p
if len(resp_body) > MAX_RESPONSE_SIZE: # cap like the HTTP/1.1 path; stop reading and retire the
self.usable = False # connection (leftover frames abandoned) instead of OOM
break
if payload: # replenish stream + connection windows
self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, sid, struct.pack("!I", len(payload))))
self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", len(payload))))
Expand Down Expand Up @@ -603,6 +615,9 @@ def exchange_pair(self, requests, timeout):
if flags & FLAG_PRIORITY:
p = p[5:]
state[fsid]["hb"] += p
if len(state[fsid]["hb"]) > MAX_RESPONSE_SIZE:
self.usable = False
raise IOError("oversized HTTP/2 header block during timeless pair")
if flags & FLAG_END_HEADERS:
state[fsid]["headers"] = self.dec.decode(state[fsid]["hb"])
if flags & FLAG_END_STREAM and fsid in remaining:
Expand All @@ -612,6 +627,9 @@ def exchange_pair(self, requests, timeout):
if flags & FLAG_PADDED:
p = p[1:len(p) - bytearray(payload)[0]]
state[fsid]["body"] += p
if len(state[fsid]["body"]) > MAX_RESPONSE_SIZE: # cap the buffered body (mirrors exchange())
self.usable = False
raise IOError("oversized response during timeless pair")
if payload:
self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, fsid, struct.pack("!I", len(payload))))
self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", len(payload))))
Expand Down
12 changes: 9 additions & 3 deletions lib/request/redirecthandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,20 @@ def http_error_302(self, req, fp, code, msg, headers):
redurl = self._get_header_redirect(headers) if not conf.ignoreRedirects else None

try:
content = fp.fp.read(MAX_CONNECTION_TOTAL_SIZE)
fp.fp = io.BytesIO(content)
# Note: drain via the length-aware response (honors Content-Length/chunked), NOT the raw
# socket 'fp.fp' - the latter has no HTTP framing, so on a Keep-Alive connection (no EOF)
# it blocks for the whole '--timeout' on every redirect (Issue #6080)
content = fp.read(MAX_CONNECTION_TOTAL_SIZE)
except _http_client.IncompleteRead as ex:
content = ex.partial
fp.fp = io.BytesIO(content)
except:
content = b""

# back 'fp' with the captured body so it stays re-readable downstream (Issue #5985); the
# length-aware read above has consumed the response, so restore both the buffer and read()
fp.fp = io.BytesIO(content)
fp.read = fp.fp.read

content = decodePage(content, headers.get(HTTP_HEADER.CONTENT_ENCODING), headers.get(HTTP_HEADER.CONTENT_TYPE))

threadData = getCurrentThreadData()
Expand Down
9 changes: 5 additions & 4 deletions lib/techniques/union/use.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from lib.core.common import isNumPosStrValue
from lib.core.common import listToStrValue
from lib.core.common import parseUnionPage
from lib.core.common import randomStr
from lib.core.common import removeReflectiveValues
from lib.core.common import singleTimeDebugMessage
from lib.core.common import singleTimeWarnMessage
Expand Down Expand Up @@ -131,7 +132,7 @@ def _oneShotUnionUse(expression, unpack=True, limited=False):
else:
retVal = getUnicode(retVal)
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.H2, DBMS.HSQLDB, DBMS.FIREBIRD):
output = extractRegexResult(r"(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop), removeReflectiveValues(_page, payload))
output = extractRegexResult(r"(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop), removeReflectiveValues(_page, payload), re.DOTALL)
if output:
retVal = output
else:
Expand Down Expand Up @@ -296,7 +297,7 @@ def _chunkedJsonAggUse(expression, expressionFields, expressionFieldsList, count
offset = 0

while offset < count:
query = "SELECT %s FROM (%s %s) sqmapx" % (aggExpr, base, window(offset, chunk))
query = "SELECT %s FROM (%s %s) %s" % (aggExpr, base, window(offset, chunk), randomStr())

kb.jsonAggMode = True
output = _oneShotUnionUse(query, False)
Expand Down Expand Up @@ -347,7 +348,7 @@ def unionUse(expression, unpack=True, dump=False):
debugMsg += "it does not play well with UNION query SQL injection"
singleTimeDebugMessage(debugMsg)

if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ORACLE, DBMS.PGSQL, DBMS.MSSQL, DBMS.SQLITE, DBMS.H2, DBMS.HSQLDB, DBMS.FIREBIRD) and expressionFields and not any((conf.binaryFields, conf.limitStart, conf.limitStop, conf.forcePartial, conf.disableJson)):
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ORACLE, DBMS.PGSQL, DBMS.MSSQL, DBMS.SQLITE, DBMS.H2, DBMS.HSQLDB, DBMS.FIREBIRD) and expressionFields and not any((conf.binaryFields, conf.limitStart, conf.limitStop, conf.disableJson)):
match = re.search(r"SELECT\s*(.+?)\bFROM", expression, re.I)
if match and not (Backend.isDbms(DBMS.ORACLE) and FROM_DUMMY_TABLE[DBMS.ORACLE] in expression) and not re.search(r"\b(MIN|MAX|COUNT|EXISTS)\(", expression):
kb.jsonAggMode = True
Expand All @@ -373,7 +374,7 @@ def unionUse(expression, unpack=True, dump=False):
# response cap) and the table is large, retrieve the rows in bounded windows (chunked
# JSON aggregation) before the slow per-row fallback. Done here (independent of the
# detected UNION where-clause) so it engages for any dumpable FROM-table query.
if value is None and " FROM " in expression.upper() and not re.search(SQL_SCALAR_REGEX, expression, re.I) and not any((kb.forcePartialUnion, conf.forcePartial, conf.disableJson, conf.binaryFields, conf.limitStart, conf.limitStop)):
if value is None and " FROM " in expression.upper() and not re.search(SQL_SCALAR_REGEX, expression, re.I) and not any((conf.disableJson, conf.binaryFields, conf.limitStart, conf.limitStop)):
chunkCountExpr = expression.replace(expressionFields, queries[Backend.getIdentifiedDbms()].count.query % '*', 1)
if " ORDER BY " in chunkCountExpr.upper():
chunkCountExpr = chunkCountExpr[:chunkCountExpr.upper().rindex(" ORDER BY ")]
Expand Down