From 109ce217ecbcbdfd74ad3de205527b912179ba3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 11:32:39 +0200 Subject: [PATCH] Bug fix for dump format SQLITE --- lib/core/dump.py | 11 +++++++++-- lib/core/settings.py | 6 +++++- tests/test_dump_format.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/lib/core/dump.py b/lib/core/dump.py index bbeb3d0f2..cff211659 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -54,6 +54,8 @@ from lib.core.settings import HTML_DUMP_CSS_STYLE from lib.core.settings import IS_WIN from lib.core.settings import METADB_SUFFIX from lib.core.settings import MIN_BINARY_DISK_DUMP_SIZE +from lib.core.settings import SQLITE_INT_MAX +from lib.core.settings import SQLITE_INT_MIN from lib.core.settings import TRIM_STDOUT_DUMP_SIZE from lib.core.settings import UNICODE_ENCODING from lib.core.settings import UNSAFE_DUMP_FILEPATH_REPLACEMENT @@ -558,7 +560,10 @@ class Dump(object): if not value or value == " ": # NULL continue - int(value) + # Note: keep INTEGER only for values SQLite's affinity leaves untouched; leading zeros ('007'), signs ('+1') or 64-bit overflow would be silently rewritten on insert + parsed = int(value) + if str(parsed) != value or not (SQLITE_INT_MIN <= parsed <= SQLITE_INT_MAX): + raise ValueError except ValueError: colType = None break @@ -572,7 +577,9 @@ class Dump(object): if not value or value == " ": # NULL continue - float(value) + # Note: likewise REAL must round-trip textually ('2.00' or '1e5' would lose their exact form) + if repr(float(value)) != value: + raise ValueError except ValueError: colType = None break diff --git a/lib/core/settings.py b/lib/core/settings.py index 5b3d3190b..a0ee0556a 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.119" +VERSION = "1.10.7.120" 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) @@ -683,6 +683,10 @@ HASH_EMPTY_PASSWORD_MARKER = "" # Maximum integer value MAX_INT = sys.maxsize +# Signed 64-bit range of SQLite's INTEGER storage class (used for safe --dump-format=SQLITE typing) +SQLITE_INT_MIN = -0x8000000000000000 +SQLITE_INT_MAX = 0x7fffffffffffffff + # Replacement for unsafe characters in dump table filenames UNSAFE_DUMP_FILEPATH_REPLACEMENT = '_' diff --git a/tests/test_dump_format.py b/tests/test_dump_format.py index d3484c28f..2fb052f2b 100644 --- a/tests/test_dump_format.py +++ b/tests/test_dump_format.py @@ -358,6 +358,37 @@ class TestSqliteDump(_FileDumpCase): finally: conn.close() + def test_non_roundtrip_numbers_stay_text(self): + # Values that look numeric but would be silently rewritten by SQLite's INTEGER/REAL + # affinity (leading zeros, sign prefix, 64-bit overflow, trailing-zero/exponent floats) + # must be stored verbatim as TEXT, otherwise the export corrupts the dumped data + tv = _PlainOrderedDict([ + ("__infos__", {"count": 1, "db": "testdb", "table": "t"}), + ("zip", {"length": 5, "values": ["007"]}), + ("phone", {"length": 10, "values": ["0917123456"]}), + ("signed", {"length": 2, "values": ["+1"]}), + ("huge", {"length": 30, "values": ["123456789012345678901234567890"]}), + ("money", {"length": 4, "values": ["2.00"]}), + ("real_int", {"length": 1, "values": ["5"]}), # genuine ints still typed INTEGER + ]) + conf.dumpFormat = DUMP_FORMAT.SQLITE + self.d.dbTableValues(tv) + + import sqlite3 + conn = sqlite3.connect(os.path.join(self.tmp, "testdb.sqlite3")) + try: + cur = conn.cursor() + cur.execute("SELECT zip, phone, signed, huge, money, real_int FROM t") + self.assertEqual(cur.fetchone(), ("007", "0917123456", "+1", "123456789012345678901234567890", "2.00", 5)) + cur.execute("PRAGMA table_info(t)") + types = {name: ctype for (_cid, name, ctype, _nn, _dv, _pk) in cur.fetchall()} + self.assertEqual(types["zip"], "TEXT") + self.assertEqual(types["huge"], "TEXT") + self.assertEqual(types["money"], "TEXT") + self.assertEqual(types["real_int"], "INTEGER") + finally: + conn.close() + # --- replication backend tests (pure sqlite3, no network/DBMS) -----------------------------------