Skip to content

Commit 002a28f

Browse files
committed
Couple of bug fixes
1 parent 1404133 commit 002a28f

8 files changed

Lines changed: 250 additions & 14 deletions

File tree

data/txt/sha256sums.txt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ b0f434f64105bd61ab0f6867b3f681b97fa02b4fb809ac538db382d031f0e609 data/xml/paylo
8484
0648264166455010921df1ec431e4c973809f37ef12cbfea75f95029222eb689 data/xml/payloads/stacked_queries.xml
8585
997556b6170964a64474a2e053abe33cf2cf029fb1acec660d4651cc67a3c7e1 data/xml/payloads/time_blind.xml
8686
40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml
87-
9d7dcbc6c5e368c44db851865ff49c791c3dee1ee62d8c02af8f8b15f4551aed data/xml/queries.xml
87+
38882b6ceb8bca59ce8ed927abe3b8840394c56b3881371c2103e229b8795040 data/xml/queries.xml
8888
e043101194219a2e4c8bc352f0d3a04b87e1c28b1bcd6c13f6d5d1c9e260b653 doc/ARCHITECTURE.md
8989
0f5a9c84cb57809be8759f483c7d05f54847115e715521ac0ecf390c0aa68465 doc/AUTHORS
9090
ce20a4b452f24a97fde7ec9ed816feee12ac148e1fde5f1722772cc866b12740 doc/CHANGELOG.md
@@ -162,7 +162,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/
162162
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py
163163
63657c00a046ca0fb28fd069407ab6305bd7b95c42f26a96ed083fd05b152252 extra/vulnserver/vulnserver.py
164164
3abecaec1a9c59645a4821463a2d761235f7a4f763a491f188a41a083bbddd98 lib/controller/action.py
165-
6574ed70c7fe0ac305dbc85ed7102f648b6a3f42fe2fe6b89172d69717327149 lib/controller/checks.py
165+
72707b5bdfc757c4e5271e156178919292b991a6e7337d3dcdeffea9df6db3ea lib/controller/checks.py
166166
dcd4adcd7a2447a624ca7927541941d25767a4581af2d762c3197dc93790f4df lib/controller/controller.py
167167
d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py
168168
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py
@@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch
189189
48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py
190190
0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py
191191
888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py
192-
f01361d999b0cf89b8418265c4a4962924fcc03a6b87e15b39c0836788725e85 lib/core/settings.py
192+
997888bab1d98fb9bc2550f3ab99df966d37f38719a41a8fb767e2cd79db6c4f lib/core/settings.py
193193
cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py
194194
bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py
195195
70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py
@@ -246,6 +246,7 @@ aeefb42ea0c68f72744bc1bfd7194ec1bc06480d8a7e23f4b8d3d23fbba2b014 lib/utils/api.
246246
442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py
247247
da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py
248248
a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps.py
249+
0fd055877e8b21d17c11447dac7f91ef1766e0b04d470c494a6d98f5249e3186 lib/utils/dialect.py
249250
51cfab194cd5b6b24d62706fb79db86c852b9e593f4c55c15b35f175e70c9d75 lib/utils/getch.py
250251
853c3595e1d2efc54b8bfb6ab12c55d1efc1603be266978e3a7d96d553d91a52 lib/utils/gui.py
251252
972c5db9c9e30ac0f91c0f8d4df4531d0304e151dac99f1399c37c952ba9f935 lib/utils/har.py
@@ -394,7 +395,7 @@ ba04af3683b9a6e29e8fa6b3bf436a57e59435cebb042414f2df82018d91599e plugins/dbms/m
394395
78f1ff4b82fd4af50e1fbdb81539862f1c31258cda212b39f4a8501960f1b95e plugins/dbms/monetdb/syntax.py
395396
236fd244f0bbc3976b389429a8176feda6c243267564c2a0eff6fc2458c1b3f9 plugins/dbms/monetdb/takeover.py
396397
6bdc774463ac87b1bd1b6a9d5c2346b7edbf40d9848b7870a30d1eaedde4fc51 plugins/dbms/mssqlserver/connector.py
397-
52c19e9067f22f5c386206943d1807af4c661500bf260930a5986e9a180e96c7 plugins/dbms/mssqlserver/enumeration.py
398+
69ba678efde8335efb8a167b63143b4fb65ea19802bc3ade30c87cb979c198e4 plugins/dbms/mssqlserver/enumeration.py
398399
67cd70b64aed27af467682ceae8e20992b6765d2374d5762efb5a4585b8a6f79 plugins/dbms/mssqlserver/filesystem.py
399400
38ade085f9f1b227eda8c89f78e3ce869e8f430c98bef0cc7cbd2c7dcd60c24e plugins/dbms/mssqlserver/fingerprint.py
400401
1ecde09e80d7b709a710281f4983a6831bc02ca3458ae0b97b28446d6db241b4 plugins/dbms/mssqlserver/__init__.py
@@ -479,7 +480,7 @@ e2e20e4707abe9ed8b6208837332d2daa4eaca282f847412063f2484dcca8fbd plugins/dbms/v
479480
2b2dad6ba1d344215cad11b629546eb9f259d7c996c202edf3de5ab22418787e plugins/dbms/virtuoso/takeover.py
480481
51c44048e4b335b306f8ed1323fd78ad6935a8c0d6e9d6efe195a9a5a24e46dc plugins/generic/connector.py
481482
a967f4ebd101c68a5dcc10ff18c882a8f44a5c3bf06613d951a739ecc3abb9b3 plugins/generic/custom.py
482-
37351d6fb7418e3659bec5c9a6f9f181a606deae74d3bc9fb8c97f495449471f plugins/generic/databases.py
483+
6d037861acbbabec529e10c50840820ca7b876c29c69310a571b519c3f3b72fa plugins/generic/databases.py
483484
36b7319ac00f8fe1a33496364a76ff165ea2e66db0150f5366a45135366369ca plugins/generic/entries.py
484485
d2de7fc135cf0db3eb4ac4a509c23ebec5250a5d8043face7f8c546a09f301b5 plugins/generic/enumeration.py
485486
a02ac4ebc1cc488a2aa5ae07e6d0c3d5064e99ded7fd529dfa073735692f11df plugins/generic/filesystem.py
@@ -581,6 +582,7 @@ a48c411fea864e6bcd6a1c7e1a35094b8cda8d15088fd9e7b0270542ae20daa9 tests/test_com
581582
5016119bdb57094381afdca35ef29a4a6641e26e4b48a9119f1db633e6123d29 tests/test_datafiles.py
582583
9c240d4f796e56376374d4ce46f358ceb7d48cc6a7427760c5bfb89ff01cb545 tests/test_datatypes.py
583584
3804eb2d730220360f9dc07d5994eb64e9f65acf3b0d8648df8df2a2177ba8fd tests/test_decodepage.py
585+
9c0a0cd0b2d52a53f75c98c60f87a022354b7c3dc4baaf3fe1e272a0af5b7f0a tests/test_dialectdbms.py
584586
e40a49cfa73c45b3c3c6d1d1d00738861e270cb7a07b28f5a5356f9c7c800cf2 tests/test_dialect.py
585587
993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py
586588
9cd5841349bc4db818658d12184929a96f7f279eff1f53ad18a54dbefbd6b276 tests/test_dump_jsonl.py

data/xml/queries.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1321,7 +1321,7 @@
13211321
</dbms>
13221322

13231323
<dbms value="ClickHouse">
1324-
<cast query="CAST(%s AS String)"/>
1324+
<cast query="CAST(%s AS Nullable(String))"/>
13251325
<length query="length(%s)"/>
13261326
<isnull query="ifNull(%s, '')"/>
13271327
<delimiter query="||"/>

lib/controller/checks.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
from lib.request.comparison import comparison
101101
from lib.request.inject import checkBooleanExpression
102102
from lib.request.templates import getPageTemplate
103+
from lib.utils.dialect import dialectCheckDbms
103104
from lib.techniques.union.test import unionTest
104105
from lib.techniques.union.use import configUnion
105106
from thirdparty import six
@@ -149,6 +150,13 @@ def checkSqlInjection(place, parameter, value):
149150
if not Backend.getIdentifiedDbms() and kb.heuristicDbms is None and not kb.droppingRequests:
150151
kb.heuristicDbms = heuristicCheckDbms(injection)
151152

153+
# keyword-free fallback: heuristicCheckDbms() above uses SELECT/quote payloads
154+
# and is skipped when the WAF/IPS is dropping requests; the operator-dialect
155+
# probes carry no SELECT/quote/schema name, so they can still narrow the DBMS in
156+
# that case (or when it was inconclusive), using the now-calibrated boolean oracle
157+
if not Backend.getIdentifiedDbms() and kb.heuristicDbms is None:
158+
kb.heuristicDbms = dialectCheckDbms(injection)
159+
152160
# If the DBMS has already been fingerprinted (via DBMS-specific
153161
# error message, simple heuristic check or via DBMS-specific
154162
# payload), ask the user to limit the tests to the fingerprinted

lib/core/settings.py

Lines changed: 2 additions & 2 deletions
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.6.120"
23+
VERSION = "1.10.6.121"
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)
@@ -306,7 +306,7 @@
306306
MAXDB_SYSTEM_DBS = ("SYSINFO", "DOMAIN")
307307
SYBASE_SYSTEM_DBS = ("master", "model", "sybsystemdb", "sybsystemprocs", "tempdb")
308308
DB2_SYSTEM_DBS = ("NULLID", "SQLJ", "SYSCAT", "SYSFUN", "SYSIBM", "SYSIBMADM", "SYSIBMINTERNAL", "SYSIBMTS", "SYSPROC", "SYSPUBLIC", "SYSSTAT", "SYSTOOLS", "SYSDEBUG", "SYSINST")
309-
HSQLDB_SYSTEM_DBS = ("INFORMATION_SCHEMA", "SYSTEM_LOB")
309+
HSQLDB_SYSTEM_DBS = ("INFORMATION_SCHEMA", "SYSTEM_LOBS")
310310
H2_SYSTEM_DBS = ("INFORMATION_SCHEMA",) + ("IGNITE", "ignite-sys-cache")
311311
INFORMIX_SYSTEM_DBS = ("sysmaster", "sysutils", "sysuser", "sysadmin")
312312
MONETDB_SYSTEM_DBS = ("tmp", "json", "profiler")

lib/utils/dialect.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
5+
See the file 'LICENSE' for copying permission
6+
"""
7+
8+
from lib.core.common import Backend
9+
from lib.core.common import popValue
10+
from lib.core.common import pushValue
11+
from lib.core.data import conf
12+
from lib.core.data import kb
13+
from lib.core.data import logger
14+
from lib.core.enums import DBMS
15+
from lib.request.inject import checkBooleanExpression
16+
17+
# Operator-dialect probes for a keyword-free back-end DBMS heuristic.
18+
#
19+
# Each probe is an arithmetic identity that holds only in the dialect(s) noted, using operator
20+
# *semantics* alone - no SQL keywords, functions, quotes or schema names. It complements
21+
# heuristicCheckDbms() (which uses (SELECT 'x')='x' string round-trips): the dialect probes carry
22+
# no SELECT/quote, so they can narrow the back-end DBMS where those are dropped (e.g. a
23+
# keyword-matching WAF/IPS, or when kb.droppingRequests has it skipped entirely).
24+
#
25+
# Each probe is evaluated through checkBooleanExpression(), i.e. as an appended boolean
26+
# (... AND (<probe>)), which yields a clean true/false from the comparison oracle. (A value-position
27+
# variant - replacing the value with id=2^0 etc. - was prototyped and rejected: those probes land on
28+
# OTHER valid rows, which sqlmap's fuzzy page comparison conflates with the anchor row, producing
29+
# false positives. See PROVE_DESIGN.md.)
30+
#
31+
# Truth table measured on a live OWASP-CRS platform across 11 engines (MySQL, MariaDB/TiDB,
32+
# PostgreSQL, CockroachDB, Microsoft SQL Server, SQLite, Firebird, ClickHouse, H2, HSQLDB, Derby);
33+
# only the zero-false-positive rules are kept (see _classify). With anchor value 2:
34+
#
35+
# * 2^0=2 -> '^' is bitwise XOR (MySQL/MSSQL: 2^0=2) vs exponentiation (PostgreSQL: 2^0=1) vs
36+
# no such operator (SQLite/Oracle/... -> error, so false)
37+
# * 2^3=8 -> '^' is exponentiation (PostgreSQL/CockroachDB: 2^3=8) - false for XOR dialects
38+
# (2^3=1) and erroring dialects; a positive PostgreSQL-family marker. CAVEAT:
39+
# '^'=exponentiation is not strictly unique to PostgreSQL - MS Access/Jet and DuckDB
40+
# also use it (neither on the platform), so this can read as PostgreSQL there.
41+
# * 5/2=2 -> integer division (PostgreSQL/MSSQL/SQLite) vs real division (MySQL/Oracle: 2.5)
42+
# * 2|0=2 -> a bitwise OR operator exists (absent in Firebird/Oracle/ClickHouse/H2)
43+
DIALECT_PROBES = (
44+
("xor", "2^0=2"),
45+
("pgpow", "2^3=8"),
46+
("intdiv", "5/2=2"),
47+
("bitor", "2|0=2"),
48+
)
49+
50+
def _classify(signature):
51+
"""
52+
Maps a measured (xor, pgpow, intdiv, bitor) operator-dialect signature to a back-end
53+
DBMS, or returns None when the signature does not *uniquely* identify a major DBMS (so
54+
detection proceeds unchanged - the heuristic never wrong-foots the scan).
55+
56+
Rules below are the subset of the measured 11-engine truth table that maps with zero
57+
false positives. Engines whose operator profile is not distinctive enough (Oracle's
58+
all-false signature, which a minimal engine like ClickHouse/H2/Firebird/HSQLDB/Derby or
59+
a fully WAF-blocked channel also produces) deliberately fall through to None:
60+
61+
>>> _classify((True, False, False, True)) # MySQL / MariaDB / TiDB
62+
'MySQL'
63+
>>> _classify((True, False, True, True)) # Microsoft SQL Server
64+
'Microsoft SQL Server'
65+
>>> _classify((False, True, True, True)) # PostgreSQL
66+
'PostgreSQL'
67+
>>> _classify((False, True, False, True)) # CockroachDB (pgwire) -> PostgreSQL family
68+
'PostgreSQL'
69+
>>> _classify((False, False, True, True)) # SQLite
70+
'SQLite'
71+
>>> _classify((False, False, True, False)) is None # Firebird/HSQLDB/Derby/H2 -> no prior
72+
True
73+
>>> _classify((False, False, False, False)) is None # all-false (Oracle/ClickHouse/blocked) -> no prior
74+
True
75+
"""
76+
77+
xor, pgpow, intdiv, bitor = signature
78+
79+
if pgpow: # '^' is exponentiation -> PostgreSQL family
80+
return DBMS.PGSQL
81+
if xor and intdiv: # '^' is XOR AND integer division -> SQL Server
82+
return DBMS.MSSQL
83+
if xor and not intdiv: # '^' is XOR AND real division -> MySQL family
84+
return DBMS.MYSQL
85+
if not xor and intdiv and bitor: # no '^', integer division, bitwise '|' -> SQLite
86+
return DBMS.SQLITE
87+
88+
return None
89+
90+
def dialectCheckDbms(injection):
91+
"""
92+
Keyword-free back-end DBMS heuristic via operator-dialect differentials, evaluated through the
93+
given (boolean-capable) injection. Complements heuristicCheckDbms() - which is skipped when the
94+
WAF/IPS is dropping requests and otherwise relies on SELECT/quote payloads - because every probe
95+
here is built from operator semantics alone. Returns the DBMS name or None; an ambiguous or
96+
WAF-blocked channel yields None, leaving the scan unchanged.
97+
"""
98+
99+
retVal = None
100+
101+
if conf.skipHeuristics:
102+
return retVal
103+
104+
pushValue(kb.injection)
105+
kb.injection = injection
106+
107+
try:
108+
# channel sanity: a tautology must read TRUE and a contradiction FALSE, otherwise the
109+
# boolean oracle is unreliable and the all-false signature (Oracle-like) would be meaningless
110+
if checkBooleanExpression("2=2") and not checkBooleanExpression("2=3"):
111+
signature = tuple(bool(checkBooleanExpression(expr)) for _, expr in DIALECT_PROBES)
112+
retVal = _classify(signature)
113+
finally:
114+
kb.injection = popValue()
115+
116+
if retVal and not Backend.getIdentifiedDbms():
117+
infoMsg = "heuristic (dialect) test shows that the back-end DBMS could be '%s'" % retVal
118+
logger.info(infoMsg)
119+
120+
return retVal

plugins/dbms/mssqlserver/enumeration.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def getTables(self):
9393

9494
if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
9595
for db in dbs:
96-
if conf.excludeSysDbs and db in self.excludeDbsList:
96+
if conf.excludeSysDbs and unsafeSQLIdentificatorNaming(db) in self.excludeDbsList:
9797
infoMsg = "skipping system database '%s'" % db
9898
singleTimeLogMessage(infoMsg)
9999
continue
@@ -116,7 +116,7 @@ def getTables(self):
116116

117117
if not kb.data.cachedTables and isInferenceAvailable() and not conf.direct:
118118
for db in dbs:
119-
if conf.excludeSysDbs and db in self.excludeDbsList:
119+
if conf.excludeSysDbs and unsafeSQLIdentificatorNaming(db) in self.excludeDbsList:
120120
infoMsg = "skipping system database '%s'" % db
121121
singleTimeLogMessage(infoMsg)
122122
continue
@@ -206,7 +206,7 @@ def searchTable(self):
206206
for db in foundTbls.keys():
207207
db = safeSQLIdentificatorNaming(db)
208208

209-
if conf.excludeSysDbs and db in self.excludeDbsList:
209+
if conf.excludeSysDbs and unsafeSQLIdentificatorNaming(db) in self.excludeDbsList:
210210
infoMsg = "skipping system database '%s'" % db
211211
singleTimeLogMessage(infoMsg)
212212
continue
@@ -343,7 +343,7 @@ def searchColumn(self):
343343
for db in (_ for _ in dbs if _):
344344
db = safeSQLIdentificatorNaming(db)
345345

346-
if conf.excludeSysDbs and db in self.excludeDbsList:
346+
if conf.excludeSysDbs and unsafeSQLIdentificatorNaming(db) in self.excludeDbsList:
347347
continue
348348

349349
if conf.exclude and re.search(conf.exclude, db, re.I) is not None:

plugins/generic/databases.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ def getTables(self, bruteForce=None):
304304
if conf.excludeSysDbs:
305305
infoMsg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(db) for db in self.excludeDbsList))
306306
logger.info(infoMsg)
307-
query += " IN (%s)" % ','.join("'%s'" % unsafeSQLIdentificatorNaming(db) for db in sorted(dbs) if db not in self.excludeDbsList)
307+
query += " IN (%s)" % ','.join("'%s'" % unsafeSQLIdentificatorNaming(db) for db in sorted(dbs) if unsafeSQLIdentificatorNaming(db) not in self.excludeDbsList)
308308
else:
309309
query += " IN (%s)" % ','.join("'%s'" % unsafeSQLIdentificatorNaming(db) for db in sorted(dbs))
310310

@@ -356,7 +356,7 @@ def getTables(self, bruteForce=None):
356356

357357
if not kb.data.cachedTables and isInferenceAvailable() and not conf.direct:
358358
for db in dbs:
359-
if conf.excludeSysDbs and db in self.excludeDbsList:
359+
if conf.excludeSysDbs and unsafeSQLIdentificatorNaming(db) in self.excludeDbsList:
360360
infoMsg = "skipping system database '%s'" % unsafeSQLIdentificatorNaming(db)
361361
logger.info(infoMsg)
362362
continue

0 commit comments

Comments
 (0)