Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sqlmapproject
GitHub Repository: sqlmapproject/sqlmap
Path: blob/master/lib/controller/controller.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
from __future__ import division
9
10
import os
11
import re
12
import subprocess
13
import time
14
15
from lib.controller.action import action
16
from lib.controller.checks import checkConnection
17
from lib.controller.checks import checkDynParam
18
from lib.controller.checks import checkInternet
19
from lib.controller.checks import checkNullConnection
20
from lib.controller.checks import checkSqlInjection
21
from lib.controller.checks import checkStability
22
from lib.controller.checks import checkWaf
23
from lib.controller.checks import heuristicCheckSqlInjection
24
from lib.core.agent import agent
25
from lib.core.common import dataToStdout
26
from lib.core.common import extractRegexResult
27
from lib.core.common import getFilteredPageContent
28
from lib.core.common import getPublicTypeMembers
29
from lib.core.common import getSafeExString
30
from lib.core.common import hashDBRetrieve
31
from lib.core.common import hashDBWrite
32
from lib.core.common import intersect
33
from lib.core.common import isDigit
34
from lib.core.common import isListLike
35
from lib.core.common import parseTargetUrl
36
from lib.core.common import popValue
37
from lib.core.common import pushValue
38
from lib.core.common import randomInt
39
from lib.core.common import randomStr
40
from lib.core.common import readInput
41
from lib.core.common import removePostHintPrefix
42
from lib.core.common import safeCSValue
43
from lib.core.common import showHttpErrorCodes
44
from lib.core.common import urldecode
45
from lib.core.common import urlencode
46
from lib.core.compat import xrange
47
from lib.core.data import conf
48
from lib.core.data import kb
49
from lib.core.data import logger
50
from lib.core.decorators import stackedmethod
51
from lib.core.enums import CONTENT_TYPE
52
from lib.core.enums import HASHDB_KEYS
53
from lib.core.enums import HEURISTIC_TEST
54
from lib.core.enums import HTTP_HEADER
55
from lib.core.enums import HTTPMETHOD
56
from lib.core.enums import NOTE
57
from lib.core.enums import PAYLOAD
58
from lib.core.enums import PLACE
59
from lib.core.exception import SqlmapBaseException
60
from lib.core.exception import SqlmapConnectionException
61
from lib.core.exception import SqlmapNoneDataException
62
from lib.core.exception import SqlmapNotVulnerableException
63
from lib.core.exception import SqlmapSilentQuitException
64
from lib.core.exception import SqlmapSkipTargetException
65
from lib.core.exception import SqlmapSystemException
66
from lib.core.exception import SqlmapUserQuitException
67
from lib.core.exception import SqlmapValueException
68
from lib.core.settings import ASP_NET_CONTROL_REGEX
69
from lib.core.settings import CSRF_TOKEN_PARAMETER_INFIXES
70
from lib.core.settings import DEFAULT_GET_POST_DELIMITER
71
from lib.core.settings import EMPTY_FORM_FIELDS_REGEX
72
from lib.core.settings import GOOGLE_ANALYTICS_COOKIE_REGEX
73
from lib.core.settings import HOST_ALIASES
74
from lib.core.settings import IGNORE_PARAMETERS
75
from lib.core.settings import LOW_TEXT_PERCENT
76
from lib.core.settings import REFERER_ALIASES
77
from lib.core.settings import USER_AGENT_ALIASES
78
from lib.core.target import initTargetEnv
79
from lib.core.target import setupTargetEnv
80
from lib.utils.hash import crackHashFile
81
82
def _selectInjection():
83
"""
84
Selection function for injection place, parameters and type.
85
"""
86
87
points = {}
88
89
for injection in kb.injections:
90
place = injection.place
91
parameter = injection.parameter
92
ptype = injection.ptype
93
94
point = (place, parameter, ptype)
95
96
if point not in points:
97
points[point] = injection
98
else:
99
for key in points[point]:
100
if key != 'data':
101
points[point][key] = points[point][key] or injection[key]
102
points[point]['data'].update(injection['data'])
103
104
if len(points) == 1:
105
kb.injection = kb.injections[0]
106
107
elif len(points) > 1:
108
message = "there were multiple injection points, please select "
109
message += "the one to use for following injections:\n"
110
111
points = []
112
113
for i in xrange(0, len(kb.injections)):
114
place = kb.injections[i].place
115
parameter = kb.injections[i].parameter
116
ptype = kb.injections[i].ptype
117
point = (place, parameter, ptype)
118
119
if point not in points:
120
points.append(point)
121
ptype = PAYLOAD.PARAMETER[ptype] if isinstance(ptype, int) else ptype
122
123
message += "[%d] place: %s, parameter: " % (i, place)
124
message += "%s, type: %s" % (parameter, ptype)
125
126
if i == 0:
127
message += " (default)"
128
129
message += "\n"
130
131
message += "[q] Quit"
132
choice = readInput(message, default='0').upper()
133
134
if isDigit(choice) and int(choice) < len(kb.injections) and int(choice) >= 0:
135
index = int(choice)
136
elif choice == 'Q':
137
raise SqlmapUserQuitException
138
else:
139
errMsg = "invalid choice"
140
raise SqlmapValueException(errMsg)
141
142
kb.injection = kb.injections[index]
143
144
def _formatInjection(inj):
145
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else inj.place
146
data = "Parameter: %s (%s)\n" % (inj.parameter, paramType)
147
148
for stype, sdata in inj.data.items():
149
title = sdata.title
150
vector = sdata.vector
151
comment = sdata.comment
152
payload = agent.adjustLateValues(sdata.payload)
153
if inj.place == PLACE.CUSTOM_HEADER:
154
payload = payload.split(',', 1)[1]
155
if stype == PAYLOAD.TECHNIQUE.UNION:
156
count = re.sub(r"(?i)(\(.+\))|(\blimit[^a-z]+)", "", sdata.payload).count(',') + 1
157
title = re.sub(r"\d+ to \d+", str(count), title)
158
vector = agent.forgeUnionQuery("[QUERY]", vector[0], vector[1], vector[2], None, None, vector[5], vector[6])
159
if count == 1:
160
title = title.replace("columns", "column")
161
elif comment:
162
vector = "%s%s" % (vector, comment)
163
data += " Type: %s\n" % PAYLOAD.SQLINJECTION[stype]
164
data += " Title: %s\n" % title
165
data += " Payload: %s\n" % urldecode(payload, unsafe="&", spaceplus=(inj.place != PLACE.GET and kb.postSpaceToPlus))
166
data += " Vector: %s\n\n" % vector if conf.verbose > 1 else "\n"
167
168
return data
169
170
def _showInjections():
171
if conf.wizard and kb.wizardMode:
172
kb.wizardMode = False
173
174
if kb.testQueryCount > 0:
175
header = "sqlmap identified the following injection point(s) with "
176
header += "a total of %d HTTP(s) requests" % kb.testQueryCount
177
else:
178
header = "sqlmap resumed the following injection point(s) from stored session"
179
180
if conf.api:
181
conf.dumper.string("", {"url": conf.url, "query": conf.parameters.get(PLACE.GET), "data": conf.parameters.get(PLACE.POST)}, content_type=CONTENT_TYPE.TARGET)
182
conf.dumper.string("", kb.injections, content_type=CONTENT_TYPE.TECHNIQUES)
183
else:
184
data = "".join(set(_formatInjection(_) for _ in kb.injections)).rstrip("\n")
185
conf.dumper.string(header, data)
186
187
if conf.tamper:
188
warnMsg = "changes made by tampering scripts are not "
189
warnMsg += "included in shown payload content(s)"
190
logger.warning(warnMsg)
191
192
if conf.hpp:
193
warnMsg = "changes made by HTTP parameter pollution are not "
194
warnMsg += "included in shown payload content(s)"
195
logger.warning(warnMsg)
196
197
def _randomFillBlankFields(value):
198
retVal = value
199
200
if extractRegexResult(EMPTY_FORM_FIELDS_REGEX, value):
201
message = "do you want to fill blank fields with random values? [Y/n] "
202
203
if readInput(message, default='Y', boolean=True):
204
for match in re.finditer(EMPTY_FORM_FIELDS_REGEX, retVal):
205
item = match.group("result")
206
if not any(_ in item for _ in IGNORE_PARAMETERS) and not re.search(ASP_NET_CONTROL_REGEX, item):
207
newValue = randomStr() if not re.search(r"^id|id$", item, re.I) else randomInt()
208
if item[-1] == DEFAULT_GET_POST_DELIMITER:
209
retVal = retVal.replace(item, "%s%s%s" % (item[:-1], newValue, DEFAULT_GET_POST_DELIMITER))
210
else:
211
retVal = retVal.replace(item, "%s%s" % (item, newValue))
212
213
return retVal
214
215
def _saveToHashDB():
216
injections = hashDBRetrieve(HASHDB_KEYS.KB_INJECTIONS, True)
217
if not isListLike(injections):
218
injections = []
219
injections.extend(_ for _ in kb.injections if _ and _.place is not None and _.parameter is not None)
220
221
_ = dict()
222
for injection in injections:
223
key = (injection.place, injection.parameter, injection.ptype)
224
if key not in _:
225
_[key] = injection
226
else:
227
_[key].data.update(injection.data)
228
hashDBWrite(HASHDB_KEYS.KB_INJECTIONS, list(_.values()), True)
229
230
_ = hashDBRetrieve(HASHDB_KEYS.KB_ABS_FILE_PATHS, True)
231
hashDBWrite(HASHDB_KEYS.KB_ABS_FILE_PATHS, kb.absFilePaths | (_ if isinstance(_, set) else set()), True)
232
233
if not hashDBRetrieve(HASHDB_KEYS.KB_CHARS):
234
hashDBWrite(HASHDB_KEYS.KB_CHARS, kb.chars, True)
235
236
if not hashDBRetrieve(HASHDB_KEYS.KB_DYNAMIC_MARKINGS):
237
hashDBWrite(HASHDB_KEYS.KB_DYNAMIC_MARKINGS, kb.dynamicMarkings, True)
238
239
def _saveToResultsFile():
240
if not conf.resultsFP:
241
return
242
243
results = {}
244
techniques = dict((_[1], _[0]) for _ in getPublicTypeMembers(PAYLOAD.TECHNIQUE))
245
246
for injection in kb.injections + kb.falsePositives:
247
if injection.place is None or injection.parameter is None:
248
continue
249
250
key = (injection.place, injection.parameter, ';'.join(injection.notes))
251
if key not in results:
252
results[key] = []
253
254
results[key].extend(list(injection.data.keys()))
255
256
try:
257
for key, value in results.items():
258
place, parameter, notes = key
259
line = "%s,%s,%s,%s,%s%s" % (safeCSValue(kb.originalUrls.get(conf.url) or conf.url), place, parameter, "".join(techniques[_][0].upper() for _ in sorted(value)), notes, os.linesep)
260
conf.resultsFP.write(line)
261
262
conf.resultsFP.flush()
263
except IOError as ex:
264
errMsg = "unable to write to the results file '%s' ('%s'). " % (conf.resultsFile, getSafeExString(ex))
265
raise SqlmapSystemException(errMsg)
266
267
@stackedmethod
268
def start():
269
"""
270
This function calls a function that performs checks on both URL
271
stability and all GET, POST, Cookie and User-Agent parameters to
272
check if they are dynamic and SQL injection affected
273
"""
274
275
if conf.hashFile:
276
crackHashFile(conf.hashFile)
277
278
if conf.direct:
279
initTargetEnv()
280
setupTargetEnv()
281
action()
282
return True
283
284
if conf.url and not any((conf.forms, conf.crawlDepth)):
285
kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None))
286
287
if conf.configFile and not kb.targets:
288
errMsg = "you did not edit the configuration file properly, set "
289
errMsg += "the target URL, list of targets or google dork"
290
logger.error(errMsg)
291
return False
292
293
if kb.targets and isListLike(kb.targets) and len(kb.targets) > 1:
294
infoMsg = "found a total of %d targets" % len(kb.targets)
295
logger.info(infoMsg)
296
297
targetCount = 0
298
initialHeaders = list(conf.httpHeaders)
299
300
for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets:
301
targetCount += 1
302
303
try:
304
if conf.checkInternet:
305
infoMsg = "checking for Internet connection"
306
logger.info(infoMsg)
307
308
if not checkInternet():
309
warnMsg = "[%s] [WARNING] no connection detected" % time.strftime("%X")
310
dataToStdout(warnMsg)
311
312
valid = False
313
for _ in xrange(conf.retries):
314
if checkInternet():
315
valid = True
316
break
317
else:
318
dataToStdout('.')
319
time.sleep(5)
320
321
if not valid:
322
errMsg = "please check your Internet connection and rerun"
323
raise SqlmapConnectionException(errMsg)
324
else:
325
dataToStdout("\n")
326
327
conf.url = targetUrl
328
conf.method = targetMethod.upper().strip() if targetMethod else targetMethod
329
conf.data = targetData
330
conf.cookie = targetCookie
331
conf.httpHeaders = list(initialHeaders)
332
conf.httpHeaders.extend(targetHeaders or [])
333
334
if conf.randomAgent or conf.mobile:
335
for header, value in initialHeaders:
336
if header.upper() == HTTP_HEADER.USER_AGENT.upper():
337
conf.httpHeaders.append((header, value))
338
break
339
340
if conf.data:
341
# Note: explicitly URL encode __ ASP(.NET) parameters (e.g. to avoid problems with Base64 encoded '+' character) - standard procedure in web browsers
342
conf.data = re.sub(r"\b(__\w+)=([^&]+)", lambda match: "%s=%s" % (match.group(1), urlencode(match.group(2), safe='%')), conf.data)
343
344
conf.httpHeaders = [conf.httpHeaders[i] for i in xrange(len(conf.httpHeaders)) if conf.httpHeaders[i][0].upper() not in (__[0].upper() for __ in conf.httpHeaders[i + 1:])]
345
346
initTargetEnv()
347
parseTargetUrl()
348
349
testSqlInj = False
350
351
if PLACE.GET in conf.parameters and not any((conf.data, conf.testParameter)):
352
for parameter in re.findall(r"([^=]+)=([^%s]+%s?|\Z)" % (re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER, re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER), conf.parameters[PLACE.GET]):
353
paramKey = (conf.hostname, conf.path, PLACE.GET, parameter[0])
354
355
if paramKey not in kb.testedParams:
356
testSqlInj = True
357
break
358
else:
359
paramKey = (conf.hostname, conf.path, None, None)
360
if paramKey not in kb.testedParams:
361
testSqlInj = True
362
363
if testSqlInj and conf.hostname in kb.vulnHosts:
364
if kb.skipVulnHost is None:
365
message = "SQL injection vulnerability has already been detected "
366
message += "against '%s'. Do you want to skip " % conf.hostname
367
message += "further tests involving it? [Y/n]"
368
369
kb.skipVulnHost = readInput(message, default='Y', boolean=True)
370
371
testSqlInj = not kb.skipVulnHost
372
373
if not testSqlInj:
374
infoMsg = "skipping '%s'" % targetUrl
375
logger.info(infoMsg)
376
continue
377
378
if conf.multipleTargets:
379
if conf.forms and conf.method:
380
message = "[%d/%s] Form:\n%s %s" % (targetCount, len(kb.targets) if isListLike(kb.targets) else '?', conf.method, targetUrl)
381
else:
382
message = "[%d/%s] URL:\n%s %s" % (targetCount, len(kb.targets) if isListLike(kb.targets) else '?', HTTPMETHOD.GET, targetUrl)
383
384
if conf.cookie:
385
message += "\nCookie: %s" % conf.cookie
386
387
if conf.data is not None:
388
message += "\n%s data: %s" % ((conf.method if conf.method != HTTPMETHOD.GET else None) or HTTPMETHOD.POST, urlencode(conf.data or "") if re.search(r"\A\s*[<{]", conf.data or "") is None else conf.data)
389
390
if conf.forms and conf.method:
391
if conf.method == HTTPMETHOD.GET and targetUrl.find("?") == -1:
392
continue
393
394
message += "\ndo you want to test this form? [Y/n/q] "
395
choice = readInput(message, default='Y').upper()
396
397
if choice == 'N':
398
continue
399
elif choice == 'Q':
400
break
401
else:
402
if conf.method != HTTPMETHOD.GET:
403
message = "Edit %s data [default: %s]%s: " % (conf.method, urlencode(conf.data or "") if re.search(r"\A\s*[<{]", conf.data or "None") is None else conf.data, " (Warning: blank fields detected)" if conf.data and extractRegexResult(EMPTY_FORM_FIELDS_REGEX, conf.data) else "")
404
conf.data = readInput(message, default=conf.data)
405
conf.data = _randomFillBlankFields(conf.data)
406
conf.data = urldecode(conf.data) if conf.data and urlencode(DEFAULT_GET_POST_DELIMITER, None) not in conf.data else conf.data
407
408
else:
409
if '?' in targetUrl:
410
firstPart, secondPart = targetUrl.split('?', 1)
411
message = "Edit GET data [default: %s]: " % secondPart
412
test = readInput(message, default=secondPart)
413
test = _randomFillBlankFields(test)
414
conf.url = "%s?%s" % (firstPart, test)
415
416
parseTargetUrl()
417
418
else:
419
if not conf.scope:
420
message += "\ndo you want to test this URL? [Y/n/q]"
421
choice = readInput(message, default='Y').upper()
422
423
if choice == 'N':
424
dataToStdout(os.linesep)
425
continue
426
elif choice == 'Q':
427
break
428
else:
429
pass
430
431
infoMsg = "testing URL '%s'" % targetUrl
432
logger.info(infoMsg)
433
434
setupTargetEnv()
435
436
if not checkConnection(suppressOutput=conf.forms):
437
continue
438
439
if conf.rParam and kb.originalPage:
440
kb.randomPool = dict([_ for _ in kb.randomPool.items() if isinstance(_[1], list)])
441
442
for match in re.finditer(r"(?si)<select[^>]+\bname\s*=\s*[\"']([^\"']+)(.+?)</select>", kb.originalPage):
443
name, _ = match.groups()
444
options = tuple(re.findall(r"<option[^>]+\bvalue\s*=\s*[\"']([^\"']+)", _))
445
if options:
446
kb.randomPool[name] = options
447
448
checkWaf()
449
450
if conf.nullConnection:
451
checkNullConnection()
452
453
if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) and (kb.injection.place is None or kb.injection.parameter is None):
454
if not any((conf.string, conf.notString, conf.regexp)) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.technique:
455
# NOTE: this is not needed anymore, leaving only to display
456
# a warning message to the user in case the page is not stable
457
checkStability()
458
459
# Do a little prioritization reorder of a testable parameter list
460
parameters = list(conf.parameters.keys())
461
462
# Order of testing list (first to last)
463
orderList = (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI, PLACE.POST, PLACE.GET)
464
465
for place in orderList[::-1]:
466
if place in parameters:
467
parameters.remove(place)
468
parameters.insert(0, place)
469
470
proceed = True
471
for place in parameters:
472
# Test User-Agent and Referer headers only if
473
# --level >= 3
474
skip = (place == PLACE.USER_AGENT and (kb.testOnlyCustom or conf.level < 3))
475
skip |= (place == PLACE.REFERER and (kb.testOnlyCustom or conf.level < 3))
476
477
# --param-filter
478
skip |= (len(conf.paramFilter) > 0 and place.upper() not in conf.paramFilter)
479
480
# Test Host header only if
481
# --level >= 5
482
skip |= (place == PLACE.HOST and (kb.testOnlyCustom or conf.level < 5))
483
484
# Test Cookie header only if --level >= 2
485
skip |= (place == PLACE.COOKIE and (kb.testOnlyCustom or conf.level < 2))
486
487
skip |= (place == PLACE.USER_AGENT and intersect(USER_AGENT_ALIASES, conf.skip, True) not in ([], None))
488
skip |= (place == PLACE.REFERER and intersect(REFERER_ALIASES, conf.skip, True) not in ([], None))
489
skip |= (place == PLACE.COOKIE and intersect(PLACE.COOKIE, conf.skip, True) not in ([], None))
490
skip |= (place == PLACE.HOST and intersect(PLACE.HOST, conf.skip, True) not in ([], None))
491
492
skip &= not (place == PLACE.USER_AGENT and intersect(USER_AGENT_ALIASES, conf.testParameter, True))
493
skip &= not (place == PLACE.REFERER and intersect(REFERER_ALIASES, conf.testParameter, True))
494
skip &= not (place == PLACE.HOST and intersect(HOST_ALIASES, conf.testParameter, True))
495
skip &= not (place == PLACE.COOKIE and intersect((PLACE.COOKIE,), conf.testParameter, True))
496
497
if skip:
498
continue
499
500
if place not in conf.paramDict or place not in conf.parameters:
501
continue
502
503
paramDict = conf.paramDict[place]
504
505
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
506
507
for parameter, value in paramDict.items():
508
if not proceed:
509
break
510
511
kb.vainRun = False
512
testSqlInj = True
513
paramKey = (conf.hostname, conf.path, place, parameter)
514
515
if kb.processUserMarks:
516
if testSqlInj and place not in (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI):
517
if kb.processNonCustom is None:
518
message = "other non-custom parameters found. "
519
message += "Do you want to process them too? [Y/n/q] "
520
choice = readInput(message, default='Y').upper()
521
522
if choice == 'Q':
523
raise SqlmapUserQuitException
524
else:
525
kb.processNonCustom = choice == 'Y'
526
527
if not kb.processNonCustom:
528
infoMsg = "skipping %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter)
529
logger.info(infoMsg)
530
continue
531
532
if paramKey in kb.testedParams:
533
testSqlInj = False
534
535
infoMsg = "skipping previously processed %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter)
536
logger.info(infoMsg)
537
538
elif any(_ in conf.testParameter for _ in (parameter, removePostHintPrefix(parameter))):
539
pass
540
541
elif parameter in conf.rParam:
542
testSqlInj = False
543
544
infoMsg = "skipping randomizing %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter)
545
logger.info(infoMsg)
546
547
elif parameter in conf.skip or kb.postHint and parameter.split(' ')[-1] in conf.skip:
548
testSqlInj = False
549
550
infoMsg = "skipping %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter)
551
logger.info(infoMsg)
552
553
elif conf.paramExclude and (re.search(conf.paramExclude, parameter, re.I) or kb.postHint and re.search(conf.paramExclude, parameter.split(' ')[-1], re.I) or re.search(conf.paramExclude, place, re.I)):
554
testSqlInj = False
555
556
infoMsg = "skipping %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter)
557
logger.info(infoMsg)
558
559
elif conf.csrfToken and re.search(conf.csrfToken, parameter, re.I):
560
testSqlInj = False
561
562
infoMsg = "skipping anti-CSRF token parameter '%s'" % parameter
563
logger.info(infoMsg)
564
565
# Ignore session-like parameters for --level < 4
566
elif conf.level < 4 and (parameter.upper() in IGNORE_PARAMETERS or any(_ in parameter.lower() for _ in CSRF_TOKEN_PARAMETER_INFIXES) or re.search(GOOGLE_ANALYTICS_COOKIE_REGEX, parameter)):
567
testSqlInj = False
568
569
infoMsg = "ignoring %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter)
570
logger.info(infoMsg)
571
572
elif PAYLOAD.TECHNIQUE.BOOLEAN in conf.technique or conf.skipStatic:
573
check = checkDynParam(place, parameter, value)
574
575
if not check:
576
warnMsg = "%sparameter '%s' does not appear to be dynamic" % ("%s " % paramType if paramType != parameter else "", parameter)
577
logger.warning(warnMsg)
578
579
if conf.skipStatic:
580
infoMsg = "skipping static %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter)
581
logger.info(infoMsg)
582
583
testSqlInj = False
584
else:
585
infoMsg = "%sparameter '%s' appears to be dynamic" % ("%s " % paramType if paramType != parameter else "", parameter)
586
logger.info(infoMsg)
587
588
kb.testedParams.add(paramKey)
589
590
if testSqlInj:
591
try:
592
if place == PLACE.COOKIE:
593
pushValue(kb.mergeCookies)
594
kb.mergeCookies = False
595
596
check = heuristicCheckSqlInjection(place, parameter)
597
598
if check != HEURISTIC_TEST.POSITIVE:
599
if conf.smart or (kb.ignoreCasted and check == HEURISTIC_TEST.CASTED):
600
infoMsg = "skipping %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter)
601
logger.info(infoMsg)
602
continue
603
604
infoMsg = "testing for SQL injection on %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter)
605
logger.info(infoMsg)
606
607
injection = checkSqlInjection(place, parameter, value)
608
proceed = not kb.endDetection
609
injectable = False
610
611
if getattr(injection, "place", None) is not None:
612
if NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE in injection.notes:
613
kb.falsePositives.append(injection)
614
else:
615
injectable = True
616
617
kb.injections.append(injection)
618
619
if not kb.alerted:
620
if conf.alert:
621
infoMsg = "executing alerting shell command(s) ('%s')" % conf.alert
622
logger.info(infoMsg)
623
try:
624
process = subprocess.Popen(conf.alert, shell=True)
625
process.wait()
626
except Exception as ex:
627
errMsg = "error occurred while executing '%s' ('%s')" % (conf.alert, getSafeExString(ex))
628
logger.error(errMsg)
629
630
kb.alerted = True
631
632
# In case when user wants to end detection phase (Ctrl+C)
633
if not proceed:
634
break
635
636
msg = "%sparameter '%s' " % ("%s " % injection.place if injection.place != injection.parameter else "", injection.parameter)
637
msg += "is vulnerable. Do you want to keep testing the others (if any)? [y/N] "
638
639
if not readInput(msg, default='N', boolean=True):
640
proceed = False
641
paramKey = (conf.hostname, conf.path, None, None)
642
kb.testedParams.add(paramKey)
643
644
if not injectable:
645
warnMsg = "%sparameter '%s' does not seem to be injectable" % ("%s " % paramType if paramType != parameter else "", parameter)
646
logger.warning(warnMsg)
647
648
finally:
649
if place == PLACE.COOKIE:
650
kb.mergeCookies = popValue()
651
652
if len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None):
653
if kb.vainRun and not conf.multipleTargets:
654
errMsg = "no parameter(s) found for testing in the provided data "
655
errMsg += "(e.g. GET parameter 'id' in 'www.site.com/index.php?id=1')"
656
if kb.originalPage:
657
advice = []
658
if not conf.forms and re.search(r"<form", kb.originalPage) is not None:
659
advice.append("--forms")
660
if not conf.crawlDepth and re.search(r"href=[\"']/?\w", kb.originalPage) is not None:
661
advice.append("--crawl=2")
662
if advice:
663
errMsg += ". You are advised to rerun with '%s'" % ' '.join(advice)
664
raise SqlmapNoneDataException(errMsg)
665
else:
666
errMsg = "all tested parameters do not appear to be injectable."
667
668
if conf.level < 5 or conf.risk < 3:
669
errMsg += " Try to increase values for '--level'/'--risk' options "
670
errMsg += "if you wish to perform more tests."
671
672
if isinstance(conf.technique, list) and len(conf.technique) < 5:
673
errMsg += " Rerun without providing the option '--technique'."
674
675
if not conf.textOnly and kb.originalPage:
676
percent = (100.0 * len(getFilteredPageContent(kb.originalPage)) / len(kb.originalPage))
677
678
if kb.dynamicMarkings:
679
errMsg += " You can give it a go with the switch '--text-only' "
680
errMsg += "if the target page has a low percentage "
681
errMsg += "of textual content (~%.2f%% of " % percent
682
errMsg += "page content is text)."
683
elif percent < LOW_TEXT_PERCENT and not kb.errorIsNone:
684
errMsg += " Please retry with the switch '--text-only' "
685
errMsg += "(along with --technique=BU) as this case "
686
errMsg += "looks like a perfect candidate "
687
errMsg += "(low textual content along with inability "
688
errMsg += "of comparison engine to detect at least "
689
errMsg += "one dynamic parameter)."
690
691
if kb.heuristicTest == HEURISTIC_TEST.POSITIVE:
692
errMsg += " As heuristic test turned out positive you are "
693
errMsg += "strongly advised to continue on with the tests."
694
695
if conf.string:
696
errMsg += " Also, you can try to rerun by providing a "
697
errMsg += "valid value for option '--string' as perhaps the string you "
698
errMsg += "have chosen does not match "
699
errMsg += "exclusively True responses."
700
elif conf.regexp:
701
errMsg += " Also, you can try to rerun by providing a "
702
errMsg += "valid value for option '--regexp' as perhaps the regular "
703
errMsg += "expression that you have chosen "
704
errMsg += "does not match exclusively True responses."
705
706
if not conf.tamper:
707
errMsg += " If you suspect that there is some kind of protection mechanism "
708
errMsg += "involved (e.g. WAF) maybe you could try to use "
709
errMsg += "option '--tamper' (e.g. '--tamper=space2comment')"
710
711
if not conf.randomAgent:
712
errMsg += " and/or switch '--random-agent'"
713
714
raise SqlmapNotVulnerableException(errMsg.rstrip('.'))
715
else:
716
# Flush the flag
717
kb.testMode = False
718
719
_saveToResultsFile()
720
_saveToHashDB()
721
_showInjections()
722
_selectInjection()
723
724
if kb.injection.place is not None and kb.injection.parameter is not None:
725
if conf.multipleTargets:
726
message = "do you want to exploit this SQL injection? [Y/n] "
727
condition = readInput(message, default='Y', boolean=True)
728
else:
729
condition = True
730
731
if condition:
732
action()
733
734
except KeyboardInterrupt:
735
if kb.lastCtrlCTime and (time.time() - kb.lastCtrlCTime < 1):
736
kb.multipleCtrlC = True
737
raise SqlmapUserQuitException("user aborted (Ctrl+C was pressed multiple times)")
738
739
kb.lastCtrlCTime = time.time()
740
741
if conf.multipleTargets:
742
warnMsg = "user aborted in multiple target mode"
743
logger.warning(warnMsg)
744
745
message = "do you want to skip to the next target in list? [Y/n/q]"
746
choice = readInput(message, default='Y').upper()
747
748
if choice == 'N':
749
return False
750
elif choice == 'Q':
751
raise SqlmapUserQuitException
752
else:
753
raise
754
755
except SqlmapSkipTargetException:
756
pass
757
758
except SqlmapUserQuitException:
759
raise
760
761
except SqlmapSilentQuitException:
762
raise
763
764
except SqlmapBaseException as ex:
765
errMsg = getSafeExString(ex)
766
767
if conf.multipleTargets:
768
_saveToResultsFile()
769
770
errMsg += ", skipping to the next target"
771
logger.error(errMsg.lstrip(", "))
772
else:
773
logger.critical(errMsg)
774
return False
775
776
finally:
777
showHttpErrorCodes()
778
779
if kb.maxConnectionsFlag:
780
warnMsg = "it appears that the target "
781
warnMsg += "has a maximum connections "
782
warnMsg += "constraint"
783
logger.warning(warnMsg)
784
785
if kb.dataOutputFlag and not conf.multipleTargets:
786
logger.info("fetched data logged to text files under '%s'" % conf.outputPath)
787
788
if conf.multipleTargets:
789
if conf.resultsFile:
790
infoMsg = "you can find results of scanning in multiple targets "
791
infoMsg += "mode inside the CSV file '%s'" % conf.resultsFile
792
logger.info(infoMsg)
793
794
return True
795
796