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 @@
+
-
+