mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-08-04 14:55:40 +00:00
Merge branch 'master' into hana-pr
# Conflicts: # data/txt/sha256sums.txt # lib/core/common.py # lib/core/settings.py
This commit is contained in:
commit
1465d55a47
239 changed files with 57035 additions and 4345 deletions
|
|
@ -51,6 +51,7 @@ from lib.core.settings import DEFAULT_GET_POST_DELIMITER
|
|||
from lib.core.settings import GENERIC_SQL_COMMENT
|
||||
from lib.core.settings import GENERIC_SQL_COMMENT_MARKER
|
||||
from lib.core.settings import INFERENCE_MARKER
|
||||
from lib.core.settings import MYSQL_UNION_VALUE_CAST
|
||||
from lib.core.settings import NULL
|
||||
from lib.core.settings import PAYLOAD_DELIMITER
|
||||
from lib.core.settings import REPLACEMENT_MARKER
|
||||
|
|
@ -69,9 +70,9 @@ class Agent(object):
|
|||
query = self.cleanupPayload(query)
|
||||
|
||||
if query.upper().startswith("AND "):
|
||||
query = re.sub(r"(?i)AND ", "SELECT ", query, 1)
|
||||
query = re.sub(r"(?i)AND ", "SELECT ", query, count=1)
|
||||
elif query.upper().startswith(" UNION ALL "):
|
||||
query = re.sub(r"(?i) UNION ALL ", "", query, 1)
|
||||
query = re.sub(r"(?i) UNION ALL ", "", query, count=1)
|
||||
elif query.startswith("; "):
|
||||
query = query.replace("; ", "", 1)
|
||||
|
||||
|
|
@ -220,7 +221,7 @@ class Agent(object):
|
|||
elif BOUNDED_INJECTION_MARKER in paramDict[parameter]:
|
||||
if base64Encoding:
|
||||
retVal = paramString.replace("%s%s" % (_origValue, BOUNDED_INJECTION_MARKER), _newValue)
|
||||
match = re.search(r"(%s)=([^&]*)" % re.sub(r" \(.+", "", parameter), retVal)
|
||||
match = re.search(r"(%s)=([^&]*)" % re.escape(re.sub(r" \(.+", "", parameter)), retVal)
|
||||
if match:
|
||||
retVal = retVal.replace(match.group(0), "%s=%s" % (match.group(1), encodeBase64(match.group(2), binary=False, encoding=conf.encoding or UNICODE_ENCODING)))
|
||||
else:
|
||||
|
|
@ -676,6 +677,49 @@ class Agent(object):
|
|||
pass
|
||||
return retVal
|
||||
|
||||
@staticmethod
|
||||
def _collapseFieldDelimiterSpace(query):
|
||||
"""
|
||||
Collapses ", " into "," to normalize the column-list delimiter, but ONLY outside
|
||||
single/double quoted string literals, so a comma-space inside a literal (e.g. in a
|
||||
WHERE clause: name='John, Jr') is preserved verbatim. The quote/escape handling
|
||||
mirrors splitFields()/zeroDepthSearch().
|
||||
|
||||
>>> Agent._collapseFieldDelimiterSpace("SELECT a, b FROM t")
|
||||
'SELECT a,b FROM t'
|
||||
>>> Agent._collapseFieldDelimiterSpace("SELECT a, b FROM t WHERE name='John, Jr'")
|
||||
"SELECT a,b FROM t WHERE name='John, Jr'"
|
||||
"""
|
||||
|
||||
retVal = []
|
||||
quote = None
|
||||
index = 0
|
||||
length = len(query)
|
||||
|
||||
while index < length:
|
||||
char = query[index]
|
||||
if quote:
|
||||
retVal.append(char)
|
||||
if char == quote:
|
||||
if index + 1 < length and query[index + 1] == quote: # escaped quote (e.g. '')
|
||||
retVal.append(query[index + 1])
|
||||
index += 2
|
||||
continue
|
||||
else:
|
||||
quote = None
|
||||
elif char in ('"', "'"):
|
||||
quote = char
|
||||
retVal.append(char)
|
||||
elif char == ',' and index + 1 < length and query[index + 1] == ' ':
|
||||
retVal.append(',') # keep the delimiter, drop the single trailing space
|
||||
index += 2
|
||||
continue
|
||||
else:
|
||||
retVal.append(char)
|
||||
index += 1
|
||||
|
||||
return "".join(retVal)
|
||||
|
||||
def concatQuery(self, query, unpack=True):
|
||||
"""
|
||||
Take in input a query string and return its processed nulled,
|
||||
|
|
@ -704,7 +748,7 @@ class Agent(object):
|
|||
|
||||
if unpack:
|
||||
concatenatedQuery = ""
|
||||
query = query.replace(", ", ',')
|
||||
query = self._collapseFieldDelimiterSpace(query)
|
||||
fieldsSelectFrom, fieldsSelect, fieldsNoSelect, fieldsSelectTop, fieldsSelectCase, _, fieldsToCastStr, fieldsExists = self.getFields(query)
|
||||
castedFields = self.nullCastConcatFields(fieldsToCastStr)
|
||||
concatenatedQuery = query.replace(fieldsToCastStr, castedFields, 1)
|
||||
|
|
@ -825,9 +869,9 @@ class Agent(object):
|
|||
|
||||
return concatenatedQuery
|
||||
|
||||
def forgeUnionQuery(self, query, position, count, comment, prefix, suffix, char, where, multipleUnions=None, limited=False, fromTable=None):
|
||||
def forgeUnionQuery(self, query, position, count, comment, prefix, suffix, char, where, multipleUnions=None, limited=False, fromTable=None, collate=False):
|
||||
"""
|
||||
Take in input an query (pseudo query) string and return its
|
||||
Take in input a query (pseudo query) string and return its
|
||||
processed UNION ALL SELECT query.
|
||||
|
||||
Examples:
|
||||
|
|
@ -867,10 +911,21 @@ class Agent(object):
|
|||
if query.startswith("SELECT "):
|
||||
query = query[len("SELECT "):]
|
||||
|
||||
# On MySQL 8+ the retrieved value (connection collation) cannot be merged in a
|
||||
# UNION column with a table column of a different collation (e.g. utf8mb4_0900_ai_ci),
|
||||
# raising "Illegal mix of collations". Normalizing the charset and forcing an explicit
|
||||
# collation (highest coercibility) wins the merge (Note: skipped for NULL/numeric values).
|
||||
# Note: requires the utf8mb4 charset (MySQL >= 5.5.3) used in MYSQL_UNION_VALUE_CAST; on
|
||||
# older versions there is no such collation clash to begin with (unknown version => assumed recent).
|
||||
collateField = collate and Backend.isDbms(DBMS.MYSQL) and isDBMSVersionAtLeast('5.5.3') is not False
|
||||
|
||||
def _collate(value):
|
||||
return MYSQL_UNION_VALUE_CAST % value if collateField and value and value != NULL and not value.isdigit() else value
|
||||
|
||||
unionQuery = self.prefixQuery("UNION ALL SELECT ", prefix=prefix)
|
||||
|
||||
if limited:
|
||||
unionQuery += ','.join(char if _ != position else '(SELECT %s)' % query for _ in xrange(0, count))
|
||||
unionQuery += ','.join(char if _ != position else _collate('(SELECT %s)' % query) for _ in xrange(0, count))
|
||||
unionQuery += fromTable
|
||||
unionQuery = self.suffixQuery(unionQuery, comment, suffix)
|
||||
|
||||
|
|
@ -900,12 +955,22 @@ class Agent(object):
|
|||
else:
|
||||
infoFile = None
|
||||
|
||||
if not infoFile:
|
||||
query = _collate(query)
|
||||
|
||||
# A fuzzy-discovered per-column type template (kb.unionTemplate, e.g. ['1234', '%s', '5678'])
|
||||
# forces type-compatible fillers on strict DBMSes (e.g. Apache Derby, which rejects bare NULL
|
||||
# and demands UNION column-type parity); '%s' marks the slot carrying the injected expression.
|
||||
template = kb.unionTemplate if isinstance(kb.unionTemplate, (list, tuple)) and len(kb.unionTemplate) == count else None
|
||||
|
||||
for element in xrange(0, count):
|
||||
if element > 0:
|
||||
unionQuery += ','
|
||||
|
||||
if conf.uValues and conf.uValues.count(',') + 1 == count:
|
||||
unionQuery += conf.uValues.split(',')[element]
|
||||
elif template is not None:
|
||||
unionQuery += query if template[element] == "%s" else template[element]
|
||||
elif element == position:
|
||||
unionQuery += query
|
||||
else:
|
||||
|
|
@ -927,8 +992,10 @@ class Agent(object):
|
|||
if element > 0:
|
||||
unionQuery += ','
|
||||
|
||||
if element == position:
|
||||
unionQuery += multipleUnions
|
||||
if template is not None:
|
||||
unionQuery += _collate(multipleUnions) if template[element] == "%s" else template[element]
|
||||
elif element == position:
|
||||
unionQuery += _collate(multipleUnions)
|
||||
else:
|
||||
unionQuery += char
|
||||
|
||||
|
|
@ -964,7 +1031,9 @@ class Agent(object):
|
|||
stopLimit = limitRegExp.group(int(limitGroupStop))
|
||||
elif limitRegExp2:
|
||||
startLimit = 0
|
||||
stopLimit = limitRegExp2.group(int(limitGroupStart))
|
||||
# Note: query2 (LIMIT without OFFSET) always has exactly one group (the
|
||||
# count); using limitGroupStart here would IndexError for H2 (groupstart=2)
|
||||
stopLimit = limitRegExp2.group(1)
|
||||
limitCond = int(stopLimit) > 1
|
||||
|
||||
elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
|
||||
|
|
@ -1066,7 +1135,7 @@ class Agent(object):
|
|||
original = query.split("SELECT ", 1)[1].split(" FROM", 1)[0]
|
||||
for part in original.split(','):
|
||||
if re.search(r"\b%s\b" % re.escape(field), part):
|
||||
_ = re.sub(r"SELECT.+?FROM", "SELECT %s AS z,row_number() over() AS y FROM" % part, query, 1)
|
||||
_ = re.sub(r"SELECT.+?FROM", "SELECT %s AS z,row_number() over() AS y FROM" % part, query, count=1)
|
||||
replacement = "SELECT x.z FROM (%s)x WHERE x.y-1=%d" % (_, num)
|
||||
limitedQuery = replacement
|
||||
break
|
||||
|
|
@ -1117,7 +1186,7 @@ class Agent(object):
|
|||
limitedQuery = safeStringFormat(limitedQuery, (fromFrom,))
|
||||
limitedQuery += "=%d" % (num + 1)
|
||||
|
||||
elif Backend.isDbms(DBMS.MSSQL):
|
||||
elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
|
||||
forgeNotIn = True
|
||||
|
||||
if " ORDER BY " in limitedQuery:
|
||||
|
|
@ -1266,7 +1335,10 @@ class Agent(object):
|
|||
if Backend.isDbms(DBMS.ORACLE) and re.search(r"qq ORDER BY \w+\)", query, re.I) is not None:
|
||||
prefix, suffix = re.sub(r"(?i)(qq)( ORDER BY \w+\))", r"\g<1> WHERE %s\g<2>" % conf.dumpWhere, query), ""
|
||||
else:
|
||||
match = re.search(r" (LIMIT|ORDER).+", query, re.I)
|
||||
# Note: require a genuine trailing clause (ORDER BY / LIMIT word-bounded), so a
|
||||
# column/identifier merely starting with "order"/"limit" (e.g. order_id) is not
|
||||
# mistaken for the suffix and the WHERE is not spliced into the wrong place
|
||||
match = re.search(r" (ORDER\s+BY\b|LIMIT\b).+", query, re.I)
|
||||
if match:
|
||||
suffix = match.group(0)
|
||||
prefix = query[:-len(suffix)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue