diff --git a/lib/core/common.py b/lib/core/common.py index 33ef8aeae..0b1904eed 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -190,7 +190,6 @@ from lib.core.settings import URI_QUESTION_MARKER from lib.core.settings import URLENCODE_CHAR_LIMIT from lib.core.settings import URLENCODE_FAILSAFE_CHARS from lib.core.settings import USER_AGENT_ALIASES -from lib.core.settings import VERSION_COMPARISON_CORRECTION from lib.core.settings import VERSION_STRING from lib.core.settings import ZIP_HEADER from lib.core.settings import WEBSCARAB_SPLITTER @@ -3500,42 +3499,40 @@ def isDBMSVersionAtLeast(minimum): if not any(isNoneValue(_) for _ in (Backend.getVersion(), minimum)) and Backend.getVersion() != UNKNOWN_DBMS_VERSION: version = Backend.getVersion().replace(" ", "").rstrip('.') - correction = 0.0 + # Note: a fuzzy/ranged detected version (e.g. '>2', '<2') is captured as a sign so an + # otherwise-equal comparison still resolves in the right direction + vSign = 0 if ">=" in version: pass elif '>' in version: - correction = VERSION_COMPARISON_CORRECTION + vSign = 1 elif '<' in version: - correction = -VERSION_COMPARISON_CORRECTION + vSign = -1 version = extractRegexResult(r"(?P[0-9][0-9.]*)", version) if version: - if '.' in version: - parts = version.split('.', 1) - parts[1] = filterStringValue(parts[1], '[0-9]') - version = '.'.join(parts) + minimum = minimum if isinstance(minimum, six.string_types) else getUnicode(minimum) - try: - version = float(filterStringValue(version, '[0-9.]')) + correction - except ValueError: - return None + mSign = 0 + if minimum.startswith(">="): + pass + elif minimum.startswith(">"): + mSign = 1 - if isinstance(minimum, six.string_types): - if '.' in minimum: - parts = minimum.split('.', 1) - parts[1] = filterStringValue(parts[1], '[0-9]') - minimum = '.'.join(parts) + minimum = extractRegexResult(r"(?P[0-9][0-9.]*)", minimum) - correction = 0.0 - if minimum.startswith(">="): - pass - elif minimum.startswith(">"): - correction = VERSION_COMPARISON_CORRECTION + if minimum: + # Note: compare dotted versions component-wise as int tuples, not as floats; a float + # collapses e.g. 5.4.3->5.43 and 5.10.0->5.100(==5.1), silently mis-ordering multi-part + # or multi-digit-minor versions (MariaDB 10.11, PostgreSQL 9.10, Presto 0.99 vs 0.178) + vParts = tuple(int(_) for _ in re.findall(r"\d+", version)) + mParts = tuple(int(_) for _ in re.findall(r"\d+", minimum)) + length = max(len(vParts), len(mParts)) + vParts += (0,) * (length - len(vParts)) + mParts += (0,) * (length - len(mParts)) - minimum = float(filterStringValue(minimum, '[0-9.]')) + correction - - retVal = version >= minimum + retVal = (vParts, vSign) >= (mParts, mSign) return retVal diff --git a/lib/core/settings.py b/lib/core/settings.py index 71e01889f..d261bb3e7 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.126" +VERSION = "1.10.7.127" 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_common.py b/tests/test_common.py index b27e8acf3..d26d1090c 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -86,6 +86,7 @@ from lib.core.common import ( getTechnique, getText, intersect, + isDBMSVersionAtLeast, isListLike, isNoneValue, isNullValue, @@ -983,6 +984,48 @@ class TestAliasToDbmsEnum(unittest.TestCase): self.assertIsNone(aliasToDbmsEnum("")) +class TestIsDBMSVersionAtLeast(unittest.TestCase): + """Version gating drives per-DBMS query selection; comparison must be component-wise.""" + + def setUp(self): + self._saved = kb.get("dbmsVersion") + + def tearDown(self): + kb.dbmsVersion = self._saved + + def _at_least(self, version, minimum): + kb.dbmsVersion = version + return isDBMSVersionAtLeast(minimum) + + def test_single_major_thresholds(self): + self.assertTrue(self._at_least("5.4.3", "5")) + self.assertTrue(self._at_least("8.0.32", "8")) + self.assertFalse(self._at_least("5.7.44", "8")) + + def test_multi_digit_minor_ordering(self): + # floats mis-sorted these (10.11->10.11 vs 10.5->10.5): component-wise fixes it + self.assertTrue(self._at_least("10.11", "10.5")) # MariaDB + self.assertFalse(self._at_least("10.6", "10.11")) + self.assertTrue(self._at_least("5.10.0", "5.5")) + self.assertTrue(self._at_least("9.10", "9.6")) # PostgreSQL + + def test_presto_sequential_minor(self): + # Presto 0.NNN: release 99 is OLDER than release 178 (float made 0.99 > 0.178) + self.assertFalse(self._at_least("0.99", "0.178")) + self.assertTrue(self._at_least("0.180", "0.178")) + + def test_range_and_prefix_semantics(self): + self.assertTrue(self._at_least("2", ">=2.0")) + self.assertFalse(self._at_least("2", ">2")) + self.assertFalse(self._at_least("<2", "2")) + self.assertTrue(self._at_least("<2", "1.5")) + + def test_unknown_version_is_none(self): + from lib.core.settings import UNKNOWN_DBMS_VERSION + kb.dbmsVersion = UNKNOWN_DBMS_VERSION + self.assertIsNone(isDBMSVersionAtLeast("5")) + + class TestGetPageWordSet(unittest.TestCase): def test_word_extraction(self): words = getPageWordSet(u"foobartest")