Improving SQLite table schema parsing (#2678)

This commit is contained in:
Miroslav Stampar 2022-12-13 23:42:24 +01:00
parent ebaee3a4e6
commit 86ac3025ed
4 changed files with 29 additions and 8 deletions

View File

@ -357,8 +357,8 @@
<blind query="SELECT tbl_name FROM sqlite_master WHERE type='table' LIMIT %d,1" count="SELECT COUNT(tbl_name) FROM sqlite_master WHERE type='table'"/> <blind query="SELECT tbl_name FROM sqlite_master WHERE type='table' LIMIT %d,1" count="SELECT COUNT(tbl_name) FROM sqlite_master WHERE type='table'"/>
</tables> </tables>
<columns> <columns>
<inband query="SELECT MAX(sql) FROM sqlite_master WHERE tbl_name='%s'"/> <inband query="SELECT MAX(sql) FROM sqlite_master WHERE type='table' AND tbl_name='%s'"/>
<blind query="SELECT sql FROM sqlite_master WHERE tbl_name='%s' LIMIT 1" condition=""/> <blind query="SELECT sql FROM sqlite_master WHERE type='table' AND tbl_name='%s' LIMIT 1" condition=""/>
</columns> </columns>
<dump_table> <dump_table>
<inband query="SELECT %s FROM %s"/> <inband query="SELECT %s FROM %s"/>

View File

@ -44,7 +44,8 @@ SCHEMA = """
CREATE TABLE users ( CREATE TABLE users (
id INTEGER, id INTEGER,
name TEXT, name TEXT,
surname TEXT surname TEXT,
PRIMARY KEY (id)
); );
INSERT INTO users (id, name, surname) VALUES (1, 'luther', 'blisset'); INSERT INTO users (id, name, surname) VALUES (1, 'luther', 'blisset');
INSERT INTO users (id, name, surname) VALUES (2, 'fluffy', 'bunny'); INSERT INTO users (id, name, surname) VALUES (2, 'fluffy', 'bunny');

View File

@ -3402,19 +3402,39 @@ def parseSqliteTableSchema(value):
>>> kb.data.cachedColumns = {} >>> kb.data.cachedColumns = {}
>>> parseSqliteTableSchema("CREATE TABLE users(\\n\\t\\tid INTEGER,\\n\\t\\tname TEXT\\n);") >>> parseSqliteTableSchema("CREATE TABLE users(\\n\\t\\tid INTEGER,\\n\\t\\tname TEXT\\n);")
True True
>>> repr(kb.data.cachedColumns).count(',') == 1 >>> tuple(kb.data.cachedColumns[conf.db][conf.tbl].items()) == (('id', 'INTEGER'), ('name', 'TEXT'))
True
>>> parseSqliteTableSchema("CREATE TABLE dummy(`foo bar` BIGINT, \\"foo\\" VARCHAR, 'bar' TEXT)");
True
>>> tuple(kb.data.cachedColumns[conf.db][conf.tbl].items()) == (('foo bar', 'BIGINT'), ('foo', 'VARCHAR'), ('bar', 'TEXT'))
True
>>> parseSqliteTableSchema("CREATE TABLE suppliers(\\n\\tsupplier_id INTEGER PRIMARY KEY DESC,\\n\\tname TEXT NOT NULL\\n);");
True
>>> tuple(kb.data.cachedColumns[conf.db][conf.tbl].items()) == (('supplier_id', 'INTEGER'), ('name', 'TEXT'))
True
>>> parseSqliteTableSchema("CREATE TABLE country_languages (\\n\\tcountry_id INTEGER NOT NULL,\\n\\tlanguage_id INTEGER NOT NULL,\\n\\tPRIMARY KEY (country_id, language_id),\\n\\tFOREIGN KEY (country_id) REFERENCES countries (country_id) ON DELETE CASCADE ON UPDATE NO ACTION,\\tFOREIGN KEY (language_id) REFERENCES languages (language_id) ON DELETE CASCADE ON UPDATE NO ACTION);");
True
>>> tuple(kb.data.cachedColumns[conf.db][conf.tbl].items()) == (('country_id', 'INTEGER'), ('language_id', 'INTEGER'))
True True
""" """
retVal = False retVal = False
value = extractRegexResult(r"(?s)\((?P<result>.+)\)", value)
if value: if value:
table = {} table = {}
columns = {} columns = OrderedDict()
for match in re.finditer(r"[(,]\s*[\"'`]?(\w+)[\"'`]?(?:\s+(INT|INTEGER|TINYINT|SMALLINT|MEDIUMINT|BIGINT|UNSIGNED BIG INT|INT2|INT8|INTEGER|CHARACTER|VARCHAR|VARYING CHARACTER|NCHAR|NATIVE CHARACTER|NVARCHAR|TEXT|CLOB|LONGTEXT|BLOB|NONE|REAL|DOUBLE|DOUBLE PRECISION|FLOAT|REAL|NUMERIC|DECIMAL|BOOLEAN|DATE|DATETIME|NUMERIC)\b)?", decodeStringEscape(value), re.I): value = re.sub(r"\(.+?\)", "", value).strip()
for match in re.finditer(r"(?:\A|,)\s*(([\"'`]).+?\2|\w+)(?:\s+(INT|INTEGER|TINYINT|SMALLINT|MEDIUMINT|BIGINT|UNSIGNED BIG INT|INT2|INT8|INTEGER|CHARACTER|VARCHAR|VARYING CHARACTER|NCHAR|NATIVE CHARACTER|NVARCHAR|TEXT|CLOB|LONGTEXT|BLOB|NONE|REAL|DOUBLE|DOUBLE PRECISION|FLOAT|REAL|NUMERIC|DECIMAL|BOOLEAN|DATE|DATETIME|NUMERIC)\b)?", decodeStringEscape(value), re.I):
column = match.group(1).strip(match.group(2) or "")
if re.search(r"(?i)\A(CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)\b", column.strip()):
continue
retVal = True retVal = True
columns[match.group(1)] = match.group(2) or "TEXT"
columns[column] = match.group(3) or "TEXT"
table[safeSQLIdentificatorNaming(conf.tbl, True)] = columns table[safeSQLIdentificatorNaming(conf.tbl, True)] = columns
kb.data.cachedColumns[conf.db] = table kb.data.cachedColumns[conf.db] = table

View File

@ -20,7 +20,7 @@ from thirdparty import six
from thirdparty.six import unichr as _unichr from thirdparty.six import unichr as _unichr
# sqlmap version (<major>.<minor>.<month>.<monthly commit>) # sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.6.12.1" VERSION = "1.6.12.2"
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} 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) VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)