From 356984929f7c87db5543095e9d48cbf1e0fcf172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 28 Jul 2026 16:42:37 +0200 Subject: [PATCH] Fixing --common-columns for -T A,B --- lib/core/settings.py | 2 +- lib/utils/brute.py | 12 +++++++----- tests/test_brute.py | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 944127bbc..d8823f833 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.222" +VERSION = "1.10.7.223" 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/lib/utils/brute.py b/lib/utils/brute.py index 5f917e26a..eb619fba0 100644 --- a/lib/utils/brute.py +++ b/lib/utils/brute.py @@ -293,7 +293,9 @@ def columnExists(columnFile, regex=None): warnMsg = "no column(s) found" logger.warning(warnMsg) else: - columns = {} + # Note: a separate name from the 'columns' wordlist (a list reused across the + # conf.tbl loop); reusing it here would rebind it to a dict and break later tables + columnData = {} for column in threadData.shared.files: if Backend.getIdentifiedDbms() in (DBMS.MYSQL,): @@ -306,15 +308,15 @@ def columnExists(columnFile, regex=None): result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE ROUND(%s)=ROUND(%s))", (column, table, column, column))) if result: - columns[column] = "numeric" + columnData[column] = "numeric" else: - columns[column] = "non-numeric" + columnData[column] = "non-numeric" if conf.db not in kb.data.cachedColumns: kb.data.cachedColumns[conf.db] = {} - kb.data.cachedColumns[conf.db][table] = columns + kb.data.cachedColumns[conf.db][table] = columnData - for _ in ((conf.db, table, item[0], item[1]) for item in columns.items()): + for _ in ((conf.db, table, item[0], item[1]) for item in columnData.items()): if _ not in kb.brute.columns: kb.brute.columns.append(_) diff --git a/tests/test_brute.py b/tests/test_brute.py index f85fe4c6c..4265537c8 100644 --- a/tests/test_brute.py +++ b/tests/test_brute.py @@ -184,6 +184,25 @@ class TestBrute(DbmsStateMixin, unittest.TestCase): self.assertEqual(cols.get(getText(safeSQLIdentificatorNaming("id"))), "numeric") self.assertEqual(cols.get(getText(safeSQLIdentificatorNaming("name"))), "non-numeric") + def test_column_exists_multiple_tables(self): + # regression: the found-columns dict must not rebind the 'columns' wordlist list, + # else the 2nd table in conf.tbl indexes a dict by int -> KeyError (crash/hang) + set_dbms(DBMS.MYSQL) + conf.tbl = "users,logs" + brute.getFileItems = lambda *a, **k: ["id", "name"] + + def _cbe(expression, expectingNone=True): + if not any(_ in expression for _ in ("users", "logs")): + return False # random-name sanity probe + return True # every column exists (type follow-ups don't matter here) + brute.inject.checkBooleanExpression = _cbe + + # pre-fix the 2nd table iteration raised KeyError (dict indexed by int); post-fix + # both tables are processed and cached (keys are safeSQLIdentificatorNaming'd) + result = brute.columnExists("/nonexistent/columns.txt") + self.assertEqual(len(result[None]), 2) + self.assertTrue(all(cols for cols in result[None].values())) + def test_add_page_text_words_filters(self): # restore the real getPageWordSet for this one and drive it directly brute.getPageWordSet = self._orig_getPageWordSet