From ad228e69472df84f46601cad149b0e09fad619ab Mon Sep 17 00:00:00 2001 From: Bernardo Damele Date: Fri, 19 Dec 2008 20:09:46 +0000 Subject: [PATCH] Ahead with the improvements to the comparison algorithm. Added support internally to forge CASE statements, used only by --is-dba query at the moment. Allow DDL, DML (INSERT, UPDATE, etc.) from user in SQL query and SQL shell. Minor code adjustments. --- doc/ChangeLog | 4 ++++ lib/controller/checks.py | 34 +++++++++++++--------------- lib/core/agent.py | 20 +++++++++++++++++ lib/core/common.py | 37 +++++------------------------- lib/core/option.py | 2 +- lib/core/settings.py | 41 ++++++++++++++++++++++++++++++++++ lib/parse/queriesfile.py | 4 ++++ lib/request/comparison.py | 12 +++++----- lib/request/inject.py | 2 ++ plugins/generic/enumeration.py | 30 ++++++++++++++++++++++--- xml/queries.xml | 12 ++++++---- 11 files changed, 132 insertions(+), 66 deletions(-) diff --git a/doc/ChangeLog b/doc/ChangeLog index c5263a367..ab7c2d10f 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,7 +1,11 @@ sqlmap (0.6.4-1) stable; urgency=low + * Major improvement to the comparison algorithm to make it work also if + the page content changes at each refresh; (work in progress) * Minor enhancement to support an option (--is-dba) to show if the current user is a database management system administrator; + * Added support internally to forge CASE statements, used only by + --is-dba query at the moment; * Major bug fix to avoid tracebacks when multiple targets are specified and one of them is not reachable; * Minor bug fix to make the --postfix work even if --prefix is not diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 597b3a6b3..8edb65caa 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -306,30 +306,26 @@ def checkStability(): condition &= secondPage == thirdPage if condition == False: - # Prepare for the comparison algorithm based on Content-Length - # header value - contentLengths = [] - requestsHeaders = ( firstHeaders, secondHeaders, thirdHeaders ) + # Prepare for the comparison algorithm based on page length value + pageLengths = [] + requestsPages = ( firstPage, secondPage, thirdPage ) - for requestHeaders in requestsHeaders: - requestHeaders = str(requestHeaders).lower() + for requestPages in requestsPages: + pageLengths.append(len(str(requestPages))) - clHeader = re.search("content-length:\s+([\d]+)", requestHeaders, re.I | re.M) + if pageLengths: + conf.pageLengths = ( min(pageLengths) - ( ( min(pageLengths) * 2 ) / 100 ), + max(pageLengths) + ( ( max(pageLengths) * 2 ) / 100 ) ) - if clHeader and clHeader.group(1).isdigit(): - contentLengths.append(int(clHeader.group(1))) + if conf.pageLengths[0] < conf.pageLengths[1]: + warnMsg = "url is not stable, sqlmap inspected the page " + warnMsg += "and identified that page length can be used " + warnMsg += "in the comparison algorithm" + logger.warn(warnMsg) - if contentLengths: - conf.contentLengths = ( min(contentLengths), max(contentLengths) ) + kb.defaultResult = True - warnMsg = "url is not stable, sqlmap inspected the headers " - warnMsg += "and identified that Content-Length can be used " - warnMsg += "in the comparison algorithm" - logger.warn(warnMsg) - - kb.defaultResult = True - - return True + return True # Prepare for the comparison algorithm based on page content's # stable lines subset diff --git a/lib/core/agent.py b/lib/core/agent.py index 800be70f9..d656986a3 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -468,5 +468,25 @@ class Agent: return limitedQuery + def forgeCaseStatement(self, expression): + """ + Take in input a query string and return its CASE statement query + string. + + Example: + + Input: (SELECT super_priv FROM mysql.user WHERE user=(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)) LIMIT 0, 1)='Y' + Output: SELECT (CASE WHEN ((SELECT super_priv FROM mysql.user WHERE user=(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)) LIMIT 0, 1)='Y') THEN 1 ELSE 0 END) + + @param expression: expression to be processed + @type num: C{str} + + @return: processed expression + @rtype: C{str} + """ + + return queries[kb.dbms].case % expression + + # SQL agent agent = Agent() diff --git a/lib/core/common.py b/lib/core/common.py index 66153ec90..3cb6e61cf 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -40,6 +40,7 @@ from lib.core.data import logger from lib.core.data import temp from lib.core.exception import sqlmapFilePathException from lib.core.data import paths +from lib.core.settings import SQL_STATEMENTS from lib.core.settings import VERSION_STRING @@ -493,39 +494,11 @@ def parsePasswordHash(password): def cleanQuery(query): - # SQL SELECT statement - upperQuery = query.replace("select ", "SELECT ") - upperQuery = upperQuery.replace(" from ", " FROM ") - upperQuery = upperQuery.replace(" where ", " WHERE ") - upperQuery = upperQuery.replace(" group by ", " GROUP BY ") - upperQuery = upperQuery.replace(" order by ", " ORDER BY ") - upperQuery = upperQuery.replace(" having ", " HAVING ") - upperQuery = upperQuery.replace(" limit ", " LIMIT ") - upperQuery = upperQuery.replace(" offset ", " OFFSET ") - upperQuery = upperQuery.replace(" union all ", " UNION ALL ") - upperQuery = upperQuery.replace(" rownum ", " ROWNUM ") + upperQuery = query - # SQL data definition - upperQuery = upperQuery.replace(" create ", " CREATE ") - upperQuery = upperQuery.replace(" drop ", " DROP ") - upperQuery = upperQuery.replace(" truncate ", " TRUNCATE ") - upperQuery = upperQuery.replace(" alter ", " ALTER ") - - # SQL data manipulation - upperQuery = upperQuery.replace(" insert ", " INSERT ") - upperQuery = upperQuery.replace(" update ", " UPDATE ") - upperQuery = upperQuery.replace(" delete ", " DELETE ") - upperQuery = upperQuery.replace(" merge ", " MERGE ") - - # SQL data control - upperQuery = upperQuery.replace(" grant ", " GRANT ") - - # SQL transaction control - upperQuery = upperQuery.replace(" start transaction ", " START TRANSACTION ") - upperQuery = upperQuery.replace(" begin work ", " BEGIN WORK ") - upperQuery = upperQuery.replace(" begin transaction ", " BEGIN TRANSACTION ") - upperQuery = upperQuery.replace(" commit ", " COMMIT ") - upperQuery = upperQuery.replace(" rollback ", " ROLLBACK ") + for sqlStatements in SQL_STATEMENTS.values(): + for sqlStatement in sqlStatements: + upperQuery = upperQuery.replace(sqlStatement, sqlStatement.upper()) return upperQuery diff --git a/lib/core/option.py b/lib/core/option.py index 3d9050250..c1989968e 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -570,7 +570,7 @@ def __setConfAttributes(): logger.debug(debugMsg) conf.cj = None - conf.contentLengths = [] + conf.pageLengths = [] conf.dbmsHandler = None conf.dumpPath = None conf.equalLines = [] diff --git a/lib/core/settings.py b/lib/core/settings.py index f78dc8e15..18a158303 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -68,3 +68,44 @@ SUPPORTED_DBMS = MSSQL_ALIASES + MYSQL_ALIASES + PGSQL_ALIASES + ORACLE_ALIAS # TODO: port to command line/configuration file options? SECONDS = 5 RETRIES = 3 + +SQL_STATEMENTS = { + "SQL SELECT statement": ( + "select ", + " from ", + " where ", + " group by ", + " order by ", + " having ", + " limit ", + " offset ", + " union all ", + " rownum ", + ), + + "SQL data definition": ( + "create ", + "drop ", + "truncate ", + "alter ", + ), + + "SQL data manipulation": ( + "insert ", + "update ", + "delete ", + "merge ", + ), + + "SQL data control": ( + "grant ", + ), + + "SQL transaction": ( + "start transaction ", + "begin work ", + "begin transaction ", + "commit ", + "rollback ", + ), + } diff --git a/lib/parse/queriesfile.py b/lib/parse/queriesfile.py index 96cbe1f1e..ca88d4664 100644 --- a/lib/parse/queriesfile.py +++ b/lib/parse/queriesfile.py @@ -107,6 +107,10 @@ class queriesHandler(ContentHandler): data = sanitizeStr(attrs.get("query")) self.__queries.substring = data + elif name == "case": + data = sanitizeStr(attrs.get("query")) + self.__queries.case = data + elif name == "inference": data = sanitizeStr(attrs.get("query")) self.__queries.inference = data diff --git a/lib/request/comparison.py b/lib/request/comparison.py index dc60e66c3..edadd962f 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -68,15 +68,13 @@ def comparison(page, headers=None, content=False): return False # By default it returns the page content MD5 hash - if not conf.equalLines and not conf.contentLengths: + if not conf.equalLines and not conf.pageLengths: return md5.new(page).hexdigest() - # TODO: go ahead from here - - # Comparison algorithm based on Content-Length header value - elif conf.contentLengths: - minValue = conf.contentLengths[0] - 10 - maxValue = conf.contentLengths[1] + 10 + # Comparison algorithm based on page length value + elif conf.pageLengths: + minValue = conf.pageLengths[0] + maxValue = conf.pageLengths[1] if len(page) >= minValue and len(page) <= maxValue: return True diff --git a/lib/request/inject.py b/lib/request/inject.py index 200ddbf00..ea6b77742 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -336,6 +336,8 @@ def goStacked(expression): TODO: write description """ + expression = cleanQuery(expression) + comment = queries[kb.dbms].comment query = agent.prefixQuery("; %s" % expression) query = agent.postfixQuery("%s;%s" % (query, comment)) diff --git a/plugins/generic/enumeration.py b/plugins/generic/enumeration.py index 7db4defde..be4c056ce 100644 --- a/plugins/generic/enumeration.py +++ b/plugins/generic/enumeration.py @@ -39,6 +39,7 @@ from lib.core.exception import sqlmapMissingMandatoryOptionException from lib.core.exception import sqlmapNoneDataException from lib.core.exception import sqlmapUndefinedMethod from lib.core.exception import sqlmapUnsupportedFeatureException +from lib.core.settings import SQL_STATEMENTS from lib.core.shell import autoCompletion from lib.core.unescaper import unescaper from lib.parse.banner import bannerParser @@ -120,7 +121,7 @@ class Enumeration: infoMsg = "testing if current user is DBA" logger.info(infoMsg) - query = queries[kb.dbms].isDba + query = agent.forgeCaseStatement(queries[kb.dbms].isDba) self.isDba = inject.getValue(query) @@ -1038,10 +1039,33 @@ class Enumeration: def sqlQuery(self, query): - infoMsg = "fetching SQL SELECT query output: '%s'" % query + output = None + selectQuery = False + sqlType = None + + for sqlTitle, sqlStatements in SQL_STATEMENTS.items(): + for sqlStatement in sqlStatements: + if query.lower().startswith(sqlStatement): + sqlType = sqlTitle + + if sqlTitle == "SQL SELECT statement": + selectQuery = True + + break + + if sqlType: + infoMsg = "fetching %s query output: '%s'" % (sqlType, query) + else: + infoMsg = "fetching SQL query output: '%s'" % query + logger.info(infoMsg) - output = inject.getValue(query, fromUser=True) + if selectQuery == False: + # TODO: test if stacked queries are supported by the web + # application before injecting + inject.goStacked(query) + else: + output = inject.getValue(query, fromUser=True) if output == "Quit": return None diff --git a/xml/queries.xml b/xml/queries.xml index b38065946..1ce6629b5 100644 --- a/xml/queries.xml +++ b/xml/queries.xml @@ -23,11 +23,12 @@ --> + - + @@ -74,11 +75,12 @@ + - + @@ -124,11 +126,12 @@ + - + @@ -175,11 +178,12 @@ + - +