From e8a2fc50e061325e1cf0033a439a7275a374c16d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 28 Jul 2026 16:24:22 +0200 Subject: [PATCH] Fixing limitCondition double-LIMIT --- lib/core/agent.py | 10 ++++++++-- lib/core/settings.py | 2 +- tests/test_agent.py | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/core/agent.py b/lib/core/agent.py index bb313dcc4..0501e95c2 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -1041,8 +1041,14 @@ class Agent(object): else: limitRegExp2 = None + # DBMSes whose paging is a simple appended "LIMIT ... OFFSET ..." (limitQuery '% (1, num)' group, + # limitregexp 'query2' for a bare LIMIT, groupstart=2/stop=1): a user-supplied LIMIT must be parsed + # AND stripped here, else limitQuery appends a SECOND LIMIT (e.g. '... LIMIT 3 LIMIT 1 OFFSET n' on + # ClickHouse) and per-row extraction (blind/error) reads bogus offsets - only UNION was unaffected. + limitOffsetAppendDbmses = (DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE, DBMS.H2, DBMS.CLICKHOUSE, DBMS.CRATEDB, DBMS.SPANNER, DBMS.HANA) + if (limitRegExp or limitRegExp2) or (Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE) and topLimit): - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE, DBMS.H2): + if Backend.getIdentifiedDbms() in limitOffsetAppendDbmses: limitGroupStart = queries[Backend.getIdentifiedDbms()].limitgroupstart.query limitGroupStop = queries[Backend.getIdentifiedDbms()].limitgroupstop.query @@ -1084,7 +1090,7 @@ class Agent(object): # From now on we need only the expression until the " LIMIT " # (or equivalent, depending on the back-end DBMS) word - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE): + if Backend.getIdentifiedDbms() in limitOffsetAppendDbmses: stopLimit += startLimit if expression.find(queries[Backend.getIdentifiedDbms()].limitstring.query) > 0: _ = expression.index(queries[Backend.getIdentifiedDbms()].limitstring.query) diff --git a/lib/core/settings.py b/lib/core/settings.py index 65996180f..944127bbc 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.220" +VERSION = "1.10.7.222" 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) diff --git a/tests/test_agent.py b/tests/test_agent.py index 12d7a179c..ed7463ba6 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -460,6 +460,21 @@ class TestLimitQuery(DbmsStateMixin, unittest.TestCase): self.assertIn("NOT IN", out.upper(), msg=out) +class TestLimitConditionUserLimit(DbmsStateMixin, unittest.TestCase): + """A user LIMIT in --sql-query must be parsed AND stripped for the 'append LIMIT/OFFSET' + DBMSes, else limitQuery appends a 2nd LIMIT and blind/error read bogus offsets.""" + + APPEND_DBMSES = (DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE, DBMS.H2, DBMS.CLICKHOUSE, DBMS.CRATEDB, DBMS.SPANNER, DBMS.HANA) + + def test_user_limit_stripped_and_parsed(self): + for dbms in self.APPEND_DBMSES: + set_dbms(dbms) + expression, limitCond, _, startLimit, stopLimit = agent.limitCondition("SELECT name FROM t ORDER BY y LIMIT 3") + self.assertTrue(limitCond, msg=dbms) + self.assertEqual((startLimit, stopLimit), (0, 3), msg=dbms) + self.assertIsNone(re.search(r"(?i)\bLIMIT\b", expression), msg="%s not stripped: %s" % (dbms, expression)) + + class TestWhereQuery(DbmsStateMixin, unittest.TestCase): """whereQuery only acts when conf.dumpWhere is set."""