Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sqlmapproject
GitHub Repository: sqlmapproject/sqlmap
Path: blob/master/lib/controller/checks.py
2989 views
1
#!/usr/bin/env python
2
3
"""
4
Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org)
5
See the file 'LICENSE' for copying permission
6
"""
7
8
import copy
9
import logging
10
import random
11
import re
12
import socket
13
import time
14
15
from extra.beep.beep import beep
16
from lib.core.agent import agent
17
from lib.core.common import Backend
18
from lib.core.common import extractRegexResult
19
from lib.core.common import extractTextTagContent
20
from lib.core.common import filterNone
21
from lib.core.common import findDynamicContent
22
from lib.core.common import Format
23
from lib.core.common import getFilteredPageContent
24
from lib.core.common import getLastRequestHTTPError
25
from lib.core.common import getPublicTypeMembers
26
from lib.core.common import getSafeExString
27
from lib.core.common import getSortedInjectionTests
28
from lib.core.common import hashDBRetrieve
29
from lib.core.common import hashDBWrite
30
from lib.core.common import intersect
31
from lib.core.common import isDigit
32
from lib.core.common import joinValue
33
from lib.core.common import listToStrValue
34
from lib.core.common import parseFilePaths
35
from lib.core.common import popValue
36
from lib.core.common import pushValue
37
from lib.core.common import randomInt
38
from lib.core.common import randomStr
39
from lib.core.common import readInput
40
from lib.core.common import showStaticWords
41
from lib.core.common import singleTimeLogMessage
42
from lib.core.common import singleTimeWarnMessage
43
from lib.core.common import unArrayizeValue
44
from lib.core.common import wasLastResponseDBMSError
45
from lib.core.common import wasLastResponseHTTPError
46
from lib.core.compat import xrange
47
from lib.core.convert import getUnicode
48
from lib.core.data import conf
49
from lib.core.data import kb
50
from lib.core.data import logger
51
from lib.core.datatype import AttribDict
52
from lib.core.datatype import InjectionDict
53
from lib.core.decorators import stackedmethod
54
from lib.core.dicts import FROM_DUMMY_TABLE
55
from lib.core.dicts import HEURISTIC_NULL_EVAL
56
from lib.core.enums import DBMS
57
from lib.core.enums import HASHDB_KEYS
58
from lib.core.enums import HEURISTIC_TEST
59
from lib.core.enums import HTTP_HEADER
60
from lib.core.enums import HTTPMETHOD
61
from lib.core.enums import NOTE
62
from lib.core.enums import NULLCONNECTION
63
from lib.core.enums import PAYLOAD
64
from lib.core.enums import PLACE
65
from lib.core.enums import REDIRECTION
66
from lib.core.enums import WEB_PLATFORM
67
from lib.core.exception import SqlmapConnectionException
68
from lib.core.exception import SqlmapDataException
69
from lib.core.exception import SqlmapNoneDataException
70
from lib.core.exception import SqlmapSilentQuitException
71
from lib.core.exception import SqlmapSkipTargetException
72
from lib.core.exception import SqlmapUserQuitException
73
from lib.core.settings import BOUNDED_INJECTION_MARKER
74
from lib.core.settings import CANDIDATE_SENTENCE_MIN_LENGTH
75
from lib.core.settings import CHECK_INTERNET_ADDRESS
76
from lib.core.settings import CHECK_INTERNET_CODE
77
from lib.core.settings import DEFAULT_COOKIE_DELIMITER
78
from lib.core.settings import DEFAULT_GET_POST_DELIMITER
79
from lib.core.settings import DUMMY_NON_SQLI_CHECK_APPENDIX
80
from lib.core.settings import FI_ERROR_REGEX
81
from lib.core.settings import FORMAT_EXCEPTION_STRINGS
82
from lib.core.settings import HEURISTIC_CHECK_ALPHABET
83
from lib.core.settings import INFERENCE_EQUALS_CHAR
84
from lib.core.settings import IPS_WAF_CHECK_PAYLOAD
85
from lib.core.settings import IPS_WAF_CHECK_RATIO
86
from lib.core.settings import IPS_WAF_CHECK_TIMEOUT
87
from lib.core.settings import MAX_DIFFLIB_SEQUENCE_LENGTH
88
from lib.core.settings import MAX_STABILITY_DELAY
89
from lib.core.settings import NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH
90
from lib.core.settings import PRECONNECT_INCOMPATIBLE_SERVERS
91
from lib.core.settings import SINGLE_QUOTE_MARKER
92
from lib.core.settings import SLEEP_TIME_MARKER
93
from lib.core.settings import SUHOSIN_MAX_VALUE_LENGTH
94
from lib.core.settings import SUPPORTED_DBMS
95
from lib.core.settings import UPPER_RATIO_BOUND
96
from lib.core.settings import URI_HTTP_HEADER
97
from lib.core.threads import getCurrentThreadData
98
from lib.core.unescaper import unescaper
99
from lib.request.connect import Connect as Request
100
from lib.request.comparison import comparison
101
from lib.request.inject import checkBooleanExpression
102
from lib.request.templates import getPageTemplate
103
from lib.techniques.union.test import unionTest
104
from lib.techniques.union.use import configUnion
105
from thirdparty import six
106
from thirdparty.six.moves import http_client as _http_client
107
108
def checkSqlInjection(place, parameter, value):
109
# Store here the details about boundaries and payload used to
110
# successfully inject
111
injection = InjectionDict()
112
113
# Localized thread data needed for some methods
114
threadData = getCurrentThreadData()
115
116
# Favoring non-string specific boundaries in case of digit-like parameter values
117
if isDigit(value):
118
kb.cache.intBoundaries = kb.cache.intBoundaries or sorted(copy.deepcopy(conf.boundaries), key=lambda boundary: any(_ in (boundary.prefix or "") or _ in (boundary.suffix or "") for _ in ('"', '\'')))
119
boundaries = kb.cache.intBoundaries
120
elif value.isalpha():
121
kb.cache.alphaBoundaries = kb.cache.alphaBoundaries or sorted(copy.deepcopy(conf.boundaries), key=lambda boundary: not any(_ in (boundary.prefix or "") or _ in (boundary.suffix or "") for _ in ('"', '\'')))
122
boundaries = kb.cache.alphaBoundaries
123
else:
124
boundaries = conf.boundaries
125
126
# Set the flag for SQL injection test mode
127
kb.testMode = True
128
129
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
130
tests = getSortedInjectionTests()
131
seenPayload = set()
132
133
kb.data.setdefault("randomInt", str(randomInt(10)))
134
kb.data.setdefault("randomStr", str(randomStr(10)))
135
136
while tests:
137
test = tests.pop(0)
138
139
try:
140
if kb.endDetection:
141
break
142
143
if conf.dbms is None:
144
# If the DBMS has not yet been fingerprinted (via simple heuristic check
145
# or via DBMS-specific payload) and boolean-based blind has been identified
146
# then attempt to identify with a simple DBMS specific boolean-based
147
# test what the DBMS may be
148
if not injection.dbms and PAYLOAD.TECHNIQUE.BOOLEAN in injection.data:
149
if not Backend.getIdentifiedDbms() and kb.heuristicDbms is None and not kb.droppingRequests:
150
kb.heuristicDbms = heuristicCheckDbms(injection)
151
152
# If the DBMS has already been fingerprinted (via DBMS-specific
153
# error message, simple heuristic check or via DBMS-specific
154
# payload), ask the user to limit the tests to the fingerprinted
155
# DBMS
156
157
if kb.reduceTests is None and not conf.testFilter and (intersect(Backend.getErrorParsedDBMSes(), SUPPORTED_DBMS, True) or kb.heuristicDbms or injection.dbms):
158
msg = "it looks like the back-end DBMS is '%s'. " % (Format.getErrorParsedDBMSes() or kb.heuristicDbms or joinValue(injection.dbms, '/'))
159
msg += "Do you want to skip test payloads specific for other DBMSes? [Y/n]"
160
kb.reduceTests = (Backend.getErrorParsedDBMSes() or [kb.heuristicDbms]) if readInput(msg, default='Y', boolean=True) else []
161
162
# If the DBMS has been fingerprinted (via DBMS-specific error
163
# message, via simple heuristic check or via DBMS-specific
164
# payload), ask the user to extend the tests to all DBMS-specific,
165
# regardless of --level and --risk values provided
166
if kb.extendTests is None and not conf.testFilter and (conf.level < 5 or conf.risk < 3) and (intersect(Backend.getErrorParsedDBMSes(), SUPPORTED_DBMS, True) or kb.heuristicDbms or injection.dbms):
167
msg = "for the remaining tests, do you want to include all tests "
168
msg += "for '%s' extending provided " % (Format.getErrorParsedDBMSes() or kb.heuristicDbms or joinValue(injection.dbms, '/'))
169
msg += "level (%d)" % conf.level if conf.level < 5 else ""
170
msg += " and " if conf.level < 5 and conf.risk < 3 else ""
171
msg += "risk (%d)" % conf.risk if conf.risk < 3 else ""
172
msg += " values? [Y/n]" if conf.level < 5 and conf.risk < 3 else " value? [Y/n]"
173
kb.extendTests = (Backend.getErrorParsedDBMSes() or [kb.heuristicDbms]) if readInput(msg, default='Y', boolean=True) else []
174
175
title = test.title
176
kb.testType = stype = test.stype
177
clause = test.clause
178
unionExtended = False
179
trueCode, falseCode = None, None
180
181
if conf.httpCollector is not None:
182
conf.httpCollector.setExtendedArguments({
183
"_title": title,
184
"_place": place,
185
"_parameter": parameter,
186
})
187
188
if stype == PAYLOAD.TECHNIQUE.UNION:
189
configUnion(test.request.char)
190
191
if "[CHAR]" in title:
192
if conf.uChar is None:
193
continue
194
else:
195
title = title.replace("[CHAR]", conf.uChar)
196
197
elif "[RANDNUM]" in title or "(NULL)" in title:
198
title = title.replace("[RANDNUM]", "random number")
199
200
if test.request.columns == "[COLSTART]-[COLSTOP]":
201
if conf.uCols is None:
202
continue
203
else:
204
title = title.replace("[COLSTART]", str(conf.uColsStart))
205
title = title.replace("[COLSTOP]", str(conf.uColsStop))
206
207
elif conf.uCols is not None:
208
debugMsg = "skipping test '%s' because the user " % title
209
debugMsg += "provided custom column range %s" % conf.uCols
210
logger.debug(debugMsg)
211
continue
212
213
match = re.search(r"(\d+)-(\d+)", test.request.columns)
214
if match and injection.data:
215
lower, upper = int(match.group(1)), int(match.group(2))
216
for _ in (lower, upper):
217
if _ > 1:
218
__ = 2 * (_ - 1) + 1 if _ == lower else 2 * _
219
unionExtended = True
220
test.request._columns = test.request.columns
221
test.request.columns = re.sub(r"\b%d\b" % _, str(__), test.request.columns)
222
title = re.sub(r"\b%d\b" % _, str(__), title)
223
test.title = re.sub(r"\b%d\b" % _, str(__), test.title)
224
225
# Skip test if the user's wants to test only for a specific
226
# technique
227
if conf.technique and isinstance(conf.technique, list) and stype not in conf.technique:
228
debugMsg = "skipping test '%s' because user " % title
229
debugMsg += "specified testing of only "
230
debugMsg += "%s techniques" % " & ".join(PAYLOAD.SQLINJECTION[_] for _ in conf.technique)
231
logger.debug(debugMsg)
232
continue
233
234
# Skip test if it is the same SQL injection type already
235
# identified by another test
236
if injection.data and stype in injection.data:
237
debugMsg = "skipping test '%s' because " % title
238
debugMsg += "the payload for %s has " % PAYLOAD.SQLINJECTION[stype]
239
debugMsg += "already been identified"
240
logger.debug(debugMsg)
241
continue
242
243
# Parse DBMS-specific payloads' details
244
if "details" in test and "dbms" in test.details:
245
payloadDbms = test.details.dbms
246
else:
247
payloadDbms = None
248
249
# Skip tests if title, vector or DBMS is not included by the
250
# given test filter
251
if conf.testFilter and not any(conf.testFilter in str(item) or re.search(conf.testFilter, str(item), re.I) for item in (test.title, test.vector, payloadDbms)):
252
debugMsg = "skipping test '%s' because its " % title
253
debugMsg += "name/vector/DBMS is not included by the given filter"
254
logger.debug(debugMsg)
255
continue
256
257
# Skip tests if title, vector or DBMS is included by the
258
# given skip filter
259
if conf.testSkip and any(conf.testSkip in str(item) or re.search(conf.testSkip, str(item), re.I) for item in (test.title, test.vector, payloadDbms)):
260
debugMsg = "skipping test '%s' because its " % title
261
debugMsg += "name/vector/DBMS is included by the given skip filter"
262
logger.debug(debugMsg)
263
continue
264
265
if payloadDbms is not None:
266
# Skip DBMS-specific test if it does not match the user's
267
# provided DBMS
268
if conf.dbms and not intersect(payloadDbms, conf.dbms, True):
269
debugMsg = "skipping test '%s' because " % title
270
debugMsg += "its declared DBMS is different than provided"
271
logger.debug(debugMsg)
272
continue
273
274
elif kb.dbmsFilter and not intersect(payloadDbms, kb.dbmsFilter, True):
275
debugMsg = "skipping test '%s' because " % title
276
debugMsg += "its declared DBMS is different than provided"
277
logger.debug(debugMsg)
278
continue
279
280
elif kb.reduceTests is False:
281
pass
282
283
# Skip DBMS-specific test if it does not match the
284
# previously identified DBMS (via DBMS-specific payload)
285
elif injection.dbms and not intersect(payloadDbms, injection.dbms, True):
286
debugMsg = "skipping test '%s' because " % title
287
debugMsg += "its declared DBMS is different than identified"
288
logger.debug(debugMsg)
289
continue
290
291
# Skip DBMS-specific test if it does not match the
292
# previously identified DBMS (via DBMS-specific error message)
293
elif kb.reduceTests and not intersect(payloadDbms, kb.reduceTests, True):
294
debugMsg = "skipping test '%s' because the heuristic " % title
295
debugMsg += "tests showed that the back-end DBMS "
296
debugMsg += "could be '%s'" % unArrayizeValue(kb.reduceTests)
297
logger.debug(debugMsg)
298
continue
299
300
# If the user did not decide to extend the tests to all
301
# DBMS-specific or the test payloads is not specific to the
302
# identified DBMS, then only test for it if both level and risk
303
# are below the corrisponding configuration's level and risk
304
# values
305
if not conf.testFilter and not (kb.extendTests and intersect(payloadDbms, kb.extendTests, True)):
306
# Skip test if the risk is higher than the provided (or default)
307
# value
308
if test.risk > conf.risk:
309
debugMsg = "skipping test '%s' because the risk (%d) " % (title, test.risk)
310
debugMsg += "is higher than the provided (%d)" % conf.risk
311
logger.debug(debugMsg)
312
continue
313
314
# Skip test if the level is higher than the provided (or default)
315
# value
316
if test.level > conf.level:
317
debugMsg = "skipping test '%s' because the level (%d) " % (title, test.level)
318
debugMsg += "is higher than the provided (%d)" % conf.level
319
logger.debug(debugMsg)
320
continue
321
322
# Skip test if it does not match the same SQL injection clause
323
# already identified by another test
324
clauseMatch = False
325
326
for clauseTest in clause:
327
if injection.clause is not None and clauseTest in injection.clause:
328
clauseMatch = True
329
break
330
331
if clause != [0] and injection.clause and injection.clause != [0] and not clauseMatch:
332
debugMsg = "skipping test '%s' because the clauses " % title
333
debugMsg += "differ from the clause already identified"
334
logger.debug(debugMsg)
335
continue
336
337
# Skip test if the user provided custom character (for UNION-based payloads)
338
if conf.uChar is not None and ("random number" in title or "(NULL)" in title):
339
debugMsg = "skipping test '%s' because the user " % title
340
debugMsg += "provided a specific character, %s" % conf.uChar
341
logger.debug(debugMsg)
342
continue
343
344
if stype == PAYLOAD.TECHNIQUE.UNION:
345
match = re.search(r"(\d+)-(\d+)", test.request.columns)
346
if match and not injection.data:
347
_ = test.request.columns.split('-')[-1]
348
if conf.uCols is None and _.isdigit():
349
if kb.futileUnion is None:
350
msg = "it is recommended to perform "
351
msg += "only basic UNION tests if there is not "
352
msg += "at least one other (potential) "
353
msg += "technique found. Do you want to reduce "
354
msg += "the number of requests? [Y/n] "
355
kb.futileUnion = readInput(msg, default='Y', boolean=True)
356
357
if kb.futileUnion and int(_) > 10:
358
debugMsg = "skipping test '%s'" % title
359
logger.debug(debugMsg)
360
continue
361
362
infoMsg = "testing '%s'" % title
363
logger.info(infoMsg)
364
365
# Force back-end DBMS according to the current test DBMS value
366
# for proper payload unescaping
367
Backend.forceDbms(payloadDbms[0] if isinstance(payloadDbms, list) else payloadDbms)
368
369
# Parse test's <request>
370
comment = agent.getComment(test.request) if len(conf.boundaries) > 1 else None
371
fstPayload = agent.cleanupPayload(test.request.payload, origValue=value if place not in (PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER) and BOUNDED_INJECTION_MARKER not in (value or "") else None)
372
373
for boundary in boundaries:
374
injectable = False
375
376
# Skip boundary if the level is higher than the provided (or
377
# default) value
378
# Parse boundary's <level>
379
if boundary.level > conf.level and not (kb.extendTests and intersect(payloadDbms, kb.extendTests, True)):
380
continue
381
382
# Skip boundary if it does not match against test's <clause>
383
# Parse test's <clause> and boundary's <clause>
384
clauseMatch = False
385
386
for clauseTest in test.clause:
387
if clauseTest in boundary.clause:
388
clauseMatch = True
389
break
390
391
if test.clause != [0] and boundary.clause != [0] and not clauseMatch:
392
continue
393
394
# Skip boundary if it does not match against test's <where>
395
# Parse test's <where> and boundary's <where>
396
whereMatch = False
397
398
for where in test.where:
399
if where in boundary.where:
400
whereMatch = True
401
break
402
403
if not whereMatch:
404
continue
405
406
# Parse boundary's <prefix>, <suffix> and <ptype>
407
prefix = boundary.prefix or ""
408
suffix = boundary.suffix or ""
409
ptype = boundary.ptype
410
411
# Options --prefix/--suffix have a higher priority (if set by user)
412
prefix = conf.prefix if conf.prefix is not None else prefix
413
suffix = conf.suffix if conf.suffix is not None else suffix
414
comment = None if conf.suffix is not None else comment
415
416
# If the previous injections succeeded, we know which prefix,
417
# suffix and parameter type to use for further tests, no
418
# need to cycle through the boundaries for the following tests
419
condBound = (injection.prefix is not None and injection.suffix is not None)
420
condBound &= (injection.prefix != prefix or injection.suffix != suffix)
421
condType = injection.ptype is not None and injection.ptype != ptype
422
423
# If the payload is an inline query test for it regardless
424
# of previously identified injection types
425
if stype != PAYLOAD.TECHNIQUE.QUERY and (condBound or condType):
426
continue
427
428
# For each test's <where>
429
for where in test.where:
430
templatePayload = None
431
vector = None
432
433
origValue = value
434
if kb.customInjectionMark in origValue:
435
origValue = origValue.split(kb.customInjectionMark)[0]
436
origValue = re.search(r"(\w*)\Z", origValue).group(1)
437
438
# Treat the parameter original value according to the
439
# test's <where> tag
440
if where == PAYLOAD.WHERE.ORIGINAL or conf.prefix:
441
if kb.tamperFunctions:
442
templatePayload = agent.payload(place, parameter, value="", newValue=origValue, where=where)
443
elif where == PAYLOAD.WHERE.NEGATIVE:
444
# Use different page template than the original
445
# one as we are changing parameters value, which
446
# will likely result in a different content
447
448
if conf.invalidLogical:
449
_ = int(kb.data.randomInt[:2])
450
origValue = "%s AND %s LIKE %s" % (origValue, _, _ + 1)
451
elif conf.invalidBignum:
452
origValue = kb.data.randomInt[:6]
453
elif conf.invalidString:
454
origValue = kb.data.randomStr[:6]
455
else:
456
origValue = "-%s" % kb.data.randomInt[:4]
457
458
templatePayload = agent.payload(place, parameter, value="", newValue=origValue, where=where)
459
elif where == PAYLOAD.WHERE.REPLACE:
460
origValue = ""
461
462
kb.pageTemplate, kb.errorIsNone = getPageTemplate(templatePayload, place)
463
464
# Forge request payload by prepending with boundary's
465
# prefix and appending the boundary's suffix to the
466
# test's ' <payload><comment> ' string
467
if fstPayload:
468
boundPayload = agent.prefixQuery(fstPayload, prefix, where, clause)
469
boundPayload = agent.suffixQuery(boundPayload, comment, suffix, where)
470
reqPayload = agent.payload(place, parameter, newValue=boundPayload, where=where)
471
472
if reqPayload:
473
stripPayload = re.sub(r"(\A|\b|_)([A-Za-z]{4}((?<!LIKE))|\d+)(_|\b|\Z)", r"\g<1>.\g<4>", reqPayload)
474
if stripPayload in seenPayload:
475
continue
476
else:
477
seenPayload.add(stripPayload)
478
else:
479
reqPayload = None
480
481
# Perform the test's request and check whether or not the
482
# payload was successful
483
# Parse test's <response>
484
for method, check in test.response.items():
485
check = agent.cleanupPayload(check, origValue=value if place not in (PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER) and BOUNDED_INJECTION_MARKER not in (value or "") else None)
486
487
# In case of boolean-based blind SQL injection
488
if method == PAYLOAD.METHOD.COMPARISON:
489
# Generate payload used for comparison
490
def genCmpPayload():
491
sndPayload = agent.cleanupPayload(test.response.comparison, origValue=value if place not in (PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER) and BOUNDED_INJECTION_MARKER not in (value or "") else None)
492
493
# Forge response payload by prepending with
494
# boundary's prefix and appending the boundary's
495
# suffix to the test's ' <payload><comment> '
496
# string
497
boundPayload = agent.prefixQuery(sndPayload, prefix, where, clause)
498
boundPayload = agent.suffixQuery(boundPayload, comment, suffix, where)
499
cmpPayload = agent.payload(place, parameter, newValue=boundPayload, where=where)
500
501
return cmpPayload
502
503
# Useful to set kb.matchRatio at first based on False response content
504
kb.matchRatio = None
505
kb.negativeLogic = (where == PAYLOAD.WHERE.NEGATIVE)
506
suggestion = None
507
Request.queryPage(genCmpPayload(), place, raise404=False)
508
falsePage, falseHeaders, falseCode = threadData.lastComparisonPage or "", threadData.lastComparisonHeaders, threadData.lastComparisonCode
509
falseRawResponse = "%s%s" % (falseHeaders, falsePage)
510
511
# Checking if there is difference between current FALSE, original and heuristics page (i.e. not used parameter)
512
if not any((kb.negativeLogic, conf.string, conf.notString, conf.code)):
513
try:
514
ratio = 1.0
515
seqMatcher = getCurrentThreadData().seqMatcher
516
517
for current in (kb.originalPage, kb.heuristicPage):
518
seqMatcher.set_seq1(current or "")
519
seqMatcher.set_seq2(falsePage or "")
520
ratio *= seqMatcher.quick_ratio()
521
522
if ratio == 1.0:
523
continue
524
except (MemoryError, OverflowError):
525
pass
526
527
# Perform the test's True request
528
trueResult = Request.queryPage(reqPayload, place, raise404=False)
529
truePage, trueHeaders, trueCode = threadData.lastComparisonPage or "", threadData.lastComparisonHeaders, threadData.lastComparisonCode
530
trueRawResponse = "%s%s" % (trueHeaders, truePage)
531
532
if trueResult and not (truePage == falsePage and not any((kb.nullConnection, conf.code))):
533
# Perform the test's False request
534
falseResult = Request.queryPage(genCmpPayload(), place, raise404=False)
535
536
if not falseResult:
537
if kb.negativeLogic:
538
boundPayload = agent.prefixQuery(kb.data.randomStr, prefix, where, clause)
539
boundPayload = agent.suffixQuery(boundPayload, comment, suffix, where)
540
errorPayload = agent.payload(place, parameter, newValue=boundPayload, where=where)
541
542
errorResult = Request.queryPage(errorPayload, place, raise404=False)
543
if errorResult:
544
continue
545
elif kb.heuristicPage and not any((conf.string, conf.notString, conf.regexp, conf.code, kb.nullConnection)):
546
_ = comparison(kb.heuristicPage, None, getRatioValue=True)
547
if (_ or 0) > (kb.matchRatio or 0):
548
kb.matchRatio = _
549
logger.debug("adjusting match ratio for current parameter to %.3f" % kb.matchRatio)
550
551
# Reducing false-positive "appears" messages in heavily dynamic environment
552
if kb.heavilyDynamic and not Request.queryPage(reqPayload, place, raise404=False):
553
continue
554
555
injectable = True
556
557
elif (threadData.lastComparisonRatio or 0) > UPPER_RATIO_BOUND and not any((conf.string, conf.notString, conf.regexp, conf.code, kb.nullConnection)):
558
originalSet = set(getFilteredPageContent(kb.pageTemplate, True, "\n").split("\n"))
559
trueSet = set(getFilteredPageContent(truePage, True, "\n").split("\n"))
560
falseSet = set(getFilteredPageContent(falsePage, True, "\n").split("\n"))
561
562
if threadData.lastErrorPage and threadData.lastErrorPage[1]:
563
errorSet = set(getFilteredPageContent(threadData.lastErrorPage[1], True, "\n").split("\n"))
564
else:
565
errorSet = set()
566
567
if originalSet == trueSet != falseSet:
568
candidates = trueSet - falseSet - errorSet
569
570
if candidates:
571
candidates = sorted(candidates, key=len)
572
for candidate in candidates:
573
if re.match(r"\A[\w.,! ]+\Z", candidate) and ' ' in candidate and candidate.strip() and len(candidate) > CANDIDATE_SENTENCE_MIN_LENGTH:
574
suggestion = conf.string = candidate
575
injectable = True
576
577
infoMsg = "%sparameter '%s' appears to be '%s' injectable (with --string=\"%s\")" % ("%s " % paramType if paramType != parameter else "", parameter, title, repr(conf.string).lstrip('u').strip("'"))
578
logger.info(infoMsg)
579
580
break
581
582
if injectable:
583
if kb.pageStable and not any((conf.string, conf.notString, conf.regexp, conf.code, kb.nullConnection)):
584
if all((falseCode, trueCode)) and falseCode != trueCode and trueCode != kb.heuristicCode:
585
suggestion = conf.code = trueCode
586
587
infoMsg = "%sparameter '%s' appears to be '%s' injectable (with --code=%d)" % ("%s " % paramType if paramType != parameter else "", parameter, title, conf.code)
588
logger.info(infoMsg)
589
else:
590
trueSet = set(extractTextTagContent(trueRawResponse))
591
trueSet |= set(__ for _ in trueSet for __ in _.split())
592
593
falseSet = set(extractTextTagContent(falseRawResponse))
594
falseSet |= set(__ for _ in falseSet for __ in _.split())
595
596
if threadData.lastErrorPage and threadData.lastErrorPage[1]:
597
errorSet = set(extractTextTagContent(threadData.lastErrorPage[1]))
598
errorSet |= set(__ for _ in errorSet for __ in _.split())
599
else:
600
errorSet = set()
601
602
candidates = filterNone(_.strip() if _.strip() in trueRawResponse and _.strip() not in falseRawResponse else None for _ in (trueSet - falseSet - errorSet))
603
604
if candidates:
605
candidates = sorted(candidates, key=len)
606
for candidate in candidates:
607
if re.match(r"\A\w{2,}\Z", candidate): # Note: length of 1 (e.g. --string=5) could cause trouble, especially in error message pages with partially reflected payload content
608
break
609
610
suggestion = conf.string = candidate
611
612
infoMsg = "%sparameter '%s' appears to be '%s' injectable (with --string=\"%s\")" % ("%s " % paramType if paramType != parameter else "", parameter, title, repr(conf.string).lstrip('u').strip("'"))
613
logger.info(infoMsg)
614
615
if not any((conf.string, conf.notString)):
616
candidates = filterNone(_.strip() if _.strip() in falseRawResponse and _.strip() not in trueRawResponse else None for _ in (falseSet - trueSet))
617
618
if candidates:
619
candidates = sorted(candidates, key=len)
620
for candidate in candidates:
621
if re.match(r"\A\w+\Z", candidate):
622
break
623
624
suggestion = conf.notString = candidate
625
626
infoMsg = "%sparameter '%s' appears to be '%s' injectable (with --not-string=\"%s\")" % ("%s " % paramType if paramType != parameter else "", parameter, title, repr(conf.notString).lstrip('u').strip("'"))
627
logger.info(infoMsg)
628
629
if not suggestion:
630
infoMsg = "%sparameter '%s' appears to be '%s' injectable " % ("%s " % paramType if paramType != parameter else "", parameter, title)
631
singleTimeLogMessage(infoMsg)
632
633
# In case of error-based SQL injection
634
elif method == PAYLOAD.METHOD.GREP:
635
# Perform the test's request and grep the response
636
# body for the test's <grep> regular expression
637
try:
638
page, headers, _ = Request.queryPage(reqPayload, place, content=True, raise404=False)
639
output = extractRegexResult(check, page, re.DOTALL | re.IGNORECASE)
640
output = output or extractRegexResult(check, threadData.lastHTTPError[2] if wasLastResponseHTTPError() else None, re.DOTALL | re.IGNORECASE)
641
output = output or extractRegexResult(check, listToStrValue((headers[key] for key in headers if key.lower() != URI_HTTP_HEADER.lower()) if headers else None), re.DOTALL | re.IGNORECASE)
642
output = output or extractRegexResult(check, threadData.lastRedirectMsg[1] if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)
643
644
if output:
645
result = output == '1'
646
647
if result:
648
infoMsg = "%sparameter '%s' is '%s' injectable " % ("%s " % paramType if paramType != parameter else "", parameter, title)
649
logger.info(infoMsg)
650
651
injectable = True
652
653
except SqlmapConnectionException as ex:
654
debugMsg = "problem occurred most likely because the "
655
debugMsg += "server hasn't recovered as expected from the "
656
debugMsg += "used error-based payload ('%s')" % getSafeExString(ex)
657
logger.debug(debugMsg)
658
659
# In case of time-based blind or stacked queries
660
# SQL injections
661
elif method == PAYLOAD.METHOD.TIME:
662
# Perform the test's request
663
trueResult = Request.queryPage(reqPayload, place, timeBasedCompare=True, raise404=False)
664
trueCode = threadData.lastCode
665
666
if trueResult:
667
# Extra validation step (e.g. to check for DROP protection mechanisms)
668
if SLEEP_TIME_MARKER in reqPayload:
669
falseResult = Request.queryPage(reqPayload.replace(SLEEP_TIME_MARKER, "0"), place, timeBasedCompare=True, raise404=False)
670
if falseResult:
671
continue
672
673
# Confirm test's results
674
trueResult = Request.queryPage(reqPayload, place, timeBasedCompare=True, raise404=False)
675
676
if trueResult:
677
infoMsg = "%sparameter '%s' appears to be '%s' injectable " % ("%s " % paramType if paramType != parameter else "", parameter, title)
678
logger.info(infoMsg)
679
680
injectable = True
681
682
# In case of UNION query SQL injection
683
elif method == PAYLOAD.METHOD.UNION:
684
# Test for UNION injection and set the sample
685
# payload as well as the vector.
686
# NOTE: vector is set to a tuple with 6 elements,
687
# used afterwards by Agent.forgeUnionQuery()
688
# method to forge the UNION query payload
689
690
configUnion(test.request.char, test.request.columns)
691
692
if len(kb.dbmsFilter or []) == 1:
693
Backend.forceDbms(kb.dbmsFilter[0])
694
elif not Backend.getIdentifiedDbms():
695
if kb.heuristicDbms is None:
696
if kb.heuristicTest == HEURISTIC_TEST.POSITIVE or injection.data:
697
warnMsg = "using unescaped version of the test "
698
warnMsg += "because of zero knowledge of the "
699
warnMsg += "back-end DBMS. You can try to "
700
warnMsg += "explicitly set it with option '--dbms'"
701
singleTimeWarnMessage(warnMsg)
702
else:
703
Backend.forceDbms(kb.heuristicDbms)
704
705
if unionExtended:
706
infoMsg = "automatically extending ranges for UNION "
707
infoMsg += "query injection technique tests as "
708
infoMsg += "there is at least one other (potential) "
709
infoMsg += "technique found"
710
singleTimeLogMessage(infoMsg)
711
712
# Test for UNION query SQL injection
713
reqPayload, vector = unionTest(comment, place, parameter, value, prefix, suffix)
714
715
if isinstance(reqPayload, six.string_types):
716
infoMsg = "%sparameter '%s' is '%s' injectable" % ("%s " % paramType if paramType != parameter else "", parameter, title)
717
logger.info(infoMsg)
718
719
injectable = True
720
721
# Overwrite 'where' because it can be set
722
# by unionTest() directly
723
where = vector[6]
724
725
kb.previousMethod = method
726
727
if conf.offline:
728
injectable = False
729
730
# If the injection test was successful feed the injection
731
# object with the test's details
732
if injectable is True:
733
# Feed with the boundaries details only the first time a
734
# test has been successful
735
if injection.place is None or injection.parameter is None:
736
if place in (PLACE.USER_AGENT, PLACE.REFERER, PLACE.HOST):
737
injection.parameter = place
738
else:
739
injection.parameter = parameter
740
741
injection.place = place
742
injection.ptype = ptype
743
injection.prefix = prefix
744
injection.suffix = suffix
745
injection.clause = clause
746
747
# Feed with test details every time a test is successful
748
if hasattr(test, "details"):
749
for key, value in test.details.items():
750
if key == "dbms":
751
injection.dbms = value
752
753
if not isinstance(value, list):
754
Backend.setDbms(value)
755
else:
756
Backend.forceDbms(value[0], True)
757
758
elif key == "dbms_version" and injection.dbms_version is None and not conf.testFilter:
759
injection.dbms_version = Backend.setVersion(value)
760
761
elif key == "os" and injection.os is None:
762
injection.os = Backend.setOs(value)
763
764
if vector is None and "vector" in test and test.vector is not None:
765
vector = test.vector
766
767
injection.data[stype] = AttribDict()
768
injection.data[stype].title = title
769
injection.data[stype].payload = agent.removePayloadDelimiters(reqPayload)
770
injection.data[stype].where = where
771
injection.data[stype].vector = vector
772
injection.data[stype].comment = comment
773
injection.data[stype].templatePayload = templatePayload
774
injection.data[stype].matchRatio = kb.matchRatio
775
injection.data[stype].trueCode = trueCode
776
injection.data[stype].falseCode = falseCode
777
778
injection.conf.textOnly = conf.textOnly
779
injection.conf.titles = conf.titles
780
injection.conf.code = conf.code
781
injection.conf.string = conf.string
782
injection.conf.notString = conf.notString
783
injection.conf.regexp = conf.regexp
784
injection.conf.optimize = conf.optimize
785
786
if conf.beep:
787
beep()
788
789
# There is no need to perform this test for other
790
# <where> tags
791
break
792
793
if injectable is True:
794
kb.vulnHosts.add(conf.hostname)
795
break
796
797
# Reset forced back-end DBMS value
798
Backend.flushForcedDbms()
799
800
except KeyboardInterrupt:
801
warnMsg = "user aborted during detection phase"
802
logger.warning(warnMsg)
803
804
if conf.multipleTargets:
805
msg = "how do you want to proceed? [ne(X)t target/(s)kip current test/(e)nd detection phase/(n)ext parameter/(c)hange verbosity/(q)uit]"
806
choice = readInput(msg, default='X', checkBatch=False).upper()
807
else:
808
msg = "how do you want to proceed? [(S)kip current test/(e)nd detection phase/(n)ext parameter/(c)hange verbosity/(q)uit]"
809
choice = readInput(msg, default='S', checkBatch=False).upper()
810
811
if choice == 'X':
812
if conf.multipleTargets:
813
raise SqlmapSkipTargetException
814
elif choice == 'C':
815
choice = None
816
while not ((choice or "").isdigit() and 0 <= int(choice) <= 6):
817
if choice:
818
logger.warning("invalid value")
819
msg = "enter new verbosity level: [0-6] "
820
choice = readInput(msg, default=str(conf.verbose), checkBatch=False)
821
conf.verbose = int(choice)
822
setVerbosity()
823
if hasattr(test.request, "columns") and hasattr(test.request, "_columns"):
824
test.request.columns = test.request._columns
825
delattr(test.request, "_columns")
826
tests.insert(0, test)
827
elif choice == 'N':
828
return None
829
elif choice == 'E':
830
kb.endDetection = True
831
elif choice == 'Q':
832
raise SqlmapUserQuitException
833
834
finally:
835
# Reset forced back-end DBMS value
836
Backend.flushForcedDbms()
837
838
Backend.flushForcedDbms(True)
839
840
# Return the injection object
841
if injection.place is not None and injection.parameter is not None:
842
if not conf.dropSetCookie and PAYLOAD.TECHNIQUE.BOOLEAN in injection.data and injection.data[PAYLOAD.TECHNIQUE.BOOLEAN].vector.startswith('OR'):
843
warnMsg = "in OR boolean-based injection cases, please consider usage "
844
warnMsg += "of switch '--drop-set-cookie' if you experience any "
845
warnMsg += "problems during data retrieval"
846
logger.warning(warnMsg)
847
848
if not checkFalsePositives(injection):
849
if conf.hostname in kb.vulnHosts:
850
kb.vulnHosts.remove(conf.hostname)
851
if NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE not in injection.notes:
852
injection.notes.append(NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE)
853
else:
854
injection = None
855
856
if injection and NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE not in injection.notes:
857
checkSuhosinPatch(injection)
858
checkFilteredChars(injection)
859
860
return injection
861
862
@stackedmethod
863
def heuristicCheckDbms(injection):
864
"""
865
This functions is called when boolean-based blind is identified with a
866
generic payload and the DBMS has not yet been fingerprinted to attempt
867
to identify with a simple DBMS specific boolean-based test what the DBMS
868
may be
869
"""
870
871
retVal = False
872
873
if conf.skipHeuristics:
874
return retVal
875
876
pushValue(kb.injection)
877
kb.injection = injection
878
879
for dbms in getPublicTypeMembers(DBMS, True):
880
randStr1, randStr2 = randomStr(), randomStr()
881
882
Backend.forceDbms(dbms)
883
884
if dbms in HEURISTIC_NULL_EVAL:
885
result = checkBooleanExpression("(SELECT %s%s) IS NULL" % (HEURISTIC_NULL_EVAL[dbms], FROM_DUMMY_TABLE.get(dbms, "")))
886
elif not ((randStr1 in unescaper.escape("'%s'" % randStr1)) and list(FROM_DUMMY_TABLE.values()).count(FROM_DUMMY_TABLE.get(dbms, "")) != 1):
887
result = checkBooleanExpression("(SELECT '%s'%s)=%s%s%s" % (randStr1, FROM_DUMMY_TABLE.get(dbms, ""), SINGLE_QUOTE_MARKER, randStr1, SINGLE_QUOTE_MARKER))
888
else:
889
result = False
890
891
if result:
892
if not checkBooleanExpression("(SELECT '%s'%s)=%s%s%s" % (randStr1, FROM_DUMMY_TABLE.get(dbms, ""), SINGLE_QUOTE_MARKER, randStr2, SINGLE_QUOTE_MARKER)):
893
retVal = dbms
894
break
895
896
Backend.flushForcedDbms()
897
kb.injection = popValue()
898
899
if retVal:
900
infoMsg = "heuristic (extended) test shows that the back-end DBMS " # Not as important as "parsing" counter-part (because of false-positives)
901
infoMsg += "could be '%s' " % retVal
902
logger.info(infoMsg)
903
904
kb.heuristicExtendedDbms = retVal
905
906
return retVal
907
908
@stackedmethod
909
def checkFalsePositives(injection):
910
"""
911
Checks for false positives (only in single special cases)
912
"""
913
914
retVal = True
915
916
if all(_ in (PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) for _ in injection.data) or (len(injection.data) == 1 and PAYLOAD.TECHNIQUE.UNION in injection.data and "Generic" in injection.data[PAYLOAD.TECHNIQUE.UNION].title):
917
pushValue(kb.injection)
918
919
infoMsg = "checking if the injection point on %s " % injection.place
920
infoMsg += "parameter '%s' is a false positive" % injection.parameter
921
logger.info(infoMsg)
922
923
def _():
924
return int(randomInt(2)) + 1
925
926
kb.injection = injection
927
928
for level in xrange(conf.level):
929
while True:
930
randInt1, randInt2, randInt3 = (_() for j in xrange(3))
931
932
randInt1 = min(randInt1, randInt2, randInt3)
933
randInt3 = max(randInt1, randInt2, randInt3)
934
935
if conf.string and any(conf.string in getUnicode(_) for _ in (randInt1, randInt2, randInt3)):
936
continue
937
938
if conf.notString and any(conf.notString in getUnicode(_) for _ in (randInt1, randInt2, randInt3)):
939
continue
940
941
if randInt3 > randInt2 > randInt1:
942
break
943
944
if not checkBooleanExpression("%d%s%d" % (randInt1, INFERENCE_EQUALS_CHAR, randInt1)):
945
retVal = False
946
break
947
948
if PAYLOAD.TECHNIQUE.BOOLEAN not in injection.data:
949
checkBooleanExpression("%d%s%d" % (randInt1, INFERENCE_EQUALS_CHAR, randInt2)) # just in case if DBMS hasn't properly recovered from previous delayed request
950
951
if checkBooleanExpression("%d%s%d" % (randInt1, INFERENCE_EQUALS_CHAR, randInt3)): # this must not be evaluated to True
952
retVal = False
953
break
954
955
elif checkBooleanExpression("%d%s%d" % (randInt3, INFERENCE_EQUALS_CHAR, randInt2)): # this must not be evaluated to True
956
retVal = False
957
break
958
959
elif not checkBooleanExpression("%d%s%d" % (randInt2, INFERENCE_EQUALS_CHAR, randInt2)): # this must be evaluated to True
960
retVal = False
961
break
962
963
elif checkBooleanExpression("%d %d" % (randInt3, randInt2)): # this must not be evaluated to True (invalid statement)
964
retVal = False
965
break
966
967
if not retVal:
968
warnMsg = "false positive or unexploitable injection point detected"
969
logger.warning(warnMsg)
970
971
kb.injection = popValue()
972
973
return retVal
974
975
@stackedmethod
976
def checkSuhosinPatch(injection):
977
"""
978
Checks for existence of Suhosin-patch (and alike) protection mechanism(s)
979
"""
980
981
if injection.place in (PLACE.GET, PLACE.URI):
982
debugMsg = "checking for parameter length "
983
debugMsg += "constraining mechanisms"
984
logger.debug(debugMsg)
985
986
pushValue(kb.injection)
987
988
kb.injection = injection
989
randInt = randomInt()
990
991
if not checkBooleanExpression("%d=%s%d" % (randInt, ' ' * SUHOSIN_MAX_VALUE_LENGTH, randInt)):
992
warnMsg = "parameter length constraining "
993
warnMsg += "mechanism detected (e.g. Suhosin patch). "
994
warnMsg += "Potential problems in enumeration phase can be expected"
995
logger.warning(warnMsg)
996
997
kb.injection = popValue()
998
999
@stackedmethod
1000
def checkFilteredChars(injection):
1001
debugMsg = "checking for filtered characters"
1002
logger.debug(debugMsg)
1003
1004
pushValue(kb.injection)
1005
1006
kb.injection = injection
1007
randInt = randomInt()
1008
1009
# all other techniques are already using parentheses in tests
1010
if len(injection.data) == 1 and PAYLOAD.TECHNIQUE.BOOLEAN in injection.data:
1011
if not checkBooleanExpression("(%d)=%d" % (randInt, randInt)):
1012
warnMsg = "it appears that some non-alphanumeric characters (i.e. ()) are "
1013
warnMsg += "filtered by the back-end server. There is a strong "
1014
warnMsg += "possibility that sqlmap won't be able to properly "
1015
warnMsg += "exploit this vulnerability"
1016
logger.warning(warnMsg)
1017
1018
# inference techniques depend on character '>'
1019
if not any(_ in injection.data for _ in (PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.QUERY)):
1020
if not checkBooleanExpression("%d>%d" % (randInt + 1, randInt)):
1021
warnMsg = "it appears that the character '>' is "
1022
warnMsg += "filtered by the back-end server. You are strongly "
1023
warnMsg += "advised to rerun with the '--tamper=between'"
1024
logger.warning(warnMsg)
1025
1026
kb.injection = popValue()
1027
1028
def heuristicCheckSqlInjection(place, parameter):
1029
if conf.skipHeuristics:
1030
return None
1031
1032
origValue = conf.paramDict[place][parameter]
1033
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
1034
1035
prefix = ""
1036
suffix = ""
1037
randStr = ""
1038
1039
if conf.prefix or conf.suffix:
1040
if conf.prefix:
1041
prefix = conf.prefix
1042
1043
if conf.suffix:
1044
suffix = conf.suffix
1045
1046
while randStr.count('\'') != 1 or randStr.count('\"') != 1:
1047
randStr = randomStr(length=10, alphabet=HEURISTIC_CHECK_ALPHABET)
1048
1049
kb.heuristicMode = True
1050
1051
payload = "%s%s%s" % (prefix, randStr, suffix)
1052
payload = agent.payload(place, parameter, newValue=payload)
1053
page, _, code = Request.queryPage(payload, place, content=True, raise404=False)
1054
1055
kb.heuristicPage = page
1056
kb.heuristicCode = code
1057
kb.heuristicMode = False
1058
1059
parseFilePaths(page)
1060
result = wasLastResponseDBMSError()
1061
1062
infoMsg = "heuristic (basic) test shows that %sparameter '%s' might " % ("%s " % paramType if paramType != parameter else "", parameter)
1063
1064
def _(page):
1065
return any(_ in (page or "") for _ in FORMAT_EXCEPTION_STRINGS)
1066
1067
casting = _(page) and not _(kb.originalPage)
1068
1069
if not casting and not result and kb.dynamicParameter and origValue.isdigit() and not kb.heavilyDynamic:
1070
randInt = int(randomInt())
1071
payload = "%s%s%s" % (prefix, "%d-%d" % (int(origValue) + randInt, randInt), suffix)
1072
payload = agent.payload(place, parameter, newValue=payload, where=PAYLOAD.WHERE.REPLACE)
1073
result = Request.queryPage(payload, place, raise404=False)
1074
1075
if not result:
1076
randStr = randomStr()
1077
payload = "%s%s%s" % (prefix, "%s.%d%s" % (origValue, random.randint(1, 9), randStr), suffix)
1078
payload = agent.payload(place, parameter, newValue=payload, where=PAYLOAD.WHERE.REPLACE)
1079
casting = Request.queryPage(payload, place, raise404=False)
1080
1081
kb.heuristicTest = HEURISTIC_TEST.CASTED if casting else HEURISTIC_TEST.NEGATIVE if not result else HEURISTIC_TEST.POSITIVE
1082
1083
if kb.heavilyDynamic:
1084
debugMsg = "heuristic check stopped because of heavy dynamicity"
1085
logger.debug(debugMsg)
1086
return kb.heuristicTest
1087
1088
if casting:
1089
errMsg = "possible %s casting detected (e.g. '" % ("integer" if origValue.isdigit() else "type")
1090
1091
platform = conf.url.split('.')[-1].lower()
1092
if platform == WEB_PLATFORM.ASP:
1093
errMsg += "%s=CInt(request.querystring(\"%s\"))" % (parameter, parameter)
1094
elif platform == WEB_PLATFORM.ASPX:
1095
errMsg += "int.TryParse(Request.QueryString[\"%s\"], out %s)" % (parameter, parameter)
1096
elif platform == WEB_PLATFORM.JSP:
1097
errMsg += "%s=Integer.parseInt(request.getParameter(\"%s\"))" % (parameter, parameter)
1098
else:
1099
errMsg += "$%s=intval($_REQUEST[\"%s\"])" % (parameter, parameter)
1100
1101
errMsg += "') at the back-end web application"
1102
logger.error(errMsg)
1103
1104
if kb.ignoreCasted is None:
1105
message = "do you want to skip those kind of cases (and save scanning time)? %s " % ("[Y/n]" if conf.multipleTargets else "[y/N]")
1106
kb.ignoreCasted = readInput(message, default='Y' if conf.multipleTargets else 'N', boolean=True)
1107
1108
elif result:
1109
infoMsg += "be injectable"
1110
if Backend.getErrorParsedDBMSes():
1111
infoMsg += " (possible DBMS: '%s')" % Format.getErrorParsedDBMSes()
1112
logger.info(infoMsg)
1113
1114
else:
1115
infoMsg += "not be injectable"
1116
logger.warning(infoMsg)
1117
1118
kb.heuristicMode = True
1119
kb.disableHtmlDecoding = True
1120
1121
randStr1, randStr2 = randomStr(NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH), randomStr(NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH)
1122
value = "%s%s%s" % (randStr1, DUMMY_NON_SQLI_CHECK_APPENDIX, randStr2)
1123
payload = "%s%s%s" % (prefix, "'%s" % value, suffix)
1124
payload = agent.payload(place, parameter, newValue=payload)
1125
page, _, _ = Request.queryPage(payload, place, content=True, raise404=False)
1126
1127
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
1128
1129
# Reference: https://bugs.python.org/issue18183
1130
if value.upper() in (page or "").upper():
1131
infoMsg = "heuristic (XSS) test shows that %sparameter '%s' might be vulnerable to cross-site scripting (XSS) attacks" % ("%s " % paramType if paramType != parameter else "", parameter)
1132
logger.info(infoMsg)
1133
1134
if conf.beep:
1135
beep()
1136
1137
for match in re.finditer(FI_ERROR_REGEX, page or ""):
1138
if randStr1.lower() in match.group(0).lower():
1139
infoMsg = "heuristic (FI) test shows that %sparameter '%s' might be vulnerable to file inclusion (FI) attacks" % ("%s " % paramType if paramType != parameter else "", parameter)
1140
logger.info(infoMsg)
1141
1142
if conf.beep:
1143
beep()
1144
1145
break
1146
1147
kb.disableHtmlDecoding = False
1148
kb.heuristicMode = False
1149
1150
return kb.heuristicTest
1151
1152
def checkDynParam(place, parameter, value):
1153
"""
1154
This function checks if the URL parameter is dynamic. If it is
1155
dynamic, the content of the page differs, otherwise the
1156
dynamicity might depend on another parameter.
1157
"""
1158
1159
if kb.choices.redirect:
1160
return None
1161
1162
kb.matchRatio = None
1163
dynResult = None
1164
randInt = randomInt()
1165
1166
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
1167
1168
infoMsg = "testing if %sparameter '%s' is dynamic" % ("%s " % paramType if paramType != parameter else "", parameter)
1169
logger.info(infoMsg)
1170
1171
try:
1172
payload = agent.payload(place, parameter, value, getUnicode(randInt))
1173
dynResult = Request.queryPage(payload, place, raise404=False)
1174
except SqlmapConnectionException:
1175
pass
1176
1177
result = None if dynResult is None else not dynResult
1178
kb.dynamicParameter = result
1179
1180
return result
1181
1182
def checkDynamicContent(firstPage, secondPage):
1183
"""
1184
This function checks for the dynamic content in the provided pages
1185
"""
1186
1187
if kb.nullConnection:
1188
debugMsg = "dynamic content checking skipped "
1189
debugMsg += "because NULL connection used"
1190
logger.debug(debugMsg)
1191
return
1192
1193
if any(page is None for page in (firstPage, secondPage)):
1194
warnMsg = "can't check dynamic content "
1195
warnMsg += "because of lack of page content"
1196
logger.critical(warnMsg)
1197
return
1198
1199
if firstPage and secondPage and any(len(_) > MAX_DIFFLIB_SEQUENCE_LENGTH for _ in (firstPage, secondPage)):
1200
ratio = None
1201
else:
1202
try:
1203
seqMatcher = getCurrentThreadData().seqMatcher
1204
seqMatcher.set_seq1(firstPage)
1205
seqMatcher.set_seq2(secondPage)
1206
ratio = seqMatcher.quick_ratio()
1207
except MemoryError:
1208
ratio = None
1209
1210
if ratio is None:
1211
kb.skipSeqMatcher = True
1212
1213
# In case of an intolerable difference turn on dynamicity removal engine
1214
elif ratio <= UPPER_RATIO_BOUND:
1215
findDynamicContent(firstPage, secondPage)
1216
1217
count = 0
1218
while not Request.queryPage():
1219
count += 1
1220
1221
if count > conf.retries:
1222
warnMsg = "target URL content appears to be too dynamic. "
1223
warnMsg += "Switching to '--text-only' "
1224
logger.warning(warnMsg)
1225
1226
conf.textOnly = True
1227
return
1228
1229
warnMsg = "target URL content appears to be heavily dynamic. "
1230
warnMsg += "sqlmap is going to retry the request(s)"
1231
singleTimeLogMessage(warnMsg, logging.CRITICAL)
1232
1233
kb.heavilyDynamic = True
1234
1235
secondPage, _, _ = Request.queryPage(content=True)
1236
findDynamicContent(firstPage, secondPage)
1237
1238
def checkStability():
1239
"""
1240
This function checks if the URL content is stable requesting the
1241
same page two times with a small delay within each request to
1242
assume that it is stable.
1243
1244
In case the content of the page differs when requesting
1245
the same page, the dynamicity might depend on other parameters,
1246
like for instance string matching (--string).
1247
"""
1248
1249
infoMsg = "testing if the target URL content is stable"
1250
logger.info(infoMsg)
1251
1252
firstPage = kb.originalPage # set inside checkConnection()
1253
1254
delay = MAX_STABILITY_DELAY - (time.time() - (kb.originalPageTime or 0))
1255
delay = max(0, min(MAX_STABILITY_DELAY, delay))
1256
time.sleep(delay)
1257
1258
secondPage, _, _ = Request.queryPage(content=True, noteResponseTime=False, raise404=False)
1259
1260
if kb.choices.redirect:
1261
return None
1262
1263
kb.pageStable = (firstPage == secondPage)
1264
1265
if kb.pageStable:
1266
if firstPage:
1267
infoMsg = "target URL content is stable"
1268
logger.info(infoMsg)
1269
else:
1270
errMsg = "there was an error checking the stability of page "
1271
errMsg += "because of lack of content. Please check the "
1272
errMsg += "page request results (and probable errors) by "
1273
errMsg += "using higher verbosity levels"
1274
logger.error(errMsg)
1275
1276
else:
1277
warnMsg = "target URL content is not stable (i.e. content differs). sqlmap will base the page "
1278
warnMsg += "comparison on a sequence matcher. If no dynamic nor "
1279
warnMsg += "injectable parameters are detected, or in case of "
1280
warnMsg += "junk results, refer to user's manual paragraph "
1281
warnMsg += "'Page comparison'"
1282
logger.warning(warnMsg)
1283
1284
message = "how do you want to proceed? [(C)ontinue/(s)tring/(r)egex/(q)uit] "
1285
choice = readInput(message, default='C').upper()
1286
1287
if choice == 'Q':
1288
raise SqlmapUserQuitException
1289
1290
elif choice == 'S':
1291
showStaticWords(firstPage, secondPage)
1292
1293
message = "please enter value for parameter 'string': "
1294
string = readInput(message)
1295
1296
if string:
1297
conf.string = string
1298
1299
if kb.nullConnection:
1300
debugMsg = "turning off NULL connection "
1301
debugMsg += "support because of string checking"
1302
logger.debug(debugMsg)
1303
1304
kb.nullConnection = None
1305
else:
1306
errMsg = "Empty value supplied"
1307
raise SqlmapNoneDataException(errMsg)
1308
1309
elif choice == 'R':
1310
message = "please enter value for parameter 'regex': "
1311
regex = readInput(message)
1312
1313
if regex:
1314
conf.regex = regex
1315
1316
if kb.nullConnection:
1317
debugMsg = "turning off NULL connection "
1318
debugMsg += "support because of regex checking"
1319
logger.debug(debugMsg)
1320
1321
kb.nullConnection = None
1322
else:
1323
errMsg = "Empty value supplied"
1324
raise SqlmapNoneDataException(errMsg)
1325
1326
else:
1327
checkDynamicContent(firstPage, secondPage)
1328
1329
return kb.pageStable
1330
1331
@stackedmethod
1332
def checkWaf():
1333
"""
1334
Reference: http://seclists.org/nmap-dev/2011/q2/att-1005/http-waf-detect.nse
1335
"""
1336
1337
if any((conf.string, conf.notString, conf.regexp, conf.dummy, conf.offline, conf.skipWaf)):
1338
return None
1339
1340
if kb.originalCode == _http_client.NOT_FOUND:
1341
return None
1342
1343
_ = hashDBRetrieve(HASHDB_KEYS.CHECK_WAF_RESULT, True)
1344
if _ is not None:
1345
if _:
1346
warnMsg = "previous heuristics detected that the target "
1347
warnMsg += "is protected by some kind of WAF/IPS"
1348
logger.critical(warnMsg)
1349
return _
1350
1351
if not kb.originalPage:
1352
return None
1353
1354
infoMsg = "checking if the target is protected by "
1355
infoMsg += "some kind of WAF/IPS"
1356
logger.info(infoMsg)
1357
1358
retVal = False
1359
payload = "%d %s" % (randomInt(), IPS_WAF_CHECK_PAYLOAD)
1360
1361
place = PLACE.GET
1362
if PLACE.URI in conf.parameters:
1363
value = "%s=%s" % (randomStr(), agent.addPayloadDelimiters(payload))
1364
else:
1365
value = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + DEFAULT_GET_POST_DELIMITER
1366
value += "%s=%s" % (randomStr(), agent.addPayloadDelimiters(payload))
1367
1368
pushValue(kb.choices.redirect)
1369
pushValue(kb.resendPostOnRedirect)
1370
pushValue(conf.timeout)
1371
1372
kb.choices.redirect = REDIRECTION.YES
1373
kb.resendPostOnRedirect = False
1374
conf.timeout = IPS_WAF_CHECK_TIMEOUT
1375
1376
try:
1377
retVal = (Request.queryPage(place=place, value=value, getRatioValue=True, noteResponseTime=False, silent=True, raise404=False, disableTampering=True)[1] or 0) < IPS_WAF_CHECK_RATIO
1378
except SqlmapConnectionException:
1379
retVal = True
1380
finally:
1381
kb.matchRatio = None
1382
1383
conf.timeout = popValue()
1384
kb.resendPostOnRedirect = popValue()
1385
kb.choices.redirect = popValue()
1386
1387
hashDBWrite(HASHDB_KEYS.CHECK_WAF_RESULT, retVal, True)
1388
1389
if retVal:
1390
if not kb.identifiedWafs:
1391
warnMsg = "heuristics detected that the target "
1392
warnMsg += "is protected by some kind of WAF/IPS"
1393
logger.critical(warnMsg)
1394
1395
message = "are you sure that you want to "
1396
message += "continue with further target testing? [Y/n] "
1397
choice = readInput(message, default='Y', boolean=True)
1398
1399
if not choice:
1400
raise SqlmapUserQuitException
1401
else:
1402
if not conf.tamper:
1403
warnMsg = "please consider usage of tamper scripts (option '--tamper')"
1404
singleTimeWarnMessage(warnMsg)
1405
1406
return retVal
1407
1408
@stackedmethod
1409
def checkNullConnection():
1410
"""
1411
Reference: http://www.wisec.it/sectou.php?id=472f952d79293
1412
"""
1413
1414
if conf.data:
1415
return False
1416
1417
_ = hashDBRetrieve(HASHDB_KEYS.CHECK_NULL_CONNECTION_RESULT, True)
1418
if _ is not None:
1419
kb.nullConnection = _
1420
1421
if _:
1422
dbgMsg = "resuming NULL connection method '%s'" % _
1423
logger.debug(dbgMsg)
1424
1425
else:
1426
infoMsg = "testing NULL connection to the target URL"
1427
logger.info(infoMsg)
1428
1429
pushValue(kb.pageCompress)
1430
kb.pageCompress = False
1431
1432
try:
1433
page, headers, _ = Request.getPage(method=HTTPMETHOD.HEAD, raise404=False)
1434
1435
if not page and HTTP_HEADER.CONTENT_LENGTH in (headers or {}):
1436
kb.nullConnection = NULLCONNECTION.HEAD
1437
1438
infoMsg = "NULL connection is supported with HEAD method ('Content-Length')"
1439
logger.info(infoMsg)
1440
else:
1441
page, headers, _ = Request.getPage(auxHeaders={HTTP_HEADER.RANGE: "bytes=-1"})
1442
1443
if page and len(page) == 1 and HTTP_HEADER.CONTENT_RANGE in (headers or {}):
1444
kb.nullConnection = NULLCONNECTION.RANGE
1445
1446
infoMsg = "NULL connection is supported with GET method ('Range')"
1447
logger.info(infoMsg)
1448
else:
1449
_, headers, _ = Request.getPage(skipRead=True)
1450
1451
if HTTP_HEADER.CONTENT_LENGTH in (headers or {}):
1452
kb.nullConnection = NULLCONNECTION.SKIP_READ
1453
1454
infoMsg = "NULL connection is supported with 'skip-read' method"
1455
logger.info(infoMsg)
1456
1457
except SqlmapConnectionException:
1458
pass
1459
1460
finally:
1461
kb.pageCompress = popValue()
1462
kb.nullConnection = False if kb.nullConnection is None else kb.nullConnection
1463
hashDBWrite(HASHDB_KEYS.CHECK_NULL_CONNECTION_RESULT, kb.nullConnection, True)
1464
1465
return kb.nullConnection in getPublicTypeMembers(NULLCONNECTION, True)
1466
1467
def checkConnection(suppressOutput=False):
1468
threadData = getCurrentThreadData()
1469
1470
if not re.search(r"\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\Z", conf.hostname):
1471
if not any((conf.proxy, conf.tor, conf.dummy, conf.offline)):
1472
try:
1473
debugMsg = "resolving hostname '%s'" % conf.hostname
1474
logger.debug(debugMsg)
1475
socket.getaddrinfo(conf.hostname, None)
1476
except socket.gaierror:
1477
errMsg = "host '%s' does not exist" % conf.hostname
1478
raise SqlmapConnectionException(errMsg)
1479
except socket.error as ex:
1480
errMsg = "problem occurred while "
1481
errMsg += "resolving a host name '%s' ('%s')" % (conf.hostname, getSafeExString(ex))
1482
raise SqlmapConnectionException(errMsg)
1483
except UnicodeError as ex:
1484
errMsg = "problem occurred while "
1485
errMsg += "handling a host name '%s' ('%s')" % (conf.hostname, getSafeExString(ex))
1486
raise SqlmapDataException(errMsg)
1487
1488
if not suppressOutput and not conf.dummy and not conf.offline:
1489
infoMsg = "testing connection to the target URL"
1490
logger.info(infoMsg)
1491
1492
try:
1493
kb.originalPageTime = time.time()
1494
page, headers, _ = Request.queryPage(content=True, noteResponseTime=False)
1495
1496
rawResponse = "%s%s" % (listToStrValue(headers.headers if headers else ""), page)
1497
1498
if conf.string:
1499
infoMsg = "testing if the provided string is within the "
1500
infoMsg += "target URL page content"
1501
logger.info(infoMsg)
1502
1503
if conf.string not in rawResponse:
1504
warnMsg = "you provided '%s' as the string to " % conf.string
1505
warnMsg += "match, but such a string is not within the target "
1506
warnMsg += "URL raw response, sqlmap will carry on anyway"
1507
logger.warning(warnMsg)
1508
1509
if conf.regexp:
1510
infoMsg = "testing if the provided regular expression matches within "
1511
infoMsg += "the target URL page content"
1512
logger.info(infoMsg)
1513
1514
if not re.search(conf.regexp, rawResponse, re.I | re.M):
1515
warnMsg = "you provided '%s' as the regular expression " % conf.regexp
1516
warnMsg += "which does not have any match within the target URL raw response. sqlmap "
1517
warnMsg += "will carry on anyway"
1518
logger.warning(warnMsg)
1519
1520
kb.errorIsNone = False
1521
1522
if any(_ in (kb.serverHeader or "") for _ in PRECONNECT_INCOMPATIBLE_SERVERS):
1523
singleTimeWarnMessage("turning off pre-connect mechanism because of incompatible server ('%s')" % kb.serverHeader)
1524
conf.disablePrecon = True
1525
1526
if not kb.originalPage and wasLastResponseHTTPError():
1527
if getLastRequestHTTPError() not in (conf.ignoreCode or []):
1528
errMsg = "unable to retrieve page content"
1529
raise SqlmapConnectionException(errMsg)
1530
elif wasLastResponseDBMSError():
1531
warnMsg = "there is a DBMS error found in the HTTP response body "
1532
warnMsg += "which could interfere with the results of the tests"
1533
logger.warning(warnMsg)
1534
elif wasLastResponseHTTPError():
1535
if getLastRequestHTTPError() not in (conf.ignoreCode or []):
1536
warnMsg = "the web server responded with an HTTP error code (%d) " % getLastRequestHTTPError()
1537
warnMsg += "which could interfere with the results of the tests"
1538
logger.warning(warnMsg)
1539
else:
1540
kb.errorIsNone = True
1541
1542
if kb.choices.redirect == REDIRECTION.YES and threadData.lastRedirectURL and threadData.lastRedirectURL[0] == threadData.lastRequestUID:
1543
if (threadData.lastRedirectURL[1] or "").startswith("https://") and conf.hostname in getUnicode(threadData.lastRedirectURL[1]):
1544
conf.url = re.sub(r"https?://", "https://", conf.url)
1545
match = re.search(r":(\d+)", threadData.lastRedirectURL[1])
1546
port = match.group(1) if match else 443
1547
conf.url = re.sub(r":\d+(/|\Z)", r":%s\g<1>" % port, conf.url)
1548
1549
except SqlmapConnectionException as ex:
1550
if conf.ipv6:
1551
warnMsg = "check connection to a provided "
1552
warnMsg += "IPv6 address with a tool like ping6 "
1553
warnMsg += "(e.g. 'ping6 -I eth0 %s') " % conf.hostname
1554
warnMsg += "prior to running sqlmap to avoid "
1555
warnMsg += "any addressing issues"
1556
singleTimeWarnMessage(warnMsg)
1557
1558
if any(code in kb.httpErrorCodes for code in (_http_client.NOT_FOUND, )):
1559
errMsg = getSafeExString(ex)
1560
logger.critical(errMsg)
1561
1562
if conf.multipleTargets:
1563
return False
1564
1565
msg = "it is not recommended to continue in this kind of cases. Do you want to quit and make sure that everything is set up properly? [Y/n] "
1566
if readInput(msg, default='Y', boolean=True):
1567
raise SqlmapSilentQuitException
1568
else:
1569
kb.ignoreNotFound = True
1570
else:
1571
raise
1572
finally:
1573
kb.originalPage = kb.pageTemplate = threadData.lastPage
1574
kb.originalCode = threadData.lastCode
1575
1576
if conf.cj and not conf.cookie and not any(_[0] == HTTP_HEADER.COOKIE for _ in conf.httpHeaders) and not conf.dropSetCookie:
1577
candidate = DEFAULT_COOKIE_DELIMITER.join("%s=%s" % (_.name, _.value) for _ in conf.cj)
1578
1579
message = "you have not declared cookie(s), while "
1580
message += "server wants to set its own ('%s'). " % re.sub(r"(=[^=;]{10}[^=;])[^=;]+([^=;]{10})", r"\g<1>...\g<2>", candidate)
1581
message += "Do you want to use those [Y/n] "
1582
if readInput(message, default='Y', boolean=True):
1583
kb.mergeCookies = True
1584
conf.httpHeaders.append((HTTP_HEADER.COOKIE, candidate))
1585
1586
return True
1587
1588
def checkInternet():
1589
return Request.getPage(url=CHECK_INTERNET_ADDRESS, checking=True)[2] == CHECK_INTERNET_CODE
1590
1591
def setVerbosity(): # Cross-referenced function
1592
raise NotImplementedError
1593
1594