Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ad228e6

Browse files
committed
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.
1 parent 68354be commit ad228e6

11 files changed

Lines changed: 133 additions & 67 deletions

File tree

doc/ChangeLog

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
sqlmap (0.6.4-1) stable; urgency=low
22

3+
* Major improvement to the comparison algorithm to make it work also if
4+
the page content changes at each refresh; (work in progress)
35
* Minor enhancement to support an option (--is-dba) to show if the
46
current user is a database management system administrator;
7+
* Added support internally to forge CASE statements, used only by
8+
--is-dba query at the moment;
59
* Major bug fix to avoid tracebacks when multiple targets are specified
610
and one of them is not reachable;
711
* Minor bug fix to make the --postfix work even if --prefix is not

lib/controller/checks.py

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -306,30 +306,26 @@ def checkStability():
306306
condition &= secondPage == thirdPage
307307

308308
if condition == False:
309-
# Prepare for the comparison algorithm based on Content-Length
310-
# header value
311-
contentLengths = []
312-
requestsHeaders = ( firstHeaders, secondHeaders, thirdHeaders )
309+
# Prepare for the comparison algorithm based on page length value
310+
pageLengths = []
311+
requestsPages = ( firstPage, secondPage, thirdPage )
313312

314-
for requestHeaders in requestsHeaders:
315-
requestHeaders = str(requestHeaders).lower()
313+
for requestPages in requestsPages:
314+
pageLengths.append(len(str(requestPages)))
316315

317-
clHeader = re.search("content-length:\s+([\d]+)", requestHeaders, re.I | re.M)
316+
if pageLengths:
317+
conf.pageLengths = ( min(pageLengths) - ( ( min(pageLengths) * 2 ) / 100 ),
318+
max(pageLengths) + ( ( max(pageLengths) * 2 ) / 100 ) )
318319

319-
if clHeader and clHeader.group(1).isdigit():
320-
contentLengths.append(int(clHeader.group(1)))
320+
if conf.pageLengths[0] < conf.pageLengths[1]:
321+
warnMsg = "url is not stable, sqlmap inspected the page "
322+
warnMsg += "and identified that page length can be used "
323+
warnMsg += "in the comparison algorithm"
324+
logger.warn(warnMsg)
321325

322-
if contentLengths:
323-
conf.contentLengths = ( min(contentLengths), max(contentLengths) )
326+
kb.defaultResult = True
324327

325-
warnMsg = "url is not stable, sqlmap inspected the headers "
326-
warnMsg += "and identified that Content-Length can be used "
327-
warnMsg += "in the comparison algorithm"
328-
logger.warn(warnMsg)
329-
330-
kb.defaultResult = True
331-
332-
return True
328+
return True
333329

334330
# Prepare for the comparison algorithm based on page content's
335331
# stable lines subset

lib/core/agent.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,5 +468,25 @@ def limitQuery(self, num, query, fieldsList=None):
468468
return limitedQuery
469469

470470

471+
def forgeCaseStatement(self, expression):
472+
"""
473+
Take in input a query string and return its CASE statement query
474+
string.
475+
476+
Example:
477+
478+
Input: (SELECT super_priv FROM mysql.user WHERE user=(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)) LIMIT 0, 1)='Y'
479+
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)
480+
481+
@param expression: expression to be processed
482+
@type num: C{str}
483+
484+
@return: processed expression
485+
@rtype: C{str}
486+
"""
487+
488+
return queries[kb.dbms].case % expression
489+
490+
471491
# SQL agent
472492
agent = Agent()

lib/core/common.py

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from lib.core.data import temp
4141
from lib.core.exception import sqlmapFilePathException
4242
from lib.core.data import paths
43+
from lib.core.settings import SQL_STATEMENTS
4344
from lib.core.settings import VERSION_STRING
4445

4546

@@ -493,39 +494,11 @@ def parsePasswordHash(password):
493494

494495

495496
def cleanQuery(query):
496-
# SQL SELECT statement
497-
upperQuery = query.replace("select ", "SELECT ")
498-
upperQuery = upperQuery.replace(" from ", " FROM ")
499-
upperQuery = upperQuery.replace(" where ", " WHERE ")
500-
upperQuery = upperQuery.replace(" group by ", " GROUP BY ")
501-
upperQuery = upperQuery.replace(" order by ", " ORDER BY ")
502-
upperQuery = upperQuery.replace(" having ", " HAVING ")
503-
upperQuery = upperQuery.replace(" limit ", " LIMIT ")
504-
upperQuery = upperQuery.replace(" offset ", " OFFSET ")
505-
upperQuery = upperQuery.replace(" union all ", " UNION ALL ")
506-
upperQuery = upperQuery.replace(" rownum ", " ROWNUM ")
507-
508-
# SQL data definition
509-
upperQuery = upperQuery.replace(" create ", " CREATE ")
510-
upperQuery = upperQuery.replace(" drop ", " DROP ")
511-
upperQuery = upperQuery.replace(" truncate ", " TRUNCATE ")
512-
upperQuery = upperQuery.replace(" alter ", " ALTER ")
513-
514-
# SQL data manipulation
515-
upperQuery = upperQuery.replace(" insert ", " INSERT ")
516-
upperQuery = upperQuery.replace(" update ", " UPDATE ")
517-
upperQuery = upperQuery.replace(" delete ", " DELETE ")
518-
upperQuery = upperQuery.replace(" merge ", " MERGE ")
519-
520-
# SQL data control
521-
upperQuery = upperQuery.replace(" grant ", " GRANT ")
522-
523-
# SQL transaction control
524-
upperQuery = upperQuery.replace(" start transaction ", " START TRANSACTION ")
525-
upperQuery = upperQuery.replace(" begin work ", " BEGIN WORK ")
526-
upperQuery = upperQuery.replace(" begin transaction ", " BEGIN TRANSACTION ")
527-
upperQuery = upperQuery.replace(" commit ", " COMMIT ")
528-
upperQuery = upperQuery.replace(" rollback ", " ROLLBACK ")
497+
upperQuery = query
498+
499+
for sqlStatements in SQL_STATEMENTS.values():
500+
for sqlStatement in sqlStatements:
501+
upperQuery = upperQuery.replace(sqlStatement, sqlStatement.upper())
529502

530503
return upperQuery
531504

lib/core/option.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,7 @@ def __setConfAttributes():
570570
logger.debug(debugMsg)
571571

572572
conf.cj = None
573-
conf.contentLengths = []
573+
conf.pageLengths = []
574574
conf.dbmsHandler = None
575575
conf.dumpPath = None
576576
conf.equalLines = []

lib/core/settings.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,44 @@
6868
# TODO: port to command line/configuration file options?
6969
SECONDS = 5
7070
RETRIES = 3
71+
72+
SQL_STATEMENTS = {
73+
"SQL SELECT statement": (
74+
"select ",
75+
" from ",
76+
" where ",
77+
" group by ",
78+
" order by ",
79+
" having ",
80+
" limit ",
81+
" offset ",
82+
" union all ",
83+
" rownum ",
84+
),
85+
86+
"SQL data definition": (
87+
"create ",
88+
"drop ",
89+
"truncate ",
90+
"alter ",
91+
),
92+
93+
"SQL data manipulation": (
94+
"insert ",
95+
"update ",
96+
"delete ",
97+
"merge ",
98+
),
99+
100+
"SQL data control": (
101+
"grant ",
102+
),
103+
104+
"SQL transaction": (
105+
"start transaction ",
106+
"begin work ",
107+
"begin transaction ",
108+
"commit ",
109+
"rollback ",
110+
),
111+
}

lib/parse/queriesfile.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ def startElement(self, name, attrs):
107107
data = sanitizeStr(attrs.get("query"))
108108
self.__queries.substring = data
109109

110+
elif name == "case":
111+
data = sanitizeStr(attrs.get("query"))
112+
self.__queries.case = data
113+
110114
elif name == "inference":
111115
data = sanitizeStr(attrs.get("query"))
112116
self.__queries.inference = data

lib/request/comparison.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,13 @@ def comparison(page, headers=None, content=False):
6868
return False
6969

7070
# By default it returns the page content MD5 hash
71-
if not conf.equalLines and not conf.contentLengths:
71+
if not conf.equalLines and not conf.pageLengths:
7272
return md5.new(page).hexdigest()
7373

74-
# TODO: go ahead from here
75-
76-
# Comparison algorithm based on Content-Length header value
77-
elif conf.contentLengths:
78-
minValue = conf.contentLengths[0] - 10
79-
maxValue = conf.contentLengths[1] + 10
74+
# Comparison algorithm based on page length value
75+
elif conf.pageLengths:
76+
minValue = conf.pageLengths[0]
77+
maxValue = conf.pageLengths[1]
8078

8179
if len(page) >= minValue and len(page) <= maxValue:
8280
return True

lib/request/inject.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,8 @@ def goStacked(expression):
336336
TODO: write description
337337
"""
338338

339+
expression = cleanQuery(expression)
340+
339341
comment = queries[kb.dbms].comment
340342
query = agent.prefixQuery("; %s" % expression)
341343
query = agent.postfixQuery("%s;%s" % (query, comment))

plugins/generic/enumeration.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from lib.core.exception import sqlmapNoneDataException
4040
from lib.core.exception import sqlmapUndefinedMethod
4141
from lib.core.exception import sqlmapUnsupportedFeatureException
42+
from lib.core.settings import SQL_STATEMENTS
4243
from lib.core.shell import autoCompletion
4344
from lib.core.unescaper import unescaper
4445
from lib.parse.banner import bannerParser
@@ -120,7 +121,7 @@ def isDba(self):
120121
infoMsg = "testing if current user is DBA"
121122
logger.info(infoMsg)
122123

123-
query = queries[kb.dbms].isDba
124+
query = agent.forgeCaseStatement(queries[kb.dbms].isDba)
124125

125126
self.isDba = inject.getValue(query)
126127

@@ -1038,10 +1039,33 @@ def dumpAll(self):
10381039

10391040

10401041
def sqlQuery(self, query):
1041-
infoMsg = "fetching SQL SELECT query output: '%s'" % query
1042+
output = None
1043+
selectQuery = False
1044+
sqlType = None
1045+
1046+
for sqlTitle, sqlStatements in SQL_STATEMENTS.items():
1047+
for sqlStatement in sqlStatements:
1048+
if query.lower().startswith(sqlStatement):
1049+
sqlType = sqlTitle
1050+
1051+
if sqlTitle == "SQL SELECT statement":
1052+
selectQuery = True
1053+
1054+
break
1055+
1056+
if sqlType:
1057+
infoMsg = "fetching %s query output: '%s'" % (sqlType, query)
1058+
else:
1059+
infoMsg = "fetching SQL query output: '%s'" % query
1060+
10421061
logger.info(infoMsg)
10431062

1044-
output = inject.getValue(query, fromUser=True)
1063+
if selectQuery == False:
1064+
# TODO: test if stacked queries are supported by the web
1065+
# application before injecting
1066+
inject.goStacked(query)
1067+
else:
1068+
output = inject.getValue(query, fromUser=True)
10451069

10461070
if output == "Quit":
10471071
return None

0 commit comments

Comments
 (0)