diff --git a/extra/shutils/regressiontest.py b/extra/shutils/regressiontest.py index 24b62a801..52c5d1b81 100644 --- a/extra/shutils/regressiontest.py +++ b/extra/shutils/regressiontest.py @@ -26,7 +26,7 @@ SMTP_SERVER = "127.0.0.1" SMTP_PORT = 25 SMTP_TIMEOUT = 30 FROM = "regressiontest@sqlmap.org" -TO = "dev@sqlmap.org" +TO = ["bernardo.damele@gmail.com", "miroslav.stampar@gmail.com"] SUBJECT = "Regression test results on %s using revision %s" % (TIME, REVISION) def prepare_email(content): @@ -87,18 +87,23 @@ def main(): test_counts.append(test_count) - console_output_fd = codecs.open(os.path.join(output_folder, "console_output"), "rb", "utf8") - console_output = console_output_fd.read() - console_output_fd.close() + console_output_file = os.path.join(output_folder, "console_output") + log_file = os.path.join(output_folder, "debiandev", "log") + traceback_file = os.path.join(output_folder, "traceback") - attachments[test_count] = str(console_output) + if os.path.exists(console_output_file): + console_output_fd = codecs.open(console_output_file, "rb", "utf8") + console_output = console_output_fd.read() + console_output_fd.close() + attachments[test_count] = str(console_output) - log_fd = codecs.open(os.path.join(output_folder, "debiandev", "log"), "rb", "utf8") - log = log_fd.read() - log_fd.close() + if os.path.exists(log_file): + log_fd = codecs.open(log_file, "rb", "utf8") + log = log_fd.read() + log_fd.close() - if traceback: - traceback_fd = codecs.open(os.path.join(output_folder, "traceback"), "rb", "utf8") + if os.path.exists(traceback_file): + traceback_fd = codecs.open(traceback_file, "rb", "utf8") traceback = traceback_fd.read() traceback_fd.close() diff --git a/extra/shutils/regressiontest_cronjob.sh b/extra/shutils/regressiontest_cronjob.sh index 9a20bd3d6..b569b4e8d 100755 --- a/extra/shutils/regressiontest_cronjob.sh +++ b/extra/shutils/regressiontest_cronjob.sh @@ -6,9 +6,18 @@ SQLMAP_HOME="/opt/sqlmap" REGRESSION_SCRIPT="${SQLMAP_HOME}/extra/shutils" +FROM="regressiontest@sqlmap.org" +TO="bernardo.damele@gmail.com, miroslav.stampar@gmail.com" +SUBJECT="Automated regression test failed on $(date)" + cd $SQLMAP_HOME git pull rm -f output 2>/dev/null cd $REGRESSION_SCRIPT -python regressiontest.py +python regressiontest.py 1>/tmp/regressiontest.log 2>&1 + +if [ $? -ne 0 ] +then + cat /tmp/regressiontest.log | mailx -s "${SUBJECT}" -aFrom:${FROM} ${TO} +fi diff --git a/lib/core/agent.py b/lib/core/agent.py index d81d4be83..0e0770b01 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -535,7 +535,7 @@ class Agent(object): elif fieldsNoSelect: concatenatedQuery = "CONCAT('%s',%s,'%s')" % (kb.chars.start, concatenatedQuery, kb.chars.stop) - elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE, DBMS.DB2): + elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE, DBMS.DB2, DBMS.FIREBIRD): if fieldsExists: concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1) concatenatedQuery += "||'%s'" % kb.chars.stop @@ -822,8 +822,7 @@ class Agent(object): limitedQuery += " %s" % limitStr elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - if " ORDER BY " in limitedQuery and "(SELECT " in limitedQuery: - orderBy = limitedQuery[limitedQuery.index(" ORDER BY "):] + if " ORDER BY " in limitedQuery and "SELECT " in limitedQuery: limitedQuery = limitedQuery[:limitedQuery.index(" ORDER BY ")] if query.startswith("SELECT "): @@ -831,6 +830,7 @@ class Agent(object): limitedQuery = "%s FROM (%s,%s" % (untilFrom, untilFrom.replace(delimiter, ','), limitStr) else: limitedQuery = "%s FROM (SELECT %s,%s" % (untilFrom, ','.join(f for f in field), limitStr) + limitedQuery = limitedQuery % fromFrom limitedQuery += "=%d" % (num + 1) diff --git a/lib/core/dump.py b/lib/core/dump.py index b7b98818e..4b553f424 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -175,7 +175,8 @@ class Dump(object): for setting in settings: self._write(" %s: %s" % (subHeader, setting)) - self.singleString("") + if userSettings: + self.singleString("") def dbs(self, dbs): self.lister("available databases", dbs) diff --git a/lib/core/testing.py b/lib/core/testing.py index 376747012..ff4ed5258 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -167,6 +167,9 @@ def liveTest(): result = runCase(switches, parse) + test_case_fd = codecs.open(os.path.join(paths.SQLMAP_OUTPUT_PATH, "test_case"), "wb", UNICODE_ENCODING) + test_case_fd.write("%s\n" % name) + if result: logger.info("test passed") cleanCase() @@ -183,6 +186,7 @@ def liveTest(): errMsg += " - SQL injection not detected" logger.error(errMsg) + test_case_fd.write("%s\n" % errMsg) if failedParseOn: console_output_fd = codecs.open(os.path.join(paths.SQLMAP_OUTPUT_PATH, "console_output"), "wb", UNICODE_ENCODING) @@ -199,6 +203,7 @@ def liveTest(): if conf.stopFail is True: return retVal + test_case_fd.close() retVal &= bool(result) dataToStdout("\n") diff --git a/lib/techniques/error/use.py b/lib/techniques/error/use.py index d2edbb1ec..e84f0680a 100644 --- a/lib/techniques/error/use.py +++ b/lib/techniques/error/use.py @@ -23,6 +23,7 @@ from lib.core.common import incrementCounter from lib.core.common import initTechnique from lib.core.common import isListLike from lib.core.common import isNumPosStrValue +from lib.core.common import isTechniqueAvailable from lib.core.common import listToStrValue from lib.core.common import readInput from lib.core.common import unArrayizeValue @@ -34,6 +35,7 @@ from lib.core.data import logger from lib.core.data import queries from lib.core.dicts import FROM_DUMMY_TABLE from lib.core.enums import DBMS +from lib.core.enums import PAYLOAD from lib.core.settings import CHECK_ZERO_COLUMNS_THRESHOLD from lib.core.settings import MYSQL_ERROR_CHUNK_LENGTH from lib.core.settings import MSSQL_ERROR_CHUNK_LENGTH @@ -180,6 +182,9 @@ def _errorFields(expression, expressionFields, expressionFieldsList, num=None, e else: expressionReplaced = expression.replace(expressionFields, field, 1) + if kb.technique == PAYLOAD.TECHNIQUE.QUERY and Backend.isDbms(DBMS.FIREBIRD) and expressionReplaced.startswith("SELECT "): + expressionReplaced = "SELECT %s" % agent.concatQuery(expressionReplaced) + output = NULL if emptyFields and field in emptyFields else _oneShotErrorUse(expressionReplaced, field) if not kb.threadContinue: diff --git a/plugins/dbms/firebird/fingerprint.py b/plugins/dbms/firebird/fingerprint.py index cf005bd9d..8d192d3cb 100644 --- a/plugins/dbms/firebird/fingerprint.py +++ b/plugins/dbms/firebird/fingerprint.py @@ -74,6 +74,7 @@ class Fingerprint(GenericFingerprint): ("1.5", ("NULLIF(%d,%d) IS NULL", "EXISTS(SELECT CURRENT_TRANSACTION FROM RDB$DATABASE)")), ("2.0", ("EXISTS(SELECT CURRENT_TIME(0) FROM RDB$DATABASE)", "BIT_LENGTH(%d)>0", "CHAR_LENGTH(%d)>0")), ("2.1", ("BIN_XOR(%d,%d)=0", "PI()>0.%d", "RAND()<1.%d", "FLOOR(1.%d)>=0")), + # TODO: add test for Firebird 2.5 ) for i in xrange(len(table)): @@ -122,7 +123,7 @@ class Fingerprint(GenericFingerprint): logger.info(infoMsg) randInt = randomInt() - result = inject.checkBooleanExpression("EXISTS(SELECT * FROM RDB$DATABASE WHERE %d=%d)" % (randInt, randInt)) + result = inject.checkBooleanExpression("(SELECT COUNT(*) FROM RDB$DATABASE WHERE %d=%d)>0" % (randInt, randInt)) if result: infoMsg = "confirming %s" % DBMS.FIREBIRD diff --git a/plugins/dbms/firebird/syntax.py b/plugins/dbms/firebird/syntax.py index f0d91c78f..a77359249 100644 --- a/plugins/dbms/firebird/syntax.py +++ b/plugins/dbms/firebird/syntax.py @@ -16,6 +16,9 @@ class Syntax(GenericSyntax): @staticmethod def escape(expression, quote=True): if isDBMSVersionAtLeast('2.1'): + if expression == u"'''": + return "ASCII_CHAR(%d)" % (ord("'")) + if quote: while True: index = expression.find("'") diff --git a/plugins/dbms/mssqlserver/connector.py b/plugins/dbms/mssqlserver/connector.py index c859f9632..7eb0a82bb 100644 --- a/plugins/dbms/mssqlserver/connector.py +++ b/plugins/dbms/mssqlserver/connector.py @@ -41,7 +41,7 @@ class Connector(GenericConnector): try: self.connector = pymssql.connect(host="%s:%d" % (self.hostname, self.port), user=self.user, password=self.password, database=self.db, login_timeout=conf.timeout, timeout=conf.timeout) - except pymssql.OperationalError, msg: + except (pymssql.InterfaceError, pymssql.OperationalError), msg: raise SqlmapConnectionException(msg) self.initCursor() diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py index 03d3e8256..0dbb6d6f8 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -513,22 +513,24 @@ class Databases: query = rootQuery.inband.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) query += condQuery elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - query = rootQuery.inband.query % unsafeSQLIdentificatorNaming(tbl.upper()) + query = rootQuery.inband.query % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper())) query += condQuery elif Backend.isDbms(DBMS.MSSQL): query = rootQuery.inband.query % (conf.db, conf.db, conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) query += condQuery.replace("[DB]", conf.db) - elif Backend.isDbms(DBMS.SQLITE): + elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD): query = rootQuery.inband.query % tbl values = inject.getValue(query, blind=False, time=False) if Backend.isDbms(DBMS.MSSQL) and isNoneValue(values): index, values = 1, [] + while True: query = rootQuery.inband.query2 % (conf.db, tbl, index) value = unArrayizeValue(inject.getValue(query, blind=False, time=False)) + if isNoneValue(value) or value == " ": break else: @@ -591,7 +593,7 @@ class Databases: query += condQuery elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - query = rootQuery.blind.count % unsafeSQLIdentificatorNaming(tbl.upper()) + query = rootQuery.blind.count % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper())) query += condQuery elif Backend.isDbms(DBMS.MSSQL): @@ -639,7 +641,7 @@ class Databases: query += condQuery field = None elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - query = rootQuery.blind.query % unsafeSQLIdentificatorNaming(tbl.upper()) + query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper())) query += condQuery field = None elif Backend.isDbms(DBMS.MSSQL): @@ -659,7 +661,7 @@ class Databases: if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column, unsafeSQLIdentificatorNaming(conf.db)) elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl.upper()), column) + query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl.upper()), column, unsafeSQLIdentificatorNaming(conf.db.upper())) elif Backend.isDbms(DBMS.MSSQL): query = rootQuery.blind.query2 % (conf.db, conf.db, conf.db, conf.db, column, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) @@ -736,7 +738,11 @@ class Databases: db = db.upper() table = table.upper() - query = "SELECT %s FROM %s.%s" % (queries[Backend.getIdentifiedDbms()].count.query % '*', safeSQLIdentificatorNaming(db), safeSQLIdentificatorNaming(table, True)) + if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD): + query = "SELECT %s FROM %s" % (queries[Backend.getIdentifiedDbms()].count.query % '*', safeSQLIdentificatorNaming(table, True)) + else: + query = "SELECT %s FROM %s.%s" % (queries[Backend.getIdentifiedDbms()].count.query % '*', safeSQLIdentificatorNaming(db), safeSQLIdentificatorNaming(table, True)) + count = inject.getValue(query, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) if isNumPosStrValue(count): @@ -759,7 +765,7 @@ class Databases: if not conf.db: conf.db, conf.tbl = conf.tbl.split(".") - if conf.tbl is not None and conf.db is None: + if conf.tbl is not None and conf.db is None and Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD): warnMsg = "missing database parameter. sqlmap is going to " warnMsg += "use the current database to retrieve the " warnMsg += "number of entries for table '%s'" % unsafeSQLIdentificatorNaming(conf.tbl) diff --git a/plugins/generic/search.py b/plugins/generic/search.py index 6917b10da..238ec8471 100644 --- a/plugins/generic/search.py +++ b/plugins/generic/search.py @@ -29,6 +29,7 @@ from lib.core.enums import PAYLOAD from lib.core.exception import SqlmapMissingMandatoryOptionException from lib.core.exception import SqlmapUserQuitException from lib.core.settings import CURRENT_DB +from lib.core.settings import METADB_SUFFIX from lib.request import inject from lib.techniques.brute.use import columnExists from lib.techniques.brute.use import tableExists @@ -199,7 +200,7 @@ class Search: if isinstance(values, basestring): values = [values] for value in values: - newValues.append(["SQLite_masterdb", value]) + newValues.append(["SQLite_%s" % METADB_SUFFIX, value]) values = newValues @@ -258,7 +259,7 @@ class Search: if tblConsider == "2": continue else: - foundTbls["SQLite_masterdb"] = [] + foundTbls["SQLite_%s" % METADB_SUFFIX] = [] for db in foundTbls.keys(): db = safeSQLIdentificatorNaming(db) diff --git a/xml/livetests.xml b/xml/livetests.xml index 841d2f174..6d74eec1b 100644 --- a/xml/livetests.xml +++ b/xml/livetests.xml @@ -888,6 +888,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1044,6 +1255,21 @@ + + + + + + + + + + + + + + + @@ -1671,6 +1897,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1845,6 +2359,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1886,7 +2467,6 @@ - diff --git a/xml/payloads.xml b/xml/payloads.xml index d9cb48b22..63245157c 100644 --- a/xml/payloads.xml +++ b/xml/payloads.xml @@ -1996,6 +1996,24 @@ Formats: SQLite + + Firebird inline queries + 6 + 2 + 1 + 1,2,3,8 + 3 + [QUERY] + + SELECT '[DELIMITER_START]'||(CASE [RANDNUM] WHEN [RANDNUM] THEN 1 ELSE 0 END)||'[DELIMITER_STOP]' FROM RDB$DATABASE + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ Firebird +
+
diff --git a/xml/queries.xml b/xml/queries.xml index dce39fa72..254242824 100644 --- a/xml/queries.xml +++ b/xml/queries.xml @@ -209,7 +209,7 @@ - + @@ -269,8 +269,8 @@ - - + + @@ -359,12 +359,12 @@ + - @@ -401,16 +401,16 @@ + + - - @@ -603,8 +603,8 @@ - - + +