Skip to content

Commit 6e459d6

Browse files
committed
Couple of optimizations
1 parent 154c7e3 commit 6e459d6

5 files changed

Lines changed: 169 additions & 131 deletions

File tree

data/txt/sha256sums.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller
168168
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py
169169
9c5764c92ce536d1f0f96200359ee5ef1f37f9128769bf990cb77f1d1f8e17b1 lib/core/agent.py
170170
c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigarray.py
171-
d143df718fbaacb617b6046c73cf4e47932e1a25928a4e1ecb87ea77a3b154ed lib/core/common.py
171+
751c3bf178e91e60b25e3b01ce7636029804dd78f64e9ee0418bdb126889a7bc lib/core/common.py
172172
8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py
173173
5301ba2204404d086e9a67271cde00fc10214c63b018a95fc5aa90ff9e0b2ad9 lib/core/convert.py
174174
c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py
@@ -189,7 +189,7 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor
189189
9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py
190190
0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py
191191
888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py
192-
516d6b40efa04a5a25b0aa317ea49771f6964a57581777761f82d36d1b1b78b0 lib/core/settings.py
192+
b38aa7769be9d31f2d55172a992732f506f05fba49d6a170eb9485f78da7c360 lib/core/settings.py
193193
c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py
194194
a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py
195195
19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py
@@ -257,7 +257,7 @@ aeefb42ea0c68f72744bc1bfd7194ec1bc06480d8a7e23f4b8d3d23fbba2b014 lib/utils/api.
257257
442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py
258258
da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py
259259
a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps.py
260-
b0d8ae8513c1f5ffcaa4bf0398790f26bc2180a6acf07bf5b2c86555bf9113f6 lib/utils/dialect.py
260+
bd9267d94390ba87d6c5a35c90f2406d6a4135a7c8ea01db76dd9e6519eee2ed lib/utils/dialect.py
261261
51cfab194cd5b6b24d62706fb79db86c852b9e593f4c55c15b35f175e70c9d75 lib/utils/getch.py
262262
3c4ad819589fe4fca303706dc87969273a07a04dee85e23f064b39caf1fb80e9 lib/utils/gui.py
263263
972c5db9c9e30ac0f91c0f8d4df4531d0304e151dac99f1399c37c952ba9f935 lib/utils/har.py
@@ -602,7 +602,7 @@ c17544be5e945dc8c4fbb5c3b922da8eceec30b0fb239c32fb5f40e1660a197f tests/test_dat
602602
8a1edb6dbc000e412ba5cc598e024b669fc76ec0a8fc32136808e6325a018f70 tests/test_dbms_enum.py
603603
3804eb2d730220360f9dc07d5994eb64e9f65acf3b0d8648df8df2a2177ba8fd tests/test_decodepage.py
604604
180e5fd3f75fadf7ac1135f99797314e2cf1f8ae6dced02edfb18ccba43c0148 tests/test_deps.py
605-
b01343eb8aa42ea5c2c483ec028a24f6451aa6f668fdc0c289d5ff9554c277d7 tests/test_dialectdbms.py
605+
fa85881aa8d082a65aeacb2b03fcb5d2abb1daa9a02ee24ff048d54fbc904b90 tests/test_dialectdbms.py
606606
e40a49cfa73c45b3c3c6d1d1d00738861e270cb7a07b28f5a5356f9c7c800cf2 tests/test_dialect.py
607607
993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py
608608
7f9180a53dbf0bb3e52801fdbfffd31f365a0bff77bf90e58d2ef63a0c23026f tests/test_dns_engine.py

lib/core/common.py

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3310,7 +3310,16 @@ def isNumPosStrValue(value):
33103310

33113311
return retVal
33123312

3313-
@cachedmethod
3313+
# DBMS_DICT is static, so the alias -> enum resolution is precomputed once into a
3314+
# lookup table (replacing a per-call @cachedmethod + linear scan). aliasToDbmsEnum()
3315+
# is a hot path (Backend.getIdentifiedDbms() calls it constantly). Building via
3316+
# setdefault in dict order preserves the original first-match-wins semantics.
3317+
_DBMS_ALIAS_MAP = {}
3318+
for _dbmsKey, _dbmsItem in DBMS_DICT.items():
3319+
for _dbmsAlias in _dbmsItem[0]:
3320+
_DBMS_ALIAS_MAP.setdefault(_dbmsAlias, _dbmsKey)
3321+
_DBMS_ALIAS_MAP.setdefault(_dbmsKey.lower(), _dbmsKey)
3322+
33143323
def aliasToDbmsEnum(dbms):
33153324
"""
33163325
Returns major DBMS name from a given alias
@@ -3319,15 +3328,7 @@ def aliasToDbmsEnum(dbms):
33193328
'Microsoft SQL Server'
33203329
"""
33213330

3322-
retVal = None
3323-
3324-
if dbms:
3325-
for key, item in DBMS_DICT.items():
3326-
if dbms.lower() in item[0] or dbms.lower() == key.lower():
3327-
retVal = key
3328-
break
3329-
3330-
return retVal
3331+
return _DBMS_ALIAS_MAP.get(dbms.lower()) if dbms else None
33313332

33323333
def findDynamicContent(firstPage, secondPage, merge=False):
33333334
"""
@@ -4414,7 +4415,11 @@ def safeSQLIdentificatorNaming(name, isTable=False):
44144415

44154416
if isinstance(name, six.string_types):
44164417
retVal = getUnicode(name)
4417-
_ = isTable and Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE)
4418+
# Resolve the identified DBMS once; it is invariant within this call and
4419+
# Backend.getIdentifiedDbms() (which scans DBMS_DICT) was otherwise
4420+
# re-evaluated several times below.
4421+
dbms = Backend.getIdentifiedDbms()
4422+
_ = isTable and dbms in (DBMS.MSSQL, DBMS.SYBASE)
44184423

44194424
if _:
44204425
retVal = re.sub(r"(?i)\A\[?%s\]?\." % DEFAULT_MSSQL_SCHEMA, "%s." % DEFAULT_MSSQL_SCHEMA, retVal)
@@ -4424,13 +4429,13 @@ def safeSQLIdentificatorNaming(name, isTable=False):
44244429
if not conf.noEscape:
44254430
retVal = unsafeSQLIdentificatorNaming(retVal)
44264431

4427-
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER, DBMS.CLICKHOUSE): # Note: in SQLite double-quotes are treated as string if column/identifier is non-existent (e.g. SELECT "foobar" FROM users)
4432+
if dbms in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER, DBMS.CLICKHOUSE): # Note: in SQLite double-quotes are treated as string if column/identifier is non-existent (e.g. SELECT "foobar" FROM users)
44284433
retVal = "`%s`" % retVal
4429-
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE, DBMS.FIREBIRD, DBMS.DERBY, DBMS.MAXDB):
4434+
elif dbms in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE, DBMS.FIREBIRD, DBMS.DERBY, DBMS.MAXDB):
44304435
retVal = "\"%s\"" % retVal
4431-
elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL):
4436+
elif dbms in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL):
44324437
retVal = "\"%s\"" % retVal.upper()
4433-
elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
4438+
elif dbms in (DBMS.MSSQL, DBMS.SYBASE):
44344439
if isTable:
44354440
parts = retVal.split('.', 1)
44364441
for i in xrange(len(parts)):
@@ -4463,16 +4468,21 @@ def unsafeSQLIdentificatorNaming(name):
44634468
retVal = name
44644469

44654470
if isinstance(name, six.string_types):
4466-
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER, DBMS.CLICKHOUSE):
4471+
# Resolve the identified DBMS once; it is invariant within this call, and
4472+
# Backend.getIdentifiedDbms() is not cheap (it scans DBMS_DICT). Previously
4473+
# it was re-evaluated up to five times per call.
4474+
dbms = Backend.getIdentifiedDbms()
4475+
4476+
if dbms in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER, DBMS.CLICKHOUSE):
44674477
retVal = name.replace("`", "")
4468-
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE, DBMS.FIREBIRD, DBMS.DERBY, DBMS.MAXDB):
4478+
elif dbms in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE, DBMS.FIREBIRD, DBMS.DERBY, DBMS.MAXDB):
44694479
retVal = name.replace("\"", "")
4470-
elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL):
4480+
elif dbms in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL):
44714481
retVal = name.replace("\"", "").upper()
4472-
elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
4482+
elif dbms in (DBMS.MSSQL, DBMS.SYBASE):
44734483
retVal = name.replace("[", "").replace("]", "")
44744484

4475-
if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
4485+
if dbms in (DBMS.MSSQL, DBMS.SYBASE):
44764486
retVal = re.sub(r"(?i)\A\[?%s\]?\." % DEFAULT_MSSQL_SCHEMA, "", retVal)
44774487

44784488
return retVal

lib/core/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from thirdparty import six
2121

2222
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
23-
VERSION = "1.10.7.0"
23+
VERSION = "1.10.7.1"
2424
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
2525
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
2626
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)

lib/utils/dialect.py

Lines changed: 56 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@
2828
# OTHER valid rows, which sqlmap's fuzzy page comparison conflates with the anchor row, producing
2929
# false positives. See PROVE_DESIGN.md.)
3030
#
31-
# Truth table measured on a live OWASP-CRS platform across 16 engines (MySQL/MySQL5, MariaDB/TiDB,
32-
# PostgreSQL, CockroachDB, CrateDB, Microsoft SQL Server, SQLite, Firebird, ClickHouse, H2, HSQLDB,
33-
# Derby, MonetDB, IRIS, Trino); only the zero-false-positive rules are kept (see _classify). With
34-
# anchor value 2:
31+
# Signatures were measured against every SQL engine on a live OWASP-CRS platform (MySQL/MySQL5,
32+
# MariaDB/TiDB, PostgreSQL, CockroachDB, CrateDB, Microsoft SQL Server, SQLite, Firebird, ClickHouse,
33+
# H2, HSQLDB, Derby, MonetDB, IRIS, Trino) and encoded as an exact-signature WHITELIST in _classify()
34+
# (only measured signatures classify; anything else -> None). With anchor value 2:
3535
#
3636
# * 2^0=2 -> '^' is bitwise XOR (MySQL/MSSQL/MonetDB: 2^0=2) vs exponentiation (PostgreSQL: 2^0=1)
3737
# vs no such operator (SQLite/Oracle/... -> error, so false)
@@ -52,57 +52,69 @@
5252
("shift", "1<<2=4"),
5353
)
5454

55+
# Canary for the trustworthiness gate: a syntactically-invalid expression (a trailing operator) that
56+
# a real SQL back-end can only read as FALSE - the appended clause is a parse error, the query fails,
57+
# no row. A false-positive / noise channel (a WAF, a reflection, or a backend that ignores the
58+
# injected tail and reads every probe the same) reads it as TRUE, which is proof the boolean oracle
59+
# is trash, so the heuristic returns None (a true negative) rather than a bogus DBMS from a
60+
# meaningless signature. It uses a trailing-operator form, distinct from the '<n> <m>' no-operator
61+
# form already exercised by sqlmap's earlier false-positive check, so it adds new information.
62+
DIALECT_CANARY = "2+"
63+
64+
# Exact operator-dialect signature -> back-end DBMS. Strict WHITELIST re-derived from the live
65+
# measurement above: ONLY these signatures classify; any other - an engine not measured here, or a
66+
# false-positive / noise channel - returns None. This deliberately replaces earlier partial-condition
67+
# rules, which would confidently mis-map physically-impossible signatures onto a DBMS (e.g. the
68+
# all-true 'reads everything as true' noise, where '^' would be XOR and exponentiation at once).
69+
_SIGNATURE_DBMS = {
70+
# xor pgpow intdiv bitor shift
71+
(True, False, False, True, True): DBMS.MYSQL, # MySQL / MariaDB / TiDB
72+
(False, True, True, True, True): DBMS.PGSQL, # PostgreSQL
73+
(False, True, False, True, True): DBMS.PGSQL, # CockroachDB (pgwire; has '<<' -> shift True)
74+
(False, True, True, True, False): DBMS.PGSQL, # CrateDB
75+
(True, False, True, True, False): DBMS.MSSQL, # Microsoft SQL Server (no bit-shift)
76+
(True, False, True, True, True): DBMS.MONETDB, # MonetDB (as MSSQL but has '<<')
77+
(False, False, True, True, True): DBMS.SQLITE, # SQLite
78+
}
79+
5580
def _classify(signature):
5681
"""
57-
Maps a measured (xor, pgpow, intdiv, bitor) operator-dialect signature to a back-end
58-
DBMS, or returns None when the signature does not *uniquely* identify a major DBMS (so
59-
detection proceeds unchanged - the heuristic never wrong-foots the scan).
60-
61-
Rules below are the subset of the measured 11-engine truth table that maps with zero
62-
false positives. Engines whose operator profile is not distinctive enough (Oracle's
63-
all-false signature, which a minimal engine like ClickHouse/H2/Firebird/HSQLDB/Derby or
64-
a fully WAF-blocked channel also produces) deliberately fall through to None:
82+
Maps an exact operator-dialect signature (xor, pgpow, intdiv, bitor, shift) to a back-end DBMS
83+
through a strict whitelist of live-measured signatures, or returns None when the signature is not
84+
a known DBMS fingerprint - an engine not measured, or a noise / false-positive channel - so
85+
detection proceeds unchanged and the heuristic never wrong-foots the scan.
6586
66-
>>> _classify((True, False, False, True, True)) # MySQL / MariaDB / TiDB
87+
>>> _classify((True, False, False, True, True)) # MySQL / MariaDB / TiDB
6788
'MySQL'
68-
>>> _classify((True, False, True, True, False)) # Microsoft SQL Server (no bit-shift)
69-
'Microsoft SQL Server'
70-
>>> _classify((True, False, True, True, True)) # MonetDB (same xor/intdiv as MSSQL, but has '<<')
71-
'MonetDB'
72-
>>> _classify((False, True, True, True, False)) # PostgreSQL
89+
>>> _classify((False, True, True, True, True)) # PostgreSQL
90+
'PostgreSQL'
91+
>>> _classify((False, True, False, True, True)) # CockroachDB -> PostgreSQL family
7392
'PostgreSQL'
74-
>>> _classify((False, True, False, True, False)) # CockroachDB (pgwire) -> PostgreSQL family
93+
>>> _classify((False, True, True, True, False)) # CrateDB -> PostgreSQL family
7594
'PostgreSQL'
76-
>>> _classify((False, False, True, True, True)) # SQLite
95+
>>> _classify((True, False, True, True, False)) # Microsoft SQL Server (no bit-shift)
96+
'Microsoft SQL Server'
97+
>>> _classify((True, False, True, True, True)) # MonetDB (as MSSQL but has '<<')
98+
'MonetDB'
99+
>>> _classify((False, False, True, True, True)) # SQLite
77100
'SQLite'
78-
>>> _classify((False, False, True, False, False)) is None # Firebird/HSQLDB/Derby/H2/Trino -> no prior
101+
>>> _classify((True, True, True, True, True)) is None # 'reads everything true' noise -> None
102+
True
103+
>>> _classify((False, False, False, False, False)) is None # all-false (Oracle/ClickHouse/IRIS/blocked) -> None
79104
True
80-
>>> _classify((False, False, False, False, False)) is None # all-false (Oracle/ClickHouse/IRIS/blocked) -> no prior
105+
>>> _classify((False, False, True, False, False)) is None # Firebird/H2/HSQLDB/Derby/Trino -> not distinctive
81106
True
82107
"""
83108

84-
xor, pgpow, intdiv, bitor, shift = signature
85-
86-
if pgpow: # '^' is exponentiation -> PostgreSQL family
87-
return DBMS.PGSQL
88-
if xor and intdiv: # '^' is XOR AND integer division -> SQL Server ...
89-
# ... except MonetDB shares this exact signature; it alone has a working bit-shift operator
90-
# ('1<<2=4'), SQL Server has none -> split the collision (measured zero-FP across 16 engines).
91-
return DBMS.MONETDB if shift else DBMS.MSSQL
92-
if xor and not intdiv: # '^' is XOR AND real division -> MySQL family
93-
return DBMS.MYSQL
94-
if not xor and intdiv and bitor: # no '^', integer division, bitwise '|' -> SQLite
95-
return DBMS.SQLITE
96-
97-
return None
109+
return _SIGNATURE_DBMS.get(tuple(bool(_) for _ in signature))
98110

99111
def dialectCheckDbms(injection):
100112
"""
101113
Keyword-free back-end DBMS heuristic via operator-dialect differentials, evaluated through the
102114
given (boolean-capable) injection. Complements heuristicCheckDbms() - which is skipped when the
103115
WAF/IPS is dropping requests and otherwise relies on SELECT/quote payloads - because every probe
104-
here is built from operator semantics alone. Returns the DBMS name or None; an ambiguous or
105-
WAF-blocked channel yields None, leaving the scan unchanged.
116+
here is built from operator semantics alone. Returns the DBMS name or None; an ambiguous,
117+
WAF-blocked or false-positive channel yields None, leaving the scan unchanged.
106118
"""
107119

108120
retVal = None
@@ -114,9 +126,12 @@ def dialectCheckDbms(injection):
114126
kb.injection = injection
115127

116128
try:
117-
# channel sanity: a tautology must read TRUE and a contradiction FALSE, otherwise the
118-
# boolean oracle is unreliable and the all-false signature (Oracle-like) would be meaningless
119-
if checkBooleanExpression("2=2") and not checkBooleanExpression("2=3"):
129+
# Trustworthiness gate: a real boolean oracle reads a tautology TRUE, a contradiction FALSE,
130+
# and a syntactically-invalid canary FALSE (the appended clause is a parse error -> the query
131+
# fails). A false-positive / noise channel reads them all alike - the canary as TRUE - which
132+
# is proof the oracle is trash, so classification is skipped (a true negative) instead of
133+
# emitting a bogus DBMS from a meaningless signature.
134+
if checkBooleanExpression("2=2") and not checkBooleanExpression("2=3") and not checkBooleanExpression(DIALECT_CANARY):
120135
signature = tuple(bool(checkBooleanExpression(expr)) for _, expr in DIALECT_PROBES)
121136
retVal = _classify(signature)
122137
finally:

0 commit comments

Comments
 (0)