Skip to content

Commit fb9cec1

Browse files
committed
Auto recognize binary fields in blind cases
1 parent 8bc0f47 commit fb9cec1

2 files changed

Lines changed: 43 additions & 16 deletions

File tree

lib/core/settings.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from thirdparty import six
2121

2222
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
23-
VERSION = "1.10.7.77"
23+
VERSION = "1.10.7.78"
2424
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
2525
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
2626
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)
@@ -890,6 +890,10 @@
890890
# the text extraction channel (mirrors a manual '--binary-fields', using the already-fetched column type)
891891
BINARY_FIELDS_TYPE_REGEX = r"(?i)binary|blob|bytea|image|\braw\b"
892892

893+
# Uppercased keywords of the above, for building an in-SQL "is this column binary-typed?" check when only
894+
# column names (not types) were fetched - i.e. blind dumping (keep in sync with BINARY_FIELDS_TYPE_REGEX)
895+
BINARY_FIELDS_TYPE_KEYWORDS = ("BINARY", "BLOB", "BYTEA", "IMAGE", "RAW")
896+
893897
# Maximum number of redirections to any single URL - this is needed because of the state that cookies introduce
894898
MAX_SINGLE_URL_REDIRECTIONS = 4
895899

plugins/generic/databases.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from lib.core.exception import SqlmapMissingMandatoryOptionException
4747
from lib.core.exception import SqlmapNoneDataException
4848
from lib.core.exception import SqlmapUserQuitException
49+
from lib.core.settings import BINARY_FIELDS_TYPE_KEYWORDS
4950
from lib.core.settings import CURRENT_DB
5051
from lib.core.settings import METADB_SUFFIX
5152
from lib.core.settings import PLUS_ONE_DBMSES
@@ -931,7 +932,16 @@ def columnNameQuery(index):
931932
warnMsg += "possible to get column comments"
932933
singleTimeWarnMessage(warnMsg)
933934

934-
if not onlyColNames:
935+
# In dump mode we don't need the exact type, only whether the column is binary (so its raw
936+
# bytes get hex-extracted instead of mangled/truncated - issues #8/#582/#2827). Rather than
937+
# extract the whole type string char-by-char, wrap the type query into a single-bit check and
938+
# extract just that (~10x fewer requests). Skipped where query2 doesn't yield a type name:
939+
# MSSQL (returns the column name), Firebird/Informix (numeric type codes), and PostgreSQL
940+
# (its bytea already renders fine, so it's excluded from auto-hexing anyway).
941+
binaryProbe = onlyColNames and dumpMode and not Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.PGSQL, DBMS.FIREBIRD, DBMS.INFORMIX)
942+
943+
if not onlyColNames or binaryProbe:
944+
query = None
935945
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE, DBMS.VIRTUOSO, DBMS.CLICKHOUSE):
936946
query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column, unsafeSQLIdentificatorNaming(conf.db))
937947
elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL):
@@ -947,22 +957,35 @@ def columnNameQuery(index):
947957
elif Backend.isDbms(DBMS.SPANNER):
948958
query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column, unsafeSQLIdentificatorNaming(conf.db), unsafeSQLIdentificatorNaming(conf.db))
949959

950-
colType = unArrayizeValue(inject.getValue(query, union=False, error=False))
951-
key = int(colType) if hasattr(colType, "isdigit") and colType.isdigit() else colType
960+
if binaryProbe and query:
961+
typeMatch = re.match(r"(?is)\s*SELECT\s+(.+?)\s+FROM\s+(.+)", query)
962+
if typeMatch:
963+
binaryCondition = " OR ".join("UPPER(%s) LIKE '%%%s%%'" % (typeMatch.group(1), _) for _ in BINARY_FIELDS_TYPE_KEYWORDS)
964+
query = "SELECT (CASE WHEN %s THEN 1 ELSE 0 END) FROM %s" % (binaryCondition, typeMatch.group(2))
965+
else:
966+
query = None # unexpected shape - fall back to leaving the type unknown
952967

953-
if Backend.isDbms(DBMS.FIREBIRD):
954-
colType = FIREBIRD_TYPES.get(key, colType)
955-
elif Backend.isDbms(DBMS.INFORMIX):
956-
notNull = False
957-
if isinstance(key, int) and key > 255:
958-
key -= 256
959-
notNull = True
960-
colType = INFORMIX_TYPES.get(key, colType)
961-
if notNull:
962-
colType = "%s NOT NULL" % colType
968+
colType = unArrayizeValue(inject.getValue(query, union=False, error=False)) if query else None
963969

964-
column = safeSQLIdentificatorNaming(column)
965-
columns[column] = colType
970+
if binaryProbe:
971+
column = safeSQLIdentificatorNaming(column)
972+
columns[column] = "binary" if colType in ('1', 1) else None # sentinel matched by BINARY_FIELDS_TYPE_REGEX
973+
else:
974+
key = int(colType) if hasattr(colType, "isdigit") and colType.isdigit() else colType
975+
976+
if Backend.isDbms(DBMS.FIREBIRD):
977+
colType = FIREBIRD_TYPES.get(key, colType)
978+
elif Backend.isDbms(DBMS.INFORMIX):
979+
notNull = False
980+
if isinstance(key, int) and key > 255:
981+
key -= 256
982+
notNull = True
983+
colType = INFORMIX_TYPES.get(key, colType)
984+
if notNull:
985+
colType = "%s NOT NULL" % colType
986+
987+
column = safeSQLIdentificatorNaming(column)
988+
columns[column] = colType
966989
else:
967990
column = safeSQLIdentificatorNaming(column)
968991
columns[column] = None

0 commit comments

Comments
 (0)