Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sqlmapproject
GitHub Repository: sqlmapproject/sqlmap
Path: blob/master/lib/core/common.py
3553 views
1
#!/usr/bin/env python
2
3
"""
4
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
5
See the file 'LICENSE' for copying permission
6
"""
7
8
from __future__ import division
9
10
import binascii
11
import codecs
12
import contextlib
13
import copy
14
import functools
15
import getpass
16
import hashlib
17
import inspect
18
import io
19
import json
20
import keyword
21
import locale
22
import logging
23
import ntpath
24
import os
25
import platform
26
import posixpath
27
import random
28
import re
29
import socket
30
import string
31
import subprocess
32
import sys
33
import tempfile
34
import threading
35
import time
36
import types
37
import unicodedata
38
import zlib
39
40
from difflib import SequenceMatcher
41
from math import sqrt
42
from optparse import OptionValueError
43
from xml.sax import parse
44
from xml.sax import SAXParseException
45
46
from extra.beep.beep import beep
47
from extra.cloak.cloak import decloak
48
from lib.core.bigarray import BigArray
49
from lib.core.compat import cmp
50
from lib.core.compat import codecs_open
51
from lib.core.compat import LooseVersion
52
from lib.core.compat import round
53
from lib.core.compat import xrange
54
from lib.core.convert import base64pickle
55
from lib.core.convert import base64unpickle
56
from lib.core.convert import decodeBase64
57
from lib.core.convert import decodeHex
58
from lib.core.convert import getBytes
59
from lib.core.convert import getText
60
from lib.core.convert import getUnicode
61
from lib.core.convert import htmlUnescape
62
from lib.core.convert import stdoutEncode
63
from lib.core.data import cmdLineOptions
64
from lib.core.data import conf
65
from lib.core.data import kb
66
from lib.core.data import logger
67
from lib.core.data import paths
68
from lib.core.datatype import OrderedSet
69
from lib.core.decorators import cachedmethod
70
from lib.core.defaults import defaults
71
from lib.core.dicts import DBMS_DICT
72
from lib.core.dicts import DEFAULT_DOC_ROOTS
73
from lib.core.dicts import DEPRECATED_OPTIONS
74
from lib.core.dicts import OBSOLETE_OPTIONS
75
from lib.core.dicts import SQL_STATEMENTS
76
from lib.core.enums import ADJUST_TIME_DELAY
77
from lib.core.enums import CHARSET_TYPE
78
from lib.core.enums import CONTENT_STATUS
79
from lib.core.enums import DBMS
80
from lib.core.enums import EXPECTED
81
from lib.core.enums import HASHDB_KEYS
82
from lib.core.enums import HEURISTIC_TEST
83
from lib.core.enums import HTTP_HEADER
84
from lib.core.enums import HTTPMETHOD
85
from lib.core.enums import LOGGING_LEVELS
86
from lib.core.enums import MKSTEMP_PREFIX
87
from lib.core.enums import OPTION_TYPE
88
from lib.core.enums import OS
89
from lib.core.enums import PAYLOAD
90
from lib.core.enums import PLACE
91
from lib.core.enums import POST_HINT
92
from lib.core.enums import REFLECTIVE_COUNTER
93
from lib.core.enums import SORT_ORDER
94
from lib.core.exception import SqlmapBaseException
95
from lib.core.exception import SqlmapDataException
96
from lib.core.exception import SqlmapGenericException
97
from lib.core.exception import SqlmapInstallationException
98
from lib.core.exception import SqlmapMissingDependence
99
from lib.core.exception import SqlmapNoneDataException
100
from lib.core.exception import SqlmapSilentQuitException
101
from lib.core.exception import SqlmapSyntaxException
102
from lib.core.exception import SqlmapSystemException
103
from lib.core.exception import SqlmapUserQuitException
104
from lib.core.exception import SqlmapValueException
105
from lib.core.log import LOGGER_HANDLER
106
from lib.core.optiondict import optDict
107
from lib.core.settings import BANNER
108
from lib.core.settings import BOLD_PATTERNS_REGEX
109
from lib.core.settings import BOUNDARY_BACKSLASH_MARKER
110
from lib.core.settings import BOUNDED_INJECTION_MARKER
111
from lib.core.settings import BRUTE_DOC_ROOT_PREFIXES
112
from lib.core.settings import BRUTE_DOC_ROOT_SUFFIXES
113
from lib.core.settings import BRUTE_DOC_ROOT_TARGET_MARK
114
from lib.core.settings import BURP_REQUEST_REGEX
115
from lib.core.settings import BURP_XML_HISTORY_REGEX
116
from lib.core.settings import CRAWL_EXCLUDE_EXTENSIONS
117
from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR
118
from lib.core.settings import DBMS_DIRECTORY_DICT
119
from lib.core.settings import DEFAULT_COOKIE_DELIMITER
120
from lib.core.settings import DEFAULT_GET_POST_DELIMITER
121
from lib.core.settings import DEFAULT_MSSQL_SCHEMA
122
from lib.core.settings import DEV_EMAIL_ADDRESS
123
from lib.core.settings import DOLLAR_MARKER
124
from lib.core.settings import DUMMY_USER_INJECTION
125
from lib.core.settings import DYNAMICITY_BOUNDARY_LENGTH
126
from lib.core.settings import ERROR_PARSING_REGEXES
127
from lib.core.settings import EVALCODE_ENCODED_PREFIX
128
from lib.core.settings import FILE_PATH_REGEXES
129
from lib.core.settings import FORCE_COOKIE_EXPIRATION_TIME
130
from lib.core.settings import FORM_SEARCH_REGEX
131
from lib.core.settings import GENERIC_DOC_ROOT_DIRECTORY_NAMES
132
from lib.core.settings import GIT_PAGE
133
from lib.core.settings import GITHUB_REPORT_OAUTH_TOKEN
134
from lib.core.settings import GOOGLE_ANALYTICS_COOKIE_REGEX
135
from lib.core.settings import HASHDB_MILESTONE_VALUE
136
from lib.core.settings import HOST_ALIASES
137
from lib.core.settings import HTTP_CHUNKED_SPLIT_KEYWORDS
138
from lib.core.settings import IGNORE_PARAMETERS
139
from lib.core.settings import IGNORE_SAVE_OPTIONS
140
from lib.core.settings import INFERENCE_UNKNOWN_CHAR
141
from lib.core.settings import INJECT_HERE_REGEX
142
from lib.core.settings import IP_ADDRESS_REGEX
143
from lib.core.settings import ISSUES_PAGE
144
from lib.core.settings import IS_TTY
145
from lib.core.settings import IS_WIN
146
from lib.core.settings import LARGE_OUTPUT_THRESHOLD
147
from lib.core.settings import LOCALHOST
148
from lib.core.settings import MAX_INT
149
from lib.core.settings import MIN_ENCODED_LEN_CHECK
150
from lib.core.settings import MIN_ERROR_PARSING_NON_WRITING_RATIO
151
from lib.core.settings import MIN_TIME_RESPONSES
152
from lib.core.settings import MIN_VALID_DELAYED_RESPONSE
153
from lib.core.settings import NETSCAPE_FORMAT_HEADER_COOKIES
154
from lib.core.settings import NULL
155
from lib.core.settings import PARAMETER_AMP_MARKER
156
from lib.core.settings import PARAMETER_SEMICOLON_MARKER
157
from lib.core.settings import PARAMETER_PERCENTAGE_MARKER
158
from lib.core.settings import PARTIAL_HEX_VALUE_MARKER
159
from lib.core.settings import PARTIAL_VALUE_MARKER
160
from lib.core.settings import PAYLOAD_DELIMITER
161
from lib.core.settings import PLATFORM
162
from lib.core.settings import PRINTABLE_CHAR_REGEX
163
from lib.core.settings import PROBLEMATIC_CUSTOM_INJECTION_PATTERNS
164
from lib.core.settings import PUSH_VALUE_EXCEPTION_RETRY_COUNT
165
from lib.core.settings import PYVERSION
166
from lib.core.settings import RANDOMIZATION_TLDS
167
from lib.core.settings import REFERER_ALIASES
168
from lib.core.settings import REFLECTED_BORDER_REGEX
169
from lib.core.settings import REFLECTED_MAX_REGEX_PARTS
170
from lib.core.settings import REFLECTED_REPLACEMENT_REGEX
171
from lib.core.settings import REFLECTED_REPLACEMENT_TIMEOUT
172
from lib.core.settings import REFLECTED_VALUE_MARKER
173
from lib.core.settings import REFLECTIVE_MISS_THRESHOLD
174
from lib.core.settings import REPLACEMENT_MARKER
175
from lib.core.settings import SENSITIVE_DATA_REGEX
176
from lib.core.settings import SENSITIVE_OPTIONS
177
from lib.core.settings import STDIN_PIPE_DASH
178
from lib.core.settings import SUPPORTED_DBMS
179
from lib.core.settings import TEXT_TAG_REGEX
180
from lib.core.settings import TIME_STDEV_COEFF
181
from lib.core.settings import UNICODE_ENCODING
182
from lib.core.settings import UNKNOWN_DBMS_VERSION
183
from lib.core.settings import URI_QUESTION_MARKER
184
from lib.core.settings import URLENCODE_CHAR_LIMIT
185
from lib.core.settings import URLENCODE_FAILSAFE_CHARS
186
from lib.core.settings import USER_AGENT_ALIASES
187
from lib.core.settings import VERSION_COMPARISON_CORRECTION
188
from lib.core.settings import VERSION_STRING
189
from lib.core.settings import ZIP_HEADER
190
from lib.core.settings import WEBSCARAB_SPLITTER
191
from lib.core.threads import getCurrentThreadData
192
from lib.utils.safe2bin import safecharencode
193
from lib.utils.sqlalchemy import _sqlalchemy
194
from thirdparty import six
195
from thirdparty.clientform.clientform import ParseResponse
196
from thirdparty.clientform.clientform import ParseError
197
from thirdparty.colorama.initialise import init as coloramainit
198
from thirdparty.magic import magic
199
from thirdparty.odict import OrderedDict
200
from thirdparty.six import unichr as _unichr
201
from thirdparty.six.moves import collections_abc as _collections
202
from thirdparty.six.moves import configparser as _configparser
203
from thirdparty.six.moves import http_client as _http_client
204
from thirdparty.six.moves import input as _input
205
from thirdparty.six.moves import reload_module as _reload_module
206
from thirdparty.six.moves import urllib as _urllib
207
from thirdparty.six.moves import zip as _zip
208
from thirdparty.termcolor.termcolor import colored
209
210
class UnicodeRawConfigParser(_configparser.RawConfigParser):
211
"""
212
RawConfigParser with unicode writing support
213
"""
214
215
def write(self, fp):
216
"""
217
Write an .ini-format representation of the configuration state.
218
"""
219
220
if self._defaults:
221
fp.write("[%s]\n" % _configparser.DEFAULTSECT)
222
223
for (key, value) in self._defaults.items():
224
fp.write("%s = %s" % (key, getUnicode(value, UNICODE_ENCODING)))
225
226
fp.write("\n")
227
228
for section in self._sections:
229
fp.write("[%s]\n" % section)
230
231
for (key, value) in self._sections[section].items():
232
if key != "__name__":
233
if value is None:
234
fp.write("%s\n" % (key))
235
elif not isListLike(value):
236
fp.write("%s = %s\n" % (key, getUnicode(value, UNICODE_ENCODING)))
237
238
fp.write("\n")
239
240
class Format(object):
241
@staticmethod
242
def humanize(values, chain=" or "):
243
return chain.join(values)
244
245
# Get methods
246
@staticmethod
247
def getDbms(versions=None):
248
"""
249
Format the back-end DBMS fingerprint value and return its
250
values formatted as a human readable string.
251
252
@return: detected back-end DBMS based upon fingerprint techniques.
253
@rtype: C{str}
254
"""
255
256
if versions is None and Backend.getVersionList():
257
versions = Backend.getVersionList()
258
259
# NOTE: preventing ugly (e.g.) "back-end DBMS: MySQL Unknown"
260
if isListLike(versions) and UNKNOWN_DBMS_VERSION in versions:
261
versions = None
262
263
return Backend.getDbms() if versions is None else "%s %s" % (Backend.getDbms(), " and ".join(filterNone(versions)))
264
265
@staticmethod
266
def getErrorParsedDBMSes():
267
"""
268
Parses the knowledge base htmlFp list and return its values
269
formatted as a human readable string.
270
271
@return: list of possible back-end DBMS based upon error messages
272
parsing.
273
@rtype: C{str}
274
"""
275
276
htmlParsed = None
277
278
if len(kb.htmlFp) == 0 or kb.heuristicTest != HEURISTIC_TEST.POSITIVE:
279
pass
280
elif len(kb.htmlFp) == 1:
281
htmlParsed = kb.htmlFp[0]
282
elif len(kb.htmlFp) > 1:
283
htmlParsed = " or ".join(kb.htmlFp)
284
285
return htmlParsed
286
287
@staticmethod
288
def getOs(target, info):
289
"""
290
Formats the back-end operating system fingerprint value
291
and return its values formatted as a human readable string.
292
293
Example of info (kb.headersFp) dictionary:
294
295
{
296
'distrib': set(['Ubuntu']),
297
'type': set(['Linux']),
298
'technology': set(['PHP 5.2.6', 'Apache 2.2.9']),
299
'release': set(['8.10'])
300
}
301
302
Example of info (kb.bannerFp) dictionary:
303
304
{
305
'sp': set(['Service Pack 4']),
306
'dbmsVersion': '8.00.194',
307
'dbmsServicePack': '0',
308
'distrib': set(['2000']),
309
'dbmsRelease': '2000',
310
'type': set(['Windows'])
311
}
312
313
@return: detected back-end operating system based upon fingerprint
314
techniques.
315
@rtype: C{str}
316
"""
317
318
infoStr = ""
319
infoApi = {}
320
321
if info and "type" in info:
322
if conf.api:
323
infoApi["%s operating system" % target] = info
324
else:
325
infoStr += "%s operating system: %s" % (target, Format.humanize(info["type"]))
326
327
if "distrib" in info:
328
infoStr += " %s" % Format.humanize(info["distrib"])
329
330
if "release" in info:
331
infoStr += " %s" % Format.humanize(info["release"])
332
333
if "sp" in info:
334
infoStr += " %s" % Format.humanize(info["sp"])
335
336
if "codename" in info:
337
infoStr += " (%s)" % Format.humanize(info["codename"])
338
339
if "technology" in info:
340
if conf.api:
341
infoApi["web application technology"] = Format.humanize(info["technology"], ", ")
342
else:
343
infoStr += "\nweb application technology: %s" % Format.humanize(info["technology"], ", ")
344
345
if conf.api:
346
return infoApi
347
else:
348
return infoStr.lstrip()
349
350
class Backend(object):
351
@staticmethod
352
def setDbms(dbms):
353
dbms = aliasToDbmsEnum(dbms)
354
355
if dbms is None:
356
return None
357
358
# Little precaution, in theory this condition should always be false
359
elif kb.dbms is not None and kb.dbms != dbms:
360
warnMsg = "there appears to be a high probability that "
361
warnMsg += "this could be a false positive case"
362
logger.warning(warnMsg)
363
364
msg = "sqlmap previously fingerprinted back-end DBMS as "
365
msg += "%s. However now it has been fingerprinted " % kb.dbms
366
msg += "as %s. " % dbms
367
msg += "Please, specify which DBMS should be "
368
msg += "correct [%s (default)/%s] " % (kb.dbms, dbms)
369
370
while True:
371
choice = readInput(msg, default=kb.dbms)
372
373
if aliasToDbmsEnum(choice) == kb.dbms:
374
kb.dbmsVersion = []
375
kb.resolutionDbms = kb.dbms
376
break
377
elif aliasToDbmsEnum(choice) == dbms:
378
kb.dbms = aliasToDbmsEnum(choice)
379
break
380
else:
381
warnMsg = "invalid value"
382
logger.warning(warnMsg)
383
384
elif kb.dbms is None:
385
kb.dbms = aliasToDbmsEnum(dbms)
386
387
return kb.dbms
388
389
@staticmethod
390
def setVersion(version):
391
if isinstance(version, six.string_types):
392
kb.dbmsVersion = [version]
393
394
return kb.dbmsVersion
395
396
@staticmethod
397
def setVersionList(versionsList):
398
if isinstance(versionsList, list):
399
kb.dbmsVersion = versionsList
400
elif isinstance(versionsList, six.string_types):
401
Backend.setVersion(versionsList)
402
else:
403
logger.error("invalid format of versionsList")
404
405
@staticmethod
406
def forceDbms(dbms, sticky=False):
407
if not kb.stickyDBMS:
408
kb.forcedDbms = aliasToDbmsEnum(dbms)
409
kb.stickyDBMS = sticky
410
411
@staticmethod
412
def flushForcedDbms(force=False):
413
if not kb.stickyDBMS or force:
414
kb.forcedDbms = None
415
kb.stickyDBMS = False
416
417
@staticmethod
418
def setOs(os):
419
if os is None:
420
return None
421
422
# Little precaution, in theory this condition should always be false
423
elif kb.os is not None and isinstance(os, six.string_types) and kb.os.lower() != os.lower():
424
msg = "sqlmap previously fingerprinted back-end DBMS "
425
msg += "operating system %s. However now it has " % kb.os
426
msg += "been fingerprinted to be %s. " % os
427
msg += "Please, specify which OS is "
428
msg += "correct [%s (default)/%s] " % (kb.os, os)
429
430
while True:
431
choice = readInput(msg, default=kb.os)
432
433
if choice == kb.os:
434
break
435
elif choice == os:
436
kb.os = choice.capitalize()
437
break
438
else:
439
warnMsg = "invalid value"
440
logger.warning(warnMsg)
441
442
elif kb.os is None and isinstance(os, six.string_types):
443
kb.os = os.capitalize()
444
445
return kb.os
446
447
@staticmethod
448
def setOsVersion(version):
449
if version is None:
450
return None
451
452
elif kb.osVersion is None and isinstance(version, six.string_types):
453
kb.osVersion = version
454
455
@staticmethod
456
def setOsServicePack(sp):
457
if sp is None:
458
return None
459
460
elif kb.osSP is None and isinstance(sp, int):
461
kb.osSP = sp
462
463
@staticmethod
464
def setArch():
465
msg = "what is the back-end database management system architecture?"
466
msg += "\n[1] 32-bit"
467
msg += "\n[2] 64-bit (default)"
468
469
while True:
470
choice = readInput(msg, default='2')
471
472
if hasattr(choice, "isdigit") and choice.isdigit() and int(choice) in (1, 2):
473
kb.arch = 32 if int(choice) == 1 else 64
474
break
475
else:
476
warnMsg = "invalid value. Valid values are 1 and 2"
477
logger.warning(warnMsg)
478
479
return kb.arch
480
481
# Get methods
482
@staticmethod
483
def getForcedDbms():
484
return aliasToDbmsEnum(conf.get("forceDbms")) or aliasToDbmsEnum(kb.get("forcedDbms"))
485
486
@staticmethod
487
def getDbms():
488
return aliasToDbmsEnum(kb.get("dbms"))
489
490
@staticmethod
491
def getErrorParsedDBMSes():
492
"""
493
Returns array with parsed DBMS names till now
494
495
This functions is called to:
496
497
1. Ask user whether or not skip specific DBMS tests in detection phase,
498
lib/controller/checks.py - detection phase.
499
2. Sort the fingerprint of the DBMS, lib/controller/handler.py -
500
fingerprint phase.
501
"""
502
503
return kb.htmlFp if kb.get("heuristicTest") == HEURISTIC_TEST.POSITIVE else []
504
505
@staticmethod
506
def getIdentifiedDbms():
507
"""
508
This functions is called to:
509
510
1. Sort the tests, getSortedInjectionTests() - detection phase.
511
2. Etc.
512
"""
513
514
dbms = None
515
516
if not kb:
517
pass
518
elif not kb.get("testMode") and conf.get("dbmsHandler") and getattr(conf.dbmsHandler, "_dbms", None):
519
dbms = conf.dbmsHandler._dbms
520
elif Backend.getForcedDbms() is not None:
521
dbms = Backend.getForcedDbms()
522
elif Backend.getDbms() is not None:
523
dbms = Backend.getDbms()
524
elif kb.get("injection") and kb.injection.dbms:
525
dbms = unArrayizeValue(kb.injection.dbms)
526
elif Backend.getErrorParsedDBMSes():
527
dbms = unArrayizeValue(Backend.getErrorParsedDBMSes())
528
elif conf.get("dbms"):
529
dbms = conf.get("dbms")
530
531
return aliasToDbmsEnum(dbms)
532
533
@staticmethod
534
def getVersion():
535
versions = filterNone(flattenValue(kb.dbmsVersion)) if not isinstance(kb.dbmsVersion, six.string_types) else [kb.dbmsVersion]
536
if not isNoneValue(versions):
537
return versions[0]
538
else:
539
return None
540
541
@staticmethod
542
def getVersionList():
543
versions = filterNone(flattenValue(kb.dbmsVersion)) if not isinstance(kb.dbmsVersion, six.string_types) else [kb.dbmsVersion]
544
if not isNoneValue(versions):
545
return versions
546
else:
547
return None
548
549
@staticmethod
550
def getOs():
551
return kb.os
552
553
@staticmethod
554
def getOsVersion():
555
return kb.osVersion
556
557
@staticmethod
558
def getOsServicePack():
559
return kb.osSP
560
561
@staticmethod
562
def getArch():
563
if kb.arch is None:
564
Backend.setArch()
565
return kb.arch
566
567
# Comparison methods
568
@staticmethod
569
def isDbms(dbms):
570
if not kb.get("testMode") and all((Backend.getDbms(), Backend.getIdentifiedDbms())) and Backend.getDbms() != Backend.getIdentifiedDbms():
571
singleTimeWarnMessage("identified ('%s') and fingerprinted ('%s') DBMSes differ. If you experience problems in enumeration phase please rerun with '--flush-session'" % (Backend.getIdentifiedDbms(), Backend.getDbms()))
572
return Backend.getIdentifiedDbms() == aliasToDbmsEnum(dbms)
573
574
@staticmethod
575
def isFork(fork):
576
return hashDBRetrieve(HASHDB_KEYS.DBMS_FORK) == fork
577
578
@staticmethod
579
def isDbmsWithin(aliases):
580
return Backend.getDbms() is not None and Backend.getDbms().lower() in aliases
581
582
@staticmethod
583
def isVersion(version):
584
return Backend.getVersion() is not None and Backend.getVersion() == version
585
586
@staticmethod
587
def isVersionWithin(versionList):
588
if Backend.getVersionList() is None:
589
return False
590
591
for _ in Backend.getVersionList():
592
if _ != UNKNOWN_DBMS_VERSION and _ in versionList:
593
return True
594
595
return False
596
597
@staticmethod
598
def isVersionGreaterOrEqualThan(version):
599
retVal = False
600
601
if all(_ not in (None, UNKNOWN_DBMS_VERSION) for _ in (Backend.getVersion(), version)):
602
_version = unArrayizeValue(Backend.getVersion())
603
_version = re.sub(r"[<>= ]", "", _version)
604
605
try:
606
retVal = LooseVersion(_version) >= LooseVersion(version)
607
except:
608
retVal = str(_version) >= str(version)
609
610
return retVal
611
612
@staticmethod
613
def isOs(os):
614
return Backend.getOs() is not None and Backend.getOs().lower() == os.lower()
615
616
def paramToDict(place, parameters=None):
617
"""
618
Split the parameters into names and values, check if these parameters
619
are within the testable parameters and return in a dictionary.
620
"""
621
622
testableParameters = OrderedDict()
623
624
if place in conf.parameters and not parameters:
625
parameters = conf.parameters[place]
626
627
parameters = re.sub(r"&(\w{1,4});", r"%s\g<1>%s" % (PARAMETER_AMP_MARKER, PARAMETER_SEMICOLON_MARKER), parameters)
628
if place == PLACE.COOKIE:
629
splitParams = parameters.split(conf.cookieDel or DEFAULT_COOKIE_DELIMITER)
630
else:
631
splitParams = parameters.split(conf.paramDel or DEFAULT_GET_POST_DELIMITER)
632
633
for element in splitParams:
634
element = re.sub(r"%s(.+?)%s" % (PARAMETER_AMP_MARKER, PARAMETER_SEMICOLON_MARKER), r"&\g<1>;", element)
635
parts = element.split("=")
636
637
if len(parts) >= 2:
638
parameter = urldecode(parts[0].replace(" ", ""))
639
640
if not parameter:
641
continue
642
643
if conf.paramDel and conf.paramDel == '\n':
644
parts[-1] = parts[-1].rstrip()
645
646
condition = not conf.testParameter
647
condition |= conf.testParameter is not None and parameter in conf.testParameter
648
condition |= place == PLACE.COOKIE and len(intersect((PLACE.COOKIE,), conf.testParameter, True)) > 0
649
650
if condition:
651
value = "=".join(parts[1:])
652
653
if parameter in (conf.base64Parameter or []):
654
try:
655
kb.base64Originals[parameter] = oldValue = value
656
value = urldecode(value, convall=True)
657
value = decodeBase64(value, binary=False, encoding=conf.encoding or UNICODE_ENCODING)
658
parameters = re.sub(r"\b%s(\b|\Z)" % re.escape(oldValue), value, parameters)
659
except:
660
errMsg = "parameter '%s' does not contain " % parameter
661
errMsg += "valid Base64 encoded value ('%s')" % value
662
raise SqlmapValueException(errMsg)
663
664
testableParameters[parameter] = value
665
666
if not conf.multipleTargets and not (conf.csrfToken and re.search(conf.csrfToken, parameter, re.I)):
667
_ = urldecode(testableParameters[parameter], convall=True)
668
if (_.endswith("'") and _.count("'") == 1 or re.search(r'\A9{3,}', _) or re.search(r'\A-\d+\Z', _) or re.search(DUMMY_USER_INJECTION, _)) and not re.search(GOOGLE_ANALYTICS_COOKIE_REGEX, parameter):
669
warnMsg = "it appears that you have provided tainted parameter values "
670
warnMsg += "('%s') with most likely leftover " % element
671
warnMsg += "chars/statements from manual SQL injection test(s). "
672
warnMsg += "Please, always use only valid parameter values "
673
warnMsg += "so sqlmap could be able to run properly"
674
logger.warning(warnMsg)
675
676
message = "are you really sure that you want to continue (sqlmap could have problems)? [y/N] "
677
678
if not readInput(message, default='N', boolean=True):
679
raise SqlmapSilentQuitException
680
elif not _:
681
warnMsg = "provided value for parameter '%s' is empty. " % parameter
682
warnMsg += "Please, always use only valid parameter values "
683
warnMsg += "so sqlmap could be able to run properly"
684
logger.warning(warnMsg)
685
686
if place in (PLACE.POST, PLACE.GET):
687
for regex in (r"\A((?:<[^>]+>)+\w+)((?:<[^>]+>)+)\Z", r"\A([^\w]+.*\w+)([^\w]+)\Z"):
688
match = re.search(regex, testableParameters[parameter])
689
if match:
690
try:
691
candidates = OrderedDict()
692
693
def walk(head, current=None):
694
if current is None:
695
current = head
696
if isListLike(current):
697
for _ in current:
698
walk(head, _)
699
elif isinstance(current, dict):
700
for key in current.keys():
701
value = current[key]
702
if isinstance(value, (bool, int, float, six.string_types)) or value in (None, []):
703
original = current[key]
704
if isinstance(value, bool):
705
current[key] = "%s%s" % (getUnicode(value).lower(), BOUNDED_INJECTION_MARKER)
706
elif value is None:
707
current[key] = "%s%s" % (randomInt(), BOUNDED_INJECTION_MARKER)
708
elif value == []:
709
current[key] = ["%s%s" % (randomInt(), BOUNDED_INJECTION_MARKER)]
710
else:
711
current[key] = "%s%s" % (value, BOUNDED_INJECTION_MARKER)
712
candidates["%s (%s)" % (parameter, key)] = re.sub(r"\b(%s\s*=\s*)%s" % (re.escape(parameter), re.escape(testableParameters[parameter])), r"\g<1>%s" % json.dumps(deserialized, separators=(',', ':') if ", " not in testableParameters[parameter] else None), parameters)
713
current[key] = original
714
elif isinstance(value, (list, tuple, set, dict)):
715
if value:
716
walk(head, value)
717
718
# NOTE: for cases with custom injection marker(s) inside (e.g. https://github.com/sqlmapproject/sqlmap/issues/4137#issuecomment-2013783111) - p.s. doesn't care too much about the structure (e.g. injection into the flat array values)
719
if CUSTOM_INJECTION_MARK_CHAR in testableParameters[parameter]:
720
for match in re.finditer(r'(\w+)[^\w]*"\s*:[^\w]*\w*%s' % re.escape(CUSTOM_INJECTION_MARK_CHAR), testableParameters[parameter]):
721
key = match.group(1)
722
value = testableParameters[parameter].replace(match.group(0), match.group(0).replace(CUSTOM_INJECTION_MARK_CHAR, BOUNDED_INJECTION_MARKER))
723
candidates["%s (%s)" % (parameter, key)] = re.sub(r"\b(%s\s*=\s*)%s" % (re.escape(parameter), re.escape(testableParameters[parameter])), r"\g<1>%s" % value, parameters)
724
725
if not candidates:
726
deserialized = json.loads(testableParameters[parameter])
727
walk(deserialized)
728
729
if candidates:
730
message = "it appears that provided value for %sparameter '%s' " % ("%s " % place if place != parameter else "", parameter)
731
message += "is JSON deserializable. Do you want to inject inside? [y/N] "
732
733
if readInput(message, default='N', boolean=True):
734
del testableParameters[parameter]
735
testableParameters.update(candidates)
736
break
737
except (KeyboardInterrupt, SqlmapUserQuitException):
738
raise
739
except Exception:
740
pass
741
742
_ = re.sub(regex, r"\g<1>%s\g<%d>" % (kb.customInjectionMark, len(match.groups())), testableParameters[parameter])
743
message = "it appears that provided value for %sparameter '%s' " % ("%s " % place if place != parameter else "", parameter)
744
message += "has boundaries. Do you want to inject inside? ('%s') [y/N] " % getUnicode(_)
745
746
if readInput(message, default='N', boolean=True):
747
testableParameters[parameter] = re.sub(r"\b(%s\s*=\s*)%s" % (re.escape(parameter), re.escape(testableParameters[parameter])), (r"\g<1>%s" % re.sub(regex, r"\g<1>%s\g<2>" % BOUNDED_INJECTION_MARKER, testableParameters[parameter].replace("\\", r"\\"))), parameters)
748
break
749
750
if conf.testParameter:
751
if not testableParameters:
752
paramStr = ", ".join(test for test in conf.testParameter)
753
754
if len(conf.testParameter) > 1:
755
warnMsg = "provided parameters '%s' " % paramStr
756
warnMsg += "are not inside the %s" % place
757
logger.warning(warnMsg)
758
else:
759
parameter = conf.testParameter[0]
760
761
if not intersect(USER_AGENT_ALIASES + REFERER_ALIASES + HOST_ALIASES, parameter, True):
762
debugMsg = "provided parameter '%s' " % paramStr
763
debugMsg += "is not inside the %s" % place
764
logger.debug(debugMsg)
765
766
elif len(conf.testParameter) != len(testableParameters):
767
for parameter in conf.testParameter:
768
if parameter not in testableParameters:
769
debugMsg = "provided parameter '%s' " % parameter
770
debugMsg += "is not inside the %s" % place
771
logger.debug(debugMsg)
772
773
if testableParameters:
774
for parameter, value in testableParameters.items():
775
if value and not value.isdigit():
776
for encoding in ("hex", "base64"):
777
try:
778
decoded = codecs.decode(value, encoding)
779
if len(decoded) > MIN_ENCODED_LEN_CHECK and all(_ in getBytes(string.printable) for _ in decoded):
780
warnMsg = "provided parameter '%s' " % parameter
781
warnMsg += "appears to be '%s' encoded" % encoding
782
logger.warning(warnMsg)
783
break
784
except:
785
pass
786
787
return testableParameters
788
789
def getManualDirectories():
790
directories = None
791
defaultDocRoot = DEFAULT_DOC_ROOTS.get(Backend.getOs(), DEFAULT_DOC_ROOTS[OS.LINUX])
792
793
if kb.absFilePaths:
794
for absFilePath in kb.absFilePaths:
795
if directories:
796
break
797
798
if directoryPath(absFilePath) == '/':
799
continue
800
801
absFilePath = normalizePath(absFilePath)
802
windowsDriveLetter = None
803
804
if isWindowsDriveLetterPath(absFilePath):
805
windowsDriveLetter, absFilePath = absFilePath[:2], absFilePath[2:]
806
absFilePath = ntToPosixSlashes(posixToNtSlashes(absFilePath))
807
808
for _ in list(GENERIC_DOC_ROOT_DIRECTORY_NAMES) + [conf.hostname]:
809
_ = "/%s/" % _
810
811
if _ in absFilePath:
812
directories = "%s%s" % (absFilePath.split(_)[0], _)
813
break
814
815
if not directories and conf.path.strip('/') and conf.path in absFilePath:
816
directories = absFilePath.split(conf.path)[0]
817
818
if directories and windowsDriveLetter:
819
directories = "%s/%s" % (windowsDriveLetter, ntToPosixSlashes(directories))
820
821
directories = normalizePath(directories)
822
823
if conf.webRoot:
824
directories = [conf.webRoot]
825
infoMsg = "using '%s' as web server document root" % conf.webRoot
826
logger.info(infoMsg)
827
elif directories:
828
infoMsg = "retrieved the web server document root: '%s'" % directories
829
logger.info(infoMsg)
830
else:
831
warnMsg = "unable to automatically retrieve the web server "
832
warnMsg += "document root"
833
logger.warning(warnMsg)
834
835
directories = []
836
837
message = "what do you want to use for writable directory?\n"
838
message += "[1] common location(s) ('%s') (default)\n" % ", ".join(root for root in defaultDocRoot)
839
message += "[2] custom location(s)\n"
840
message += "[3] custom directory list file\n"
841
message += "[4] brute force search"
842
choice = readInput(message, default='1')
843
844
if choice == '2':
845
message = "please provide a comma separate list of absolute directory paths: "
846
directories = readInput(message, default="").split(',')
847
elif choice == '3':
848
message = "what's the list file location?\n"
849
listPath = readInput(message, default="")
850
checkFile(listPath)
851
directories = getFileItems(listPath)
852
elif choice == '4':
853
targets = set([conf.hostname])
854
_ = conf.hostname.split('.')
855
856
if _[0] == "www":
857
targets.add('.'.join(_[1:]))
858
targets.add('.'.join(_[1:-1]))
859
else:
860
targets.add('.'.join(_[:-1]))
861
862
targets = filterNone(targets)
863
864
for prefix in BRUTE_DOC_ROOT_PREFIXES.get(Backend.getOs(), DEFAULT_DOC_ROOTS[OS.LINUX]):
865
if BRUTE_DOC_ROOT_TARGET_MARK in prefix and re.match(IP_ADDRESS_REGEX, conf.hostname):
866
continue
867
868
for suffix in BRUTE_DOC_ROOT_SUFFIXES:
869
for target in targets:
870
if not prefix.endswith("/%s" % suffix):
871
item = "%s/%s" % (prefix, suffix)
872
else:
873
item = prefix
874
875
item = item.replace(BRUTE_DOC_ROOT_TARGET_MARK, target).replace("//", '/').rstrip('/')
876
if item not in directories:
877
directories.append(item)
878
879
if BRUTE_DOC_ROOT_TARGET_MARK not in prefix:
880
break
881
882
infoMsg = "using generated directory list: %s" % ','.join(directories)
883
logger.info(infoMsg)
884
885
msg = "use any additional custom directories [Enter for None]: "
886
answer = readInput(msg)
887
888
if answer:
889
directories.extend(answer.split(','))
890
891
else:
892
directories = defaultDocRoot
893
894
return directories
895
896
def getAutoDirectories():
897
"""
898
>>> pushValue(kb.absFilePaths)
899
>>> kb.absFilePaths = [r"C:\\inetpub\\wwwroot\\index.asp", "/var/www/html"]
900
>>> getAutoDirectories()
901
['C:/inetpub/wwwroot', '/var/www/html']
902
>>> kb.absFilePaths = popValue()
903
"""
904
905
retVal = OrderedSet()
906
907
if kb.absFilePaths:
908
infoMsg = "retrieved web server absolute paths: "
909
infoMsg += "'%s'" % ", ".join(ntToPosixSlashes(path) for path in kb.absFilePaths)
910
logger.info(infoMsg)
911
912
for absFilePath in kb.absFilePaths:
913
if absFilePath:
914
directory = directoryPath(absFilePath)
915
directory = ntToPosixSlashes(directory)
916
retVal.add(directory)
917
else:
918
warnMsg = "unable to automatically parse any web server path"
919
logger.warning(warnMsg)
920
921
return list(retVal)
922
923
def filePathToSafeString(filePath):
924
"""
925
Returns string representation of a given filepath safe for a single filename usage
926
927
>>> filePathToSafeString('C:/Windows/system32')
928
'C__Windows_system32'
929
"""
930
931
retVal = filePath.replace("/", "_").replace("\\", "_")
932
retVal = retVal.replace(" ", "_").replace(":", "_")
933
934
return retVal
935
936
def singleTimeDebugMessage(message):
937
singleTimeLogMessage(message, logging.DEBUG)
938
939
def singleTimeWarnMessage(message):
940
singleTimeLogMessage(message, logging.WARN)
941
942
def singleTimeLogMessage(message, level=logging.INFO, flag=None):
943
if flag is None:
944
flag = hash(message)
945
946
if not conf.smokeTest and flag not in kb.singleLogFlags:
947
kb.singleLogFlags.add(flag)
948
logger.log(level, message)
949
950
def boldifyMessage(message, istty=None):
951
"""
952
Sets ANSI bold marking on entire message if parts found in predefined BOLD_PATTERNS
953
954
>>> boldifyMessage("Hello World", istty=True)
955
'Hello World'
956
957
>>> boldifyMessage("GET parameter id is not injectable", istty=True)
958
'\\x1b[1mGET parameter id is not injectable\\x1b[0m'
959
"""
960
961
retVal = message
962
963
if re.search(BOLD_PATTERNS_REGEX, message):
964
retVal = setColor(message, bold=True, istty=istty)
965
966
return retVal
967
968
def setColor(message, color=None, bold=False, level=None, istty=None):
969
"""
970
Sets ANSI color codes
971
972
>>> setColor("Hello World", color="red", istty=True)
973
'\\x1b[31mHello World\\x1b[0m'
974
>>> setColor("[INFO] Hello World", istty=True)
975
'[\\x1b[32mINFO\\x1b[0m] Hello World'
976
>>> setColor("[INFO] Hello [CRITICAL] World", istty=True)
977
'[INFO] Hello [CRITICAL] World'
978
"""
979
980
retVal = message
981
982
if message:
983
if (IS_TTY or istty) and not conf.get("disableColoring"): # colorizing handler
984
if level is None:
985
levels = re.findall(r"\[(?P<result>%s)\]" % '|'.join(_[0] for _ in getPublicTypeMembers(LOGGING_LEVELS)), message)
986
987
if len(levels) == 1:
988
level = levels[0]
989
990
if bold or color:
991
retVal = colored(message, color=color, on_color=None, attrs=("bold",) if bold else None)
992
elif level:
993
try:
994
level = getattr(logging, level, None)
995
except:
996
level = None
997
retVal = LOGGER_HANDLER.colorize(message, level, True)
998
else:
999
match = re.search(r"\(([^)]*)\s*fork\)", message)
1000
if match:
1001
retVal = retVal.replace(match.group(1), colored(match.group(1), color="lightgrey"))
1002
1003
if not any(_ in message for _ in ("Payload: ",)):
1004
for match in re.finditer(r"([^\w])'([^\n']+)'", message): # single-quoted (Note: watch-out for the banner)
1005
retVal = retVal.replace(match.group(0), "%s'%s'" % (match.group(1), colored(match.group(2), color="lightgrey")))
1006
1007
message = message.strip()
1008
1009
return retVal
1010
1011
def clearColors(message):
1012
"""
1013
Clears ANSI color codes
1014
1015
>>> clearColors("\x1b[38;5;82mHello \x1b[38;5;198mWorld")
1016
'Hello World'
1017
"""
1018
1019
retVal = message
1020
1021
if isinstance(message, str):
1022
retVal = re.sub(r"\x1b\[[\d;]+m", "", message)
1023
1024
return retVal
1025
1026
def dataToStdout(data, forceOutput=False, bold=False, contentType=None, status=CONTENT_STATUS.IN_PROGRESS, coloring=True):
1027
"""
1028
Writes text to the stdout (console) stream
1029
"""
1030
1031
if not IS_TTY and isinstance(data, six.string_types) and data.startswith("\r"):
1032
if re.search(r"\(\d+%\)", data):
1033
data = ""
1034
else:
1035
data = "\n%s" % data.strip("\r")
1036
1037
if not kb.get("threadException"):
1038
if forceOutput or not (getCurrentThreadData().disableStdOut or kb.get("wizardMode")):
1039
multiThreadMode = kb.get("multiThreadMode")
1040
if multiThreadMode:
1041
logging._acquireLock()
1042
1043
try:
1044
if conf.get("api"):
1045
sys.stdout.write(stdoutEncode(clearColors(data)), status, contentType)
1046
else:
1047
sys.stdout.write(stdoutEncode(setColor(data, bold=bold) if coloring else clearColors(data)))
1048
except IOError:
1049
pass
1050
except UnicodeEncodeError:
1051
sys.stdout.write(re.sub(r"[^ -~]", '?', clearColors(data)))
1052
finally:
1053
try:
1054
sys.stdout.flush()
1055
except IOError:
1056
raise SystemExit
1057
1058
if multiThreadMode:
1059
logging._releaseLock()
1060
1061
kb.prependFlag = isinstance(data, six.string_types) and (len(data) == 1 and data not in ('\n', '\r') or len(data) > 2 and data[0] == '\r' and data[-1] != '\n')
1062
1063
def dataToTrafficFile(data):
1064
if not conf.trafficFile:
1065
return
1066
1067
try:
1068
conf.trafficFP.write(data)
1069
conf.trafficFP.flush()
1070
except IOError as ex:
1071
errMsg = "something went wrong while trying "
1072
errMsg += "to write to the traffic file '%s' ('%s')" % (conf.trafficFile, getSafeExString(ex))
1073
raise SqlmapSystemException(errMsg)
1074
1075
def dataToDumpFile(dumpFile, data):
1076
try:
1077
dumpFile.write(data)
1078
dumpFile.flush()
1079
except IOError as ex:
1080
if "No space left" in getUnicode(ex):
1081
errMsg = "no space left on output device"
1082
logger.error(errMsg)
1083
elif "Permission denied" in getUnicode(ex):
1084
errMsg = "permission denied when flushing dump data"
1085
logger.error(errMsg)
1086
else:
1087
errMsg = "error occurred when writing dump data to file ('%s')" % getUnicode(ex)
1088
logger.error(errMsg)
1089
1090
def dataToOutFile(filename, data):
1091
"""
1092
Saves data to filename
1093
1094
>>> pushValue(conf.get("filePath"))
1095
>>> conf.filePath = tempfile.gettempdir()
1096
>>> "_etc_passwd" in dataToOutFile("/etc/passwd", b":::*")
1097
True
1098
>>> conf.filePath = popValue()
1099
"""
1100
1101
retVal = None
1102
1103
if data:
1104
while True:
1105
retVal = os.path.join(conf.filePath, filePathToSafeString(filename))
1106
1107
try:
1108
with open(retVal, "w+b") as f: # has to stay as non-codecs because data is raw ASCII encoded data
1109
f.write(getBytes(data))
1110
except UnicodeEncodeError as ex:
1111
_ = normalizeUnicode(filename)
1112
if filename != _:
1113
filename = _
1114
else:
1115
errMsg = "couldn't write to the "
1116
errMsg += "output file ('%s')" % getSafeExString(ex)
1117
raise SqlmapGenericException(errMsg)
1118
except IOError as ex:
1119
errMsg = "something went wrong while trying to write "
1120
errMsg += "to the output file ('%s')" % getSafeExString(ex)
1121
raise SqlmapGenericException(errMsg)
1122
else:
1123
break
1124
1125
return retVal
1126
1127
def readInput(message, default=None, checkBatch=True, boolean=False):
1128
"""
1129
Reads input from terminal
1130
"""
1131
1132
retVal = None
1133
1134
message = getUnicode(message)
1135
1136
if "\n" in message:
1137
message += "%s> " % ("\n" if message.count("\n") > 1 else "")
1138
elif message[-1] == ']':
1139
message += " "
1140
1141
if kb.get("prependFlag"):
1142
message = "\n%s" % message
1143
kb.prependFlag = False
1144
1145
if conf.get("answers"):
1146
if not any(_ in conf.answers for _ in ",="):
1147
return conf.answers
1148
1149
for item in conf.answers.split(','):
1150
question = item.split('=')[0].strip()
1151
answer = item.split('=')[1] if len(item.split('=')) > 1 else None
1152
if answer and question.lower() in message.lower():
1153
retVal = getUnicode(answer, UNICODE_ENCODING)
1154
elif answer is None and retVal:
1155
retVal = "%s,%s" % (retVal, getUnicode(item, UNICODE_ENCODING))
1156
1157
if message and IS_TTY:
1158
message = "\r%s" % message
1159
1160
if retVal:
1161
dataToStdout("%s%s\n" % (message, retVal), forceOutput=not kb.wizardMode, bold=True)
1162
1163
debugMsg = "used the given answer"
1164
logger.debug(debugMsg)
1165
1166
if retVal is None:
1167
if checkBatch and conf.get("batch") or any(conf.get(_) for _ in ("api", "nonInteractive")):
1168
if isListLike(default):
1169
options = ','.join(getUnicode(opt, UNICODE_ENCODING) for opt in default)
1170
elif default:
1171
options = getUnicode(default, UNICODE_ENCODING)
1172
else:
1173
options = six.text_type()
1174
1175
dataToStdout("%s%s\n" % (message, options), forceOutput=not kb.wizardMode, bold=True)
1176
1177
debugMsg = "used the default behavior, running in batch mode"
1178
logger.debug(debugMsg)
1179
1180
retVal = default
1181
else:
1182
try:
1183
logging._acquireLock()
1184
1185
if conf.get("beep"):
1186
beep()
1187
1188
dataToStdout("%s" % message, forceOutput=not kb.wizardMode, bold=True)
1189
kb.prependFlag = False
1190
1191
retVal = _input()
1192
if not retVal: # Note: Python doesn't print newline on empty input
1193
dataToStdout("\n")
1194
retVal = retVal.strip() or default
1195
retVal = getUnicode(retVal, encoding=getattr(sys.stdin, "encoding", None)) if retVal else retVal
1196
except:
1197
try:
1198
time.sleep(0.05) # Reference: http://www.gossamer-threads.com/lists/python/python/781893
1199
except:
1200
pass
1201
finally:
1202
kb.prependFlag = True
1203
raise SqlmapUserQuitException
1204
1205
finally:
1206
logging._releaseLock()
1207
1208
if retVal and default and isinstance(default, six.string_types) and len(default) == 1:
1209
retVal = retVal.strip()
1210
1211
if boolean:
1212
retVal = retVal.strip().upper() == 'Y'
1213
else:
1214
retVal = retVal or ""
1215
1216
return retVal
1217
1218
def setTechnique(technique):
1219
"""
1220
Thread-safe setting of currently used technique (Note: dealing with cases of per-thread technique switching)
1221
"""
1222
1223
getCurrentThreadData().technique = technique
1224
1225
def getTechnique():
1226
"""
1227
Thread-safe getting of currently used technique
1228
"""
1229
1230
return getCurrentThreadData().technique or kb.get("technique")
1231
1232
def randomRange(start=0, stop=1000, seed=None):
1233
"""
1234
Returns random integer value in given range
1235
1236
>>> random.seed(0)
1237
>>> randomRange(1, 500)
1238
152
1239
"""
1240
1241
if seed is not None:
1242
_ = getCurrentThreadData().random
1243
_.seed(seed)
1244
randint = _.randint
1245
else:
1246
randint = random.randint
1247
1248
return int(randint(start, stop))
1249
1250
def randomInt(length=4, seed=None):
1251
"""
1252
Returns random integer value with provided number of digits
1253
1254
>>> random.seed(0)
1255
>>> randomInt(6)
1256
963638
1257
"""
1258
1259
if seed is not None:
1260
_ = getCurrentThreadData().random
1261
_.seed(seed)
1262
choice = _.choice
1263
else:
1264
choice = random.choice
1265
1266
return int("".join(choice(string.digits if _ != 0 else string.digits.replace('0', '')) for _ in xrange(0, length)))
1267
1268
def randomStr(length=4, lowercase=False, alphabet=None, seed=None):
1269
"""
1270
Returns random string value with provided number of characters
1271
1272
>>> random.seed(0)
1273
>>> randomStr(6)
1274
'FUPGpY'
1275
"""
1276
1277
if seed is not None:
1278
_random = getCurrentThreadData().random
1279
_random.seed(seed)
1280
choice = _random.choice
1281
else:
1282
choice = random.choice
1283
1284
if alphabet:
1285
retVal = "".join(choice(alphabet) for _ in xrange(0, length))
1286
elif lowercase:
1287
retVal = "".join(choice(string.ascii_lowercase) for _ in xrange(0, length))
1288
else:
1289
retVal = "".join(choice(string.ascii_letters) for _ in xrange(0, length))
1290
1291
return retVal
1292
1293
def sanitizeStr(value):
1294
"""
1295
Sanitizes string value in respect to newline and line-feed characters
1296
1297
>>> sanitizeStr('foo\\n\\rbar') == 'foo bar'
1298
True
1299
>>> sanitizeStr(None) == 'None'
1300
True
1301
"""
1302
1303
return getUnicode(value).replace("\n", " ").replace("\r", "")
1304
1305
def getHeader(headers, key):
1306
"""
1307
Returns header value ignoring the letter case
1308
1309
>>> getHeader({"Foo": "bar"}, "foo")
1310
'bar'
1311
"""
1312
1313
retVal = None
1314
1315
for header in (headers or {}):
1316
if header.upper() == key.upper():
1317
retVal = headers[header]
1318
break
1319
1320
return retVal
1321
1322
def checkPipedInput():
1323
"""
1324
Checks whether input to program has been provided via standard input (e.g. cat /tmp/req.txt | python sqlmap.py -r -)
1325
# Reference: https://stackoverflow.com/a/33873570
1326
"""
1327
1328
return hasattr(sys.stdin, "fileno") and not os.isatty(sys.stdin.fileno())
1329
1330
def isZipFile(filename):
1331
"""
1332
Checks if file contains zip compressed content
1333
1334
>>> isZipFile(paths.WORDLIST)
1335
True
1336
"""
1337
1338
checkFile(filename)
1339
1340
with openFile(filename, "rb", encoding=None) as f:
1341
header = f.read(len(ZIP_HEADER))
1342
1343
return header == ZIP_HEADER
1344
1345
def isDigit(value):
1346
"""
1347
Checks if provided (string) value consists of digits (Note: Python's isdigit() is problematic)
1348
1349
>>> u'\xb2'.isdigit()
1350
True
1351
>>> isDigit(u'\xb2')
1352
False
1353
>>> isDigit('123456')
1354
True
1355
>>> isDigit('3b3')
1356
False
1357
"""
1358
1359
return re.search(r"\A[0-9]+\Z", value or "") is not None
1360
1361
def checkFile(filename, raiseOnError=True):
1362
"""
1363
Checks for file existence and readability
1364
1365
>>> checkFile(__file__)
1366
True
1367
"""
1368
1369
valid = True
1370
1371
if filename:
1372
filename = filename.strip('"\'')
1373
1374
if filename == STDIN_PIPE_DASH:
1375
return checkPipedInput()
1376
else:
1377
try:
1378
if filename is None or not os.path.isfile(filename):
1379
valid = False
1380
except:
1381
valid = False
1382
1383
if valid:
1384
try:
1385
with open(filename, "rb"):
1386
pass
1387
except:
1388
valid = False
1389
1390
if not valid and raiseOnError:
1391
raise SqlmapSystemException("unable to read file '%s'" % filename)
1392
1393
return valid
1394
1395
def banner():
1396
"""
1397
This function prints sqlmap banner with its version
1398
"""
1399
1400
if not any(_ in sys.argv for _ in ("--version", "--api")) and not conf.get("disableBanner"):
1401
result = BANNER
1402
1403
if not IS_TTY or any(_ in sys.argv for _ in ("--disable-coloring", "--disable-colouring")):
1404
result = clearColors(result)
1405
elif IS_WIN:
1406
coloramainit()
1407
1408
dataToStdout(result, forceOutput=True)
1409
1410
def parseJson(content):
1411
"""
1412
This function parses POST_HINT.JSON and POST_HINT.JSON_LIKE content
1413
1414
>>> parseJson("{'id':1, 'foo':[2,3,4]}")["id"] == 1
1415
True
1416
>>> parseJson('{"id":1}')["id"] == 1
1417
True
1418
"""
1419
1420
quote = None
1421
retVal = None
1422
1423
for regex in (r"'[^']+'\s*:", r'"[^"]+"\s*:'):
1424
match = re.search(regex, content)
1425
if match:
1426
quote = match.group(0)[0]
1427
1428
try:
1429
if quote == '"':
1430
retVal = json.loads(content)
1431
elif quote == "'":
1432
def _(match):
1433
return '"%s"' % match.group(1).replace('"', '\\"')
1434
1435
content = re.sub(r"'((?:[^'\\]|\\.)*)'", _, content)
1436
retVal = json.loads(content)
1437
except:
1438
pass
1439
1440
return retVal
1441
1442
def parsePasswordHash(password):
1443
"""
1444
In case of Microsoft SQL Server password hash value is expanded to its components
1445
1446
>>> pushValue(kb.forcedDbms)
1447
>>> kb.forcedDbms = DBMS.MSSQL
1448
>>> "salt: 4086ceb6" in parsePasswordHash("0x01004086ceb60c90646a8ab9889fe3ed8e5c150b5460ece8425a")
1449
True
1450
>>> kb.forcedDbms = popValue()
1451
"""
1452
1453
blank = ' ' * 8
1454
1455
if isNoneValue(password) or password == ' ':
1456
retVal = NULL
1457
else:
1458
retVal = password
1459
1460
if Backend.isDbms(DBMS.MSSQL) and retVal != NULL and isHexEncodedString(password):
1461
retVal = "%s\n" % password
1462
retVal += "%sheader: %s\n" % (blank, password[:6])
1463
retVal += "%ssalt: %s\n" % (blank, password[6:14])
1464
retVal += "%smixedcase: %s\n" % (blank, password[14:54])
1465
1466
if password[54:]:
1467
retVal += "%suppercase: %s" % (blank, password[54:])
1468
1469
return retVal
1470
1471
def cleanQuery(query):
1472
"""
1473
Switch all SQL statement (alike) keywords to upper case
1474
1475
>>> cleanQuery("select id from users")
1476
'SELECT id FROM users'
1477
"""
1478
1479
retVal = query
1480
queryLower = query.lower()
1481
1482
for sqlStatements in SQL_STATEMENTS.values():
1483
for sqlStatement in sqlStatements:
1484
candidate = sqlStatement.replace("(", "").replace(")", "").strip()
1485
1486
# OPTIMIZATION: Skip expensive regex compilation/search if the keyword
1487
# isn't even present in the string. This makes the function O(K) instead of O(N*K)
1488
# for the expensive regex part (where K is num keywords).
1489
if not candidate or candidate.lower() not in queryLower:
1490
continue
1491
1492
queryMatch = re.search(r"(?i)\b(%s)\b" % candidate, query)
1493
1494
if queryMatch and "sys_exec" not in query:
1495
retVal = retVal.replace(queryMatch.group(1), candidate.upper())
1496
1497
return retVal
1498
1499
def cleanReplaceUnicode(value):
1500
"""
1501
Cleans unicode for proper encode/decode
1502
1503
>>> cleanReplaceUnicode(['a', 'b'])
1504
['a', 'b']
1505
"""
1506
1507
def clean(value):
1508
return value.encode(UNICODE_ENCODING, errors="replace").decode(UNICODE_ENCODING) if isinstance(value, six.text_type) else value
1509
1510
return applyFunctionRecursively(value, clean)
1511
1512
def setPaths(rootPath):
1513
"""
1514
Sets absolute paths for project directories and files
1515
"""
1516
1517
paths.SQLMAP_ROOT_PATH = rootPath
1518
1519
# sqlmap paths
1520
paths.SQLMAP_DATA_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "data")
1521
paths.SQLMAP_EXTRAS_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "extra")
1522
paths.SQLMAP_SETTINGS_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "lib", "core", "settings.py")
1523
paths.SQLMAP_TAMPER_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "tamper")
1524
1525
paths.SQLMAP_PROCS_PATH = os.path.join(paths.SQLMAP_DATA_PATH, "procs")
1526
paths.SQLMAP_SHELL_PATH = os.path.join(paths.SQLMAP_DATA_PATH, "shell")
1527
paths.SQLMAP_TXT_PATH = os.path.join(paths.SQLMAP_DATA_PATH, "txt")
1528
paths.SQLMAP_UDF_PATH = os.path.join(paths.SQLMAP_DATA_PATH, "udf")
1529
paths.SQLMAP_XML_PATH = os.path.join(paths.SQLMAP_DATA_PATH, "xml")
1530
paths.SQLMAP_XML_BANNER_PATH = os.path.join(paths.SQLMAP_XML_PATH, "banner")
1531
paths.SQLMAP_XML_PAYLOADS_PATH = os.path.join(paths.SQLMAP_XML_PATH, "payloads")
1532
1533
# sqlmap files
1534
paths.COMMON_COLUMNS = os.path.join(paths.SQLMAP_TXT_PATH, "common-columns.txt")
1535
paths.COMMON_FILES = os.path.join(paths.SQLMAP_TXT_PATH, "common-files.txt")
1536
paths.COMMON_TABLES = os.path.join(paths.SQLMAP_TXT_PATH, "common-tables.txt")
1537
paths.COMMON_OUTPUTS = os.path.join(paths.SQLMAP_TXT_PATH, 'common-outputs.txt')
1538
paths.DIGEST_FILE = os.path.join(paths.SQLMAP_TXT_PATH, "sha256sums.txt")
1539
paths.SQL_KEYWORDS = os.path.join(paths.SQLMAP_TXT_PATH, "keywords.txt")
1540
paths.SMALL_DICT = os.path.join(paths.SQLMAP_TXT_PATH, "smalldict.txt")
1541
paths.USER_AGENTS = os.path.join(paths.SQLMAP_TXT_PATH, "user-agents.txt")
1542
paths.WORDLIST = os.path.join(paths.SQLMAP_TXT_PATH, "wordlist.tx_")
1543
paths.ERRORS_XML = os.path.join(paths.SQLMAP_XML_PATH, "errors.xml")
1544
paths.BOUNDARIES_XML = os.path.join(paths.SQLMAP_XML_PATH, "boundaries.xml")
1545
paths.QUERIES_XML = os.path.join(paths.SQLMAP_XML_PATH, "queries.xml")
1546
paths.GENERIC_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "generic.xml")
1547
paths.MSSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "mssql.xml")
1548
paths.MYSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "mysql.xml")
1549
paths.ORACLE_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "oracle.xml")
1550
paths.PGSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "postgresql.xml")
1551
1552
for path in paths.values():
1553
if any(path.endswith(_) for _ in (".txt", ".xml", ".tx_")):
1554
checkFile(path)
1555
1556
if IS_WIN:
1557
# Reference: https://pureinfotech.com/list-environment-variables-windows-10/
1558
if os.getenv("LOCALAPPDATA"):
1559
paths.SQLMAP_HOME_PATH = os.path.expandvars("%LOCALAPPDATA%\\sqlmap")
1560
elif os.getenv("USERPROFILE"):
1561
paths.SQLMAP_HOME_PATH = os.path.expandvars("%USERPROFILE%\\Local Settings\\sqlmap")
1562
else:
1563
paths.SQLMAP_HOME_PATH = os.path.join(os.path.expandvars(os.path.expanduser("~")), "sqlmap")
1564
else:
1565
paths.SQLMAP_HOME_PATH = os.path.join(os.path.expandvars(os.path.expanduser("~")), ".sqlmap")
1566
1567
if not os.path.isdir(paths.SQLMAP_HOME_PATH):
1568
if "XDG_DATA_HOME" in os.environ:
1569
paths.SQLMAP_HOME_PATH = os.path.join(os.environ["XDG_DATA_HOME"], "sqlmap")
1570
else:
1571
paths.SQLMAP_HOME_PATH = os.path.join(os.path.expandvars(os.path.expanduser("~")), ".local", "share", "sqlmap")
1572
1573
paths.SQLMAP_OUTPUT_PATH = getUnicode(paths.get("SQLMAP_OUTPUT_PATH", os.path.join(paths.SQLMAP_HOME_PATH, "output")), encoding=sys.getfilesystemencoding() or UNICODE_ENCODING)
1574
paths.SQLMAP_DUMP_PATH = os.path.join(paths.SQLMAP_OUTPUT_PATH, "%s", "dump")
1575
paths.SQLMAP_FILES_PATH = os.path.join(paths.SQLMAP_OUTPUT_PATH, "%s", "files")
1576
1577
# History files
1578
paths.SQLMAP_HISTORY_PATH = getUnicode(os.path.join(paths.SQLMAP_HOME_PATH, "history"), encoding=sys.getfilesystemencoding() or UNICODE_ENCODING)
1579
paths.API_SHELL_HISTORY = os.path.join(paths.SQLMAP_HISTORY_PATH, "api.hst")
1580
paths.OS_SHELL_HISTORY = os.path.join(paths.SQLMAP_HISTORY_PATH, "os.hst")
1581
paths.SQL_SHELL_HISTORY = os.path.join(paths.SQLMAP_HISTORY_PATH, "sql.hst")
1582
paths.SQLMAP_SHELL_HISTORY = os.path.join(paths.SQLMAP_HISTORY_PATH, "sqlmap.hst")
1583
paths.GITHUB_HISTORY = os.path.join(paths.SQLMAP_HISTORY_PATH, "github.hst")
1584
1585
def weAreFrozen():
1586
"""
1587
Returns whether we are frozen via py2exe.
1588
This will affect how we find out where we are located.
1589
1590
# Reference: http://www.py2exe.org/index.cgi/WhereAmI
1591
"""
1592
1593
return hasattr(sys, "frozen")
1594
1595
def parseTargetDirect():
1596
"""
1597
Parse target dbms and set some attributes into the configuration singleton
1598
1599
>>> pushValue(conf.direct)
1600
>>> conf.direct = "mysql://root:[email protected]:3306/testdb"
1601
>>> parseTargetDirect()
1602
>>> conf.dbmsDb
1603
'testdb'
1604
>>> conf.dbmsPass
1605
'testpass'
1606
>>> conf.direct = "mysql://user:'P@ssw0rd'@127.0.0.1:3306/test"
1607
>>> parseTargetDirect()
1608
>>> conf.dbmsPass
1609
'P@ssw0rd'
1610
>>> conf.hostname
1611
'127.0.0.1'
1612
>>> conf.direct = popValue()
1613
"""
1614
1615
if not conf.direct:
1616
return
1617
1618
details = None
1619
remote = False
1620
1621
for dbms in SUPPORTED_DBMS:
1622
details = re.search(r"^(?P<dbms>%s)://(?P<credentials>(?P<user>.*?)\:(?P<pass>.*)\@)?(?P<remote>(?P<hostname>[\w.-]+?)\:(?P<port>[\d]+)\/)?(?P<db>[\w\d\ \:\.\_~\-\/\\]*)$" % dbms, conf.direct, re.I)
1623
1624
if details:
1625
conf.dbms = details.group("dbms")
1626
1627
if details.group("credentials"):
1628
conf.dbmsUser = details.group("user").strip("'\"")
1629
conf.dbmsPass = details.group("pass").strip("'\"")
1630
else:
1631
if conf.dbmsCred:
1632
conf.dbmsUser, conf.dbmsPass = conf.dbmsCred.split(':')
1633
else:
1634
conf.dbmsUser = ""
1635
conf.dbmsPass = ""
1636
1637
if not conf.dbmsPass:
1638
conf.dbmsPass = None
1639
1640
if details.group("remote"):
1641
remote = True
1642
conf.hostname = details.group("hostname").strip()
1643
conf.port = int(details.group("port"))
1644
else:
1645
conf.hostname = "localhost"
1646
conf.port = 0
1647
1648
conf.dbmsDb = details.group("db").strip() if details.group("db") is not None else None
1649
conf.parameters[None] = "direct connection"
1650
1651
break
1652
1653
if kb.smokeMode:
1654
return
1655
1656
if not details:
1657
errMsg = "invalid target details, valid syntax is for instance "
1658
errMsg += "'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME' "
1659
errMsg += "or 'access://DATABASE_FILEPATH'"
1660
raise SqlmapSyntaxException(errMsg)
1661
1662
for dbmsName, data in DBMS_DICT.items():
1663
if dbmsName == conf.dbms or conf.dbms.lower() in data[0]:
1664
try:
1665
conf.dbms = dbmsName
1666
1667
if dbmsName in (DBMS.ACCESS, DBMS.SQLITE, DBMS.FIREBIRD):
1668
if remote:
1669
warnMsg = "direct connection over the network for "
1670
warnMsg += "%s DBMS is not supported" % dbmsName
1671
logger.warning(warnMsg)
1672
1673
conf.hostname = "localhost"
1674
conf.port = 0
1675
elif not remote:
1676
errMsg = "missing remote connection details (e.g. "
1677
errMsg += "'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME' "
1678
errMsg += "or 'access://DATABASE_FILEPATH')"
1679
raise SqlmapSyntaxException(errMsg)
1680
1681
if dbmsName in (DBMS.MSSQL, DBMS.SYBASE):
1682
__import__("_mssql")
1683
pymssql = __import__("pymssql")
1684
1685
if not hasattr(pymssql, "__version__") or pymssql.__version__ < "1.0.2":
1686
errMsg = "'%s' third-party library must be " % data[1]
1687
errMsg += "version >= 1.0.2 to work properly. "
1688
errMsg += "Download from '%s'" % data[2]
1689
raise SqlmapMissingDependence(errMsg)
1690
1691
elif dbmsName == DBMS.MYSQL:
1692
__import__("pymysql")
1693
elif dbmsName == DBMS.PGSQL:
1694
__import__("psycopg2")
1695
elif dbmsName == DBMS.ORACLE:
1696
__import__("oracledb")
1697
elif dbmsName == DBMS.SQLITE:
1698
__import__("sqlite3")
1699
elif dbmsName == DBMS.ACCESS:
1700
__import__("pyodbc")
1701
elif dbmsName == DBMS.FIREBIRD:
1702
__import__("kinterbasdb")
1703
except (SqlmapSyntaxException, SqlmapMissingDependence):
1704
raise
1705
except:
1706
if _sqlalchemy and data[3] and any(_ in _sqlalchemy.dialects.__all__ for _ in (data[3], data[3].split('+')[0])):
1707
pass
1708
else:
1709
errMsg = "sqlmap requires '%s' third-party library " % data[1]
1710
errMsg += "in order to directly connect to the DBMS "
1711
errMsg += "'%s'. You can download it from '%s'" % (dbmsName, data[2])
1712
errMsg += ". Alternative is to use a package 'python-sqlalchemy' "
1713
errMsg += "with support for dialect '%s' installed" % data[3]
1714
raise SqlmapMissingDependence(errMsg)
1715
1716
def parseTargetUrl():
1717
"""
1718
Parse target URL and set some attributes into the configuration singleton
1719
1720
>>> pushValue(conf.url)
1721
>>> conf.url = "https://www.test.com/?id=1"
1722
>>> parseTargetUrl()
1723
>>> conf.hostname
1724
'www.test.com'
1725
>>> conf.scheme
1726
'https'
1727
>>> conf.url = popValue()
1728
"""
1729
1730
if not conf.url:
1731
return
1732
1733
originalUrl = conf.url
1734
1735
if re.search(r"://\[.+\]", conf.url) and not socket.has_ipv6:
1736
errMsg = "IPv6 communication is not supported "
1737
errMsg += "on this platform"
1738
raise SqlmapGenericException(errMsg)
1739
1740
if not re.search(r"^(http|ws)s?://", conf.url, re.I):
1741
if re.search(r":443\b", conf.url):
1742
conf.url = "https://%s" % conf.url
1743
else:
1744
conf.url = "http://%s" % conf.url
1745
1746
if kb.customInjectionMark in conf.url:
1747
conf.url = conf.url.replace('?', URI_QUESTION_MARKER)
1748
1749
try:
1750
urlSplit = _urllib.parse.urlsplit(conf.url)
1751
except ValueError as ex:
1752
errMsg = "invalid URL '%s' has been given ('%s'). " % (conf.url, getSafeExString(ex))
1753
errMsg += "Please be sure that you don't have any leftover characters (e.g. '[' or ']') "
1754
errMsg += "in the hostname part"
1755
raise SqlmapGenericException(errMsg)
1756
1757
hostnamePort = urlSplit.netloc.split(":") if not re.search(r"\[.+\]", urlSplit.netloc) else filterNone((re.search(r"\[.+\]", urlSplit.netloc).group(0), re.search(r"\](:(?P<port>\d+))?", urlSplit.netloc).group("port")))
1758
1759
conf.scheme = (urlSplit.scheme.strip().lower() or "http")
1760
conf.path = urlSplit.path.strip()
1761
conf.hostname = hostnamePort[0].strip()
1762
1763
if conf.forceSSL:
1764
conf.scheme = re.sub(r"(?i)\A(http|ws)\Z", r"\g<1>s", conf.scheme)
1765
1766
conf.ipv6 = conf.hostname != conf.hostname.strip("[]")
1767
conf.hostname = conf.hostname.strip("[]").replace(kb.customInjectionMark, "")
1768
1769
try:
1770
conf.hostname.encode("idna")
1771
conf.hostname.encode(UNICODE_ENCODING)
1772
except (LookupError, UnicodeError):
1773
invalid = True
1774
else:
1775
invalid = False
1776
1777
if any((invalid, re.search(r"\s", conf.hostname), '..' in conf.hostname, conf.hostname.startswith('.'), '\n' in originalUrl)):
1778
errMsg = "invalid target URL ('%s')" % originalUrl
1779
raise SqlmapSyntaxException(errMsg)
1780
1781
if len(hostnamePort) == 2:
1782
try:
1783
conf.port = int(hostnamePort[1])
1784
except:
1785
errMsg = "invalid target URL"
1786
raise SqlmapSyntaxException(errMsg)
1787
elif conf.scheme in ("https", "wss"):
1788
conf.port = 443
1789
else:
1790
conf.port = 80
1791
1792
if conf.port < 1 or conf.port > 65535:
1793
errMsg = "invalid target URL port (%d)" % conf.port
1794
raise SqlmapSyntaxException(errMsg)
1795
1796
conf.url = getUnicode("%s://%s%s%s" % (conf.scheme, ("[%s]" % conf.hostname) if conf.ipv6 else conf.hostname, (":%d" % conf.port) if not (conf.port == 80 and conf.scheme == "http" or conf.port == 443 and conf.scheme == "https") else "", conf.path))
1797
conf.url = conf.url.replace(URI_QUESTION_MARKER, '?')
1798
1799
if urlSplit.query:
1800
if '=' not in urlSplit.query:
1801
conf.url = "%s?%s" % (conf.url, getUnicode(urlSplit.query))
1802
else:
1803
conf.parameters[PLACE.GET] = urldecode(urlSplit.query, spaceplus=not conf.base64Parameter) if urlSplit.query and urlencode(DEFAULT_GET_POST_DELIMITER, None) not in urlSplit.query else urlSplit.query
1804
1805
if (intersect(REFERER_ALIASES, conf.testParameter, True) or conf.level >= 3) and not any(_[0].upper() == HTTP_HEADER.REFERER.upper() for _ in conf.httpHeaders):
1806
debugMsg = "setting the HTTP Referer header to the target URL"
1807
logger.debug(debugMsg)
1808
conf.httpHeaders = [_ for _ in conf.httpHeaders if _[0] != HTTP_HEADER.REFERER]
1809
conf.httpHeaders.append((HTTP_HEADER.REFERER, conf.url.replace(kb.customInjectionMark, "")))
1810
1811
if (intersect(HOST_ALIASES, conf.testParameter, True) or conf.level >= 5) and not any(_[0].upper() == HTTP_HEADER.HOST.upper() for _ in conf.httpHeaders):
1812
debugMsg = "setting the HTTP Host header to the target URL"
1813
logger.debug(debugMsg)
1814
conf.httpHeaders = [_ for _ in conf.httpHeaders if _[0] != HTTP_HEADER.HOST]
1815
conf.httpHeaders.append((HTTP_HEADER.HOST, getHostHeader(conf.url)))
1816
1817
if conf.url != originalUrl:
1818
kb.originalUrls[conf.url] = originalUrl
1819
1820
def escapeJsonValue(value):
1821
"""
1822
Escapes JSON value (used in payloads)
1823
1824
# Reference: https://stackoverflow.com/a/16652683
1825
1826
>>> "\\n" in escapeJsonValue("foo\\nbar")
1827
False
1828
>>> "\\\\t" in escapeJsonValue("foo\\tbar")
1829
True
1830
"""
1831
1832
retVal = ""
1833
1834
for char in value:
1835
if char < ' ' or char == '"':
1836
retVal += json.dumps(char)[1:-1]
1837
else:
1838
retVal += char
1839
1840
return retVal
1841
1842
def expandAsteriskForColumns(expression):
1843
"""
1844
If the user provided an asterisk rather than the column(s)
1845
name, sqlmap will retrieve the columns itself and reprocess
1846
the SQL query string (expression)
1847
"""
1848
1849
match = re.search(r"(?i)\ASELECT(\s+TOP\s+[\d]+)?\s+\*\s+FROM\s+(([`'\"][^`'\"]+[`'\"]|[\w.]+)+)(\s|\Z)", expression)
1850
1851
if match:
1852
infoMsg = "you did not provide the fields in your query. "
1853
infoMsg += "sqlmap will retrieve the column names itself"
1854
logger.info(infoMsg)
1855
1856
_ = match.group(2).replace("..", '.').replace(".dbo.", '.')
1857
db, conf.tbl = _.split('.', 1) if '.' in _ else (None, _)
1858
1859
if db is None:
1860
if expression != conf.sqlQuery:
1861
conf.db = db
1862
elif conf.db:
1863
expression = re.sub(r"([^\w])%s" % re.escape(conf.tbl), r"\g<1>%s.%s" % (conf.db, conf.tbl), expression)
1864
else:
1865
conf.db = db
1866
1867
conf.db = safeSQLIdentificatorNaming(conf.db)
1868
conf.tbl = safeSQLIdentificatorNaming(conf.tbl, True)
1869
1870
columnsDict = conf.dbmsHandler.getColumns(onlyColNames=True)
1871
1872
if columnsDict and conf.db in columnsDict and conf.tbl in columnsDict[conf.db]:
1873
columns = list(columnsDict[conf.db][conf.tbl].keys())
1874
columns.sort()
1875
columnsStr = ", ".join(column for column in columns)
1876
expression = expression.replace('*', columnsStr, 1)
1877
1878
infoMsg = "the query with expanded column name(s) is: "
1879
infoMsg += "%s" % expression
1880
logger.info(infoMsg)
1881
1882
return expression
1883
1884
def getLimitRange(count, plusOne=False):
1885
"""
1886
Returns range of values used in limit/offset constructs
1887
1888
>>> [_ for _ in getLimitRange(10)]
1889
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1890
"""
1891
1892
retVal = None
1893
count = int(count)
1894
limitStart, limitStop = 1, count
1895
reverse = False
1896
1897
if kb.dumpTable:
1898
if conf.limitStart and conf.limitStop and conf.limitStart > conf.limitStop:
1899
limitStop = conf.limitStart
1900
limitStart = conf.limitStop
1901
reverse = True
1902
else:
1903
if isinstance(conf.limitStop, int) and conf.limitStop > 0 and conf.limitStop < limitStop:
1904
limitStop = conf.limitStop
1905
1906
if isinstance(conf.limitStart, int) and conf.limitStart > 0 and conf.limitStart <= limitStop:
1907
limitStart = conf.limitStart
1908
1909
retVal = xrange(limitStart, limitStop + 1) if plusOne else xrange(limitStart - 1, limitStop)
1910
1911
if reverse:
1912
retVal = xrange(retVal[-1], retVal[0] - 1, -1)
1913
1914
return retVal
1915
1916
def parseUnionPage(page):
1917
"""
1918
Returns resulting items from UNION query inside provided page content
1919
1920
>>> parseUnionPage("%sfoo%s%sbar%s" % (kb.chars.start, kb.chars.stop, kb.chars.start, kb.chars.stop))
1921
['foo', 'bar']
1922
"""
1923
1924
if page is None:
1925
return None
1926
1927
if re.search(r"(?si)\A%s.*%s\Z" % (kb.chars.start, kb.chars.stop), page):
1928
if len(page) > LARGE_OUTPUT_THRESHOLD:
1929
warnMsg = "large output detected. This might take a while"
1930
logger.warning(warnMsg)
1931
1932
data = BigArray()
1933
keys = set()
1934
1935
for match in re.finditer(r"%s(.*?)%s" % (kb.chars.start, kb.chars.stop), page, re.DOTALL | re.IGNORECASE):
1936
entry = match.group(1)
1937
1938
if kb.chars.start in entry:
1939
entry = entry.split(kb.chars.start)[-1]
1940
1941
if kb.unionDuplicates:
1942
key = entry.lower()
1943
if key not in keys:
1944
keys.add(key)
1945
else:
1946
continue
1947
1948
entry = entry.split(kb.chars.delimiter)
1949
1950
if conf.hexConvert:
1951
entry = applyFunctionRecursively(entry, decodeDbmsHexValue)
1952
1953
if kb.safeCharEncode:
1954
entry = applyFunctionRecursively(entry, safecharencode)
1955
1956
data.append(entry[0] if len(entry) == 1 else entry)
1957
else:
1958
data = page
1959
1960
if len(data) == 1 and isinstance(data[0], six.string_types):
1961
data = data[0]
1962
1963
return data
1964
1965
def parseFilePaths(page):
1966
"""
1967
Detects (possible) absolute system paths inside the provided page content
1968
1969
>>> _ = "/var/www/html/index.php"; parseFilePaths("<html>Error occurred at line 207 of: %s<br>Please contact your administrator</html>" % _); _ in kb.absFilePaths
1970
True
1971
"""
1972
1973
if page:
1974
for regex in FILE_PATH_REGEXES:
1975
for match in re.finditer(regex, page):
1976
absFilePath = match.group("result").strip()
1977
page = page.replace(absFilePath, "")
1978
1979
if isWindowsDriveLetterPath(absFilePath):
1980
absFilePath = posixToNtSlashes(absFilePath)
1981
1982
if absFilePath not in kb.absFilePaths:
1983
kb.absFilePaths.add(absFilePath)
1984
1985
def getLocalIP():
1986
"""
1987
Get local IP address (exposed to the remote/target)
1988
"""
1989
1990
retVal = None
1991
1992
try:
1993
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1994
s.connect((conf.hostname, conf.port))
1995
retVal, _ = s.getsockname()
1996
s.close()
1997
except:
1998
debugMsg = "there was an error in opening socket "
1999
debugMsg += "connection toward '%s'" % conf.hostname
2000
logger.debug(debugMsg)
2001
2002
return retVal
2003
2004
def getRemoteIP():
2005
"""
2006
Get remote/target IP address
2007
2008
>>> pushValue(conf.hostname)
2009
>>> conf.hostname = "localhost"
2010
>>> getRemoteIP() == "127.0.0.1"
2011
True
2012
>>> conf.hostname = popValue()
2013
"""
2014
2015
retVal = None
2016
2017
try:
2018
retVal = socket.gethostbyname(conf.hostname)
2019
except socket.gaierror:
2020
errMsg = "address resolution problem "
2021
errMsg += "occurred for hostname '%s'" % conf.hostname
2022
singleTimeLogMessage(errMsg, logging.ERROR)
2023
2024
return retVal
2025
2026
def getFileType(filePath):
2027
"""
2028
Returns "magic" file type for given file path
2029
2030
>>> getFileType(__file__)
2031
'text'
2032
>>> getFileType(sys.executable)
2033
'binary'
2034
"""
2035
2036
try:
2037
desc = magic.from_file(filePath) or magic.MAGIC_UNKNOWN_FILETYPE
2038
except:
2039
desc = magic.MAGIC_UNKNOWN_FILETYPE
2040
finally:
2041
desc = getText(desc)
2042
2043
if desc == getText(magic.MAGIC_UNKNOWN_FILETYPE):
2044
content = openFile(filePath, "rb", encoding=None).read()
2045
2046
try:
2047
content.decode()
2048
except:
2049
pass
2050
else:
2051
desc = "ascii"
2052
2053
return "text" if any(_ in desc.lower() for _ in ("ascii", "text")) else "binary"
2054
2055
def getCharset(charsetType=None):
2056
"""
2057
Returns list with integers representing characters of a given
2058
charset type appropriate for inference techniques
2059
2060
>>> getCharset(CHARSET_TYPE.BINARY)
2061
[0, 1, 47, 48, 49]
2062
"""
2063
2064
asciiTbl = []
2065
2066
if charsetType is None:
2067
asciiTbl.extend(xrange(0, 128))
2068
2069
# Binary
2070
elif charsetType == CHARSET_TYPE.BINARY:
2071
asciiTbl.extend((0, 1))
2072
asciiTbl.extend(xrange(47, 50))
2073
2074
# Digits
2075
elif charsetType == CHARSET_TYPE.DIGITS:
2076
asciiTbl.extend(xrange(0, 10))
2077
asciiTbl.extend(xrange(47, 58))
2078
2079
# Hexadecimal
2080
elif charsetType == CHARSET_TYPE.HEXADECIMAL:
2081
asciiTbl.extend((0, 1))
2082
asciiTbl.extend(xrange(47, 58))
2083
asciiTbl.extend(xrange(64, 71))
2084
asciiTbl.extend((87, 88)) # X
2085
asciiTbl.extend(xrange(96, 103))
2086
asciiTbl.extend((119, 120)) # x
2087
2088
# Characters
2089
elif charsetType == CHARSET_TYPE.ALPHA:
2090
asciiTbl.extend((0, 1))
2091
asciiTbl.extend(xrange(64, 91))
2092
asciiTbl.extend(xrange(96, 123))
2093
2094
# Characters and digits
2095
elif charsetType == CHARSET_TYPE.ALPHANUM:
2096
asciiTbl.extend((0, 1))
2097
asciiTbl.extend(xrange(47, 58))
2098
asciiTbl.extend(xrange(64, 91))
2099
asciiTbl.extend(xrange(96, 123))
2100
2101
return asciiTbl
2102
2103
def directoryPath(filepath):
2104
"""
2105
Returns directory path for a given filepath
2106
2107
>>> directoryPath('/var/log/apache.log')
2108
'/var/log'
2109
>>> directoryPath('/var/log')
2110
'/var/log'
2111
"""
2112
2113
retVal = filepath
2114
2115
if filepath and os.path.splitext(filepath)[-1]:
2116
retVal = ntpath.dirname(filepath) if isWindowsDriveLetterPath(filepath) else posixpath.dirname(filepath)
2117
2118
return retVal
2119
2120
def normalizePath(filepath):
2121
"""
2122
Returns normalized string representation of a given filepath
2123
2124
>>> normalizePath('//var///log/apache.log')
2125
'/var/log/apache.log'
2126
"""
2127
2128
retVal = filepath
2129
2130
if retVal:
2131
retVal = retVal.strip("\r\n")
2132
retVal = ntpath.normpath(retVal) if isWindowsDriveLetterPath(retVal) else re.sub(r"\A/{2,}", "/", posixpath.normpath(retVal))
2133
2134
return retVal
2135
2136
def safeFilepathEncode(filepath):
2137
"""
2138
Returns filepath in (ASCII) format acceptable for OS handling (e.g. reading)
2139
2140
>>> 'sqlmap' in safeFilepathEncode(paths.SQLMAP_HOME_PATH)
2141
True
2142
"""
2143
2144
retVal = filepath
2145
2146
if filepath and six.PY2 and isinstance(filepath, six.text_type):
2147
retVal = getBytes(filepath, sys.getfilesystemencoding() or UNICODE_ENCODING)
2148
2149
return retVal
2150
2151
2152
def safeExpandUser(filepath):
2153
"""
2154
Patch for a Python Issue18171 (http://bugs.python.org/issue18171)
2155
2156
>>> os.path.basename(__file__) in safeExpandUser(__file__)
2157
True
2158
"""
2159
2160
retVal = filepath
2161
2162
try:
2163
retVal = os.path.expanduser(filepath)
2164
except UnicodeError:
2165
_ = locale.getdefaultlocale()
2166
encoding = _[1] if _ and len(_) > 1 else UNICODE_ENCODING
2167
retVal = getUnicode(os.path.expanduser(filepath.encode(encoding)), encoding=encoding)
2168
2169
return retVal
2170
2171
def safeStringFormat(format_, params):
2172
"""
2173
Avoids problems with inappropriate string format strings
2174
2175
>>> safeStringFormat('SELECT foo FROM %s LIMIT %d', ('bar', '1'))
2176
'SELECT foo FROM bar LIMIT 1'
2177
>>> safeStringFormat("SELECT foo FROM %s WHERE name LIKE '%susan%' LIMIT %d", ('bar', '1'))
2178
"SELECT foo FROM bar WHERE name LIKE '%susan%' LIMIT 1"
2179
"""
2180
2181
if format_.count(PAYLOAD_DELIMITER) == 2:
2182
_ = format_.split(PAYLOAD_DELIMITER)
2183
_[1] = re.sub(r"(\A|[^A-Za-z0-9])(%d)([^A-Za-z0-9]|\Z)", r"\g<1>%s\g<3>", _[1])
2184
retVal = PAYLOAD_DELIMITER.join(_)
2185
else:
2186
retVal = re.sub(r"(\A|[^A-Za-z0-9])(%d)([^A-Za-z0-9]|\Z)", r"\g<1>%s\g<3>", format_)
2187
2188
if isinstance(params, six.string_types):
2189
retVal = retVal.replace("%s", params, 1)
2190
elif not isListLike(params):
2191
retVal = retVal.replace("%s", getUnicode(params), 1)
2192
else:
2193
start, end = 0, len(retVal)
2194
match = re.search(r"%s(.+)%s" % (PAYLOAD_DELIMITER, PAYLOAD_DELIMITER), retVal)
2195
if match and PAYLOAD_DELIMITER not in match.group(1):
2196
start, end = match.start(), match.end()
2197
if retVal.count("%s", start, end) == len(params):
2198
for param in params:
2199
index = retVal.find("%s", start)
2200
if isinstance(param, six.string_types):
2201
param = param.replace('%', PARAMETER_PERCENTAGE_MARKER)
2202
retVal = retVal[:index] + getUnicode(param) + retVal[index + 2:]
2203
else:
2204
if any('%s' in _ for _ in conf.parameters.values()):
2205
parts = format_.split(' ')
2206
for i in xrange(len(parts)):
2207
if PAYLOAD_DELIMITER in parts[i]:
2208
parts[i] = parts[i].replace(PAYLOAD_DELIMITER, "")
2209
parts[i] = "%s%s" % (parts[i], PAYLOAD_DELIMITER)
2210
break
2211
format_ = ' '.join(parts)
2212
2213
count = 0
2214
while True:
2215
match = re.search(r"(\A|[^A-Za-z0-9])(%s)([^A-Za-z0-9]|\Z)", retVal)
2216
if match:
2217
try:
2218
retVal = re.sub(r"(\A|[^A-Za-z0-9])(%s)([^A-Za-z0-9]|\Z)", r"\g<1>%s\g<3>" % params[count % len(params)], retVal, 1)
2219
except re.error:
2220
retVal = retVal.replace(match.group(0), match.group(0) % params[count % len(params)], 1)
2221
count += 1
2222
else:
2223
break
2224
2225
if count > len(params) and count % len(params):
2226
warnMsg = "wrong number of parameters during string formatting. "
2227
warnMsg += "Please report by e-mail content \"%r | %r | %r\" to '%s'" % (format_, params, retVal, DEV_EMAIL_ADDRESS)
2228
raise SqlmapValueException(warnMsg)
2229
2230
retVal = getText(retVal).replace(PARAMETER_PERCENTAGE_MARKER, '%')
2231
2232
return retVal
2233
2234
def getFilteredPageContent(page, onlyText=True, split=" "):
2235
"""
2236
Returns filtered page content without script, style and/or comments
2237
or all HTML tags
2238
2239
>>> getFilteredPageContent(u'<html><title>foobar</title><body>test</body></html>') == "foobar test"
2240
True
2241
"""
2242
2243
retVal = page
2244
2245
# only if the page's charset has been successfully identified
2246
if isinstance(page, six.text_type):
2247
retVal = re.sub(r"(?si)<script.+?</script>|<!--.+?-->|<style.+?</style>%s" % (r"|<[^>]+>|\t|\n|\r" if onlyText else ""), split, page)
2248
retVal = re.sub(r"%s{2,}" % split, split, retVal)
2249
retVal = htmlUnescape(retVal.strip().strip(split))
2250
2251
return retVal
2252
2253
def getPageWordSet(page):
2254
"""
2255
Returns word set used in page content
2256
2257
>>> sorted(getPageWordSet(u'<html><title>foobar</title><body>test</body></html>')) == [u'foobar', u'test']
2258
True
2259
"""
2260
2261
retVal = set()
2262
2263
# only if the page's charset has been successfully identified
2264
if isinstance(page, six.string_types):
2265
retVal = set(_.group(0) for _ in re.finditer(r"\w+", getFilteredPageContent(page)))
2266
2267
return retVal
2268
2269
def showStaticWords(firstPage, secondPage, minLength=3):
2270
"""
2271
Prints words appearing in two different response pages
2272
2273
>>> showStaticWords("this is a test", "this is another test")
2274
['this']
2275
"""
2276
2277
infoMsg = "finding static words in longest matching part of dynamic page content"
2278
logger.info(infoMsg)
2279
2280
firstPage = getFilteredPageContent(firstPage)
2281
secondPage = getFilteredPageContent(secondPage)
2282
2283
infoMsg = "static words: "
2284
2285
if firstPage and secondPage:
2286
match = SequenceMatcher(None, firstPage, secondPage).find_longest_match(0, len(firstPage), 0, len(secondPage))
2287
commonText = firstPage[match[0]:match[0] + match[2]]
2288
commonWords = getPageWordSet(commonText)
2289
else:
2290
commonWords = None
2291
2292
if commonWords:
2293
commonWords = [_ for _ in commonWords if len(_) >= minLength]
2294
commonWords.sort(key=functools.cmp_to_key(lambda a, b: cmp(a.lower(), b.lower())))
2295
2296
for word in commonWords:
2297
infoMsg += "'%s', " % word
2298
2299
infoMsg = infoMsg.rstrip(", ")
2300
else:
2301
infoMsg += "None"
2302
2303
logger.info(infoMsg)
2304
2305
return commonWords
2306
2307
def isWindowsDriveLetterPath(filepath):
2308
"""
2309
Returns True if given filepath starts with a Windows drive letter
2310
2311
>>> isWindowsDriveLetterPath('C:\\boot.ini')
2312
True
2313
>>> isWindowsDriveLetterPath('/var/log/apache.log')
2314
False
2315
"""
2316
2317
return re.search(r"\A[\w]\:", filepath) is not None
2318
2319
def posixToNtSlashes(filepath):
2320
"""
2321
Replaces all occurrences of Posix slashes in provided
2322
filepath with NT backslashes
2323
2324
>>> posixToNtSlashes('C:/Windows')
2325
'C:\\\\Windows'
2326
"""
2327
2328
return filepath.replace('/', '\\') if filepath else filepath
2329
2330
def ntToPosixSlashes(filepath):
2331
"""
2332
Replaces all occurrences of NT backslashes in provided
2333
filepath with Posix slashes
2334
2335
>>> ntToPosixSlashes(r'C:\\Windows')
2336
'C:/Windows'
2337
"""
2338
2339
return filepath.replace('\\', '/') if filepath else filepath
2340
2341
def isHexEncodedString(subject):
2342
"""
2343
Checks if the provided string is hex encoded
2344
2345
>>> isHexEncodedString('DEADBEEF')
2346
True
2347
>>> isHexEncodedString('test')
2348
False
2349
"""
2350
2351
return re.match(r"\A[0-9a-fA-Fx]+\Z", subject) is not None
2352
2353
@cachedmethod
2354
def getConsoleWidth(default=80):
2355
"""
2356
Returns console width
2357
2358
>>> any((getConsoleWidth(), True))
2359
True
2360
"""
2361
2362
width = None
2363
2364
if os.getenv("COLUMNS", "").isdigit():
2365
width = int(os.getenv("COLUMNS"))
2366
else:
2367
try:
2368
output = shellExec("stty size")
2369
match = re.search(r"\A\d+ (\d+)", output)
2370
2371
if match:
2372
width = int(match.group(1))
2373
except (OSError, MemoryError):
2374
pass
2375
2376
if width is None:
2377
try:
2378
import curses
2379
2380
stdscr = curses.initscr()
2381
_, width = stdscr.getmaxyx()
2382
curses.endwin()
2383
except:
2384
pass
2385
2386
return width or default
2387
2388
def shellExec(cmd):
2389
"""
2390
Executes arbitrary shell command
2391
2392
>>> shellExec('echo 1').strip() == '1'
2393
True
2394
"""
2395
2396
retVal = ""
2397
2398
try:
2399
retVal = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] or ""
2400
except Exception as ex:
2401
retVal = getSafeExString(ex)
2402
finally:
2403
retVal = getText(retVal)
2404
2405
return retVal
2406
2407
def clearConsoleLine(forceOutput=False):
2408
"""
2409
Clears current console line
2410
"""
2411
2412
if IS_TTY:
2413
dataToStdout("\r%s\r" % (" " * (getConsoleWidth() - 1)), forceOutput)
2414
2415
kb.prependFlag = False
2416
2417
def parseXmlFile(xmlFile, handler):
2418
"""
2419
Parses XML file by a given handler
2420
"""
2421
2422
try:
2423
with contextlib.closing(io.StringIO(readCachedFileContent(xmlFile))) as stream:
2424
parse(stream, handler)
2425
except (SAXParseException, UnicodeError) as ex:
2426
errMsg = "something appears to be wrong with "
2427
errMsg += "the file '%s' ('%s'). Please make " % (xmlFile, getSafeExString(ex))
2428
errMsg += "sure that you haven't made any changes to it"
2429
raise SqlmapInstallationException(errMsg)
2430
2431
def getSQLSnippet(dbms, sfile, **variables):
2432
"""
2433
Returns content of SQL snippet located inside 'procs/' directory
2434
2435
>>> 'RECONFIGURE' in getSQLSnippet(DBMS.MSSQL, "activate_sp_oacreate")
2436
True
2437
"""
2438
2439
if sfile.endswith('.sql') and os.path.exists(sfile):
2440
filename = sfile
2441
elif not sfile.endswith('.sql') and os.path.exists("%s.sql" % sfile):
2442
filename = "%s.sql" % sfile
2443
else:
2444
filename = os.path.join(paths.SQLMAP_PROCS_PATH, DBMS_DIRECTORY_DICT[dbms], sfile if sfile.endswith('.sql') else "%s.sql" % sfile)
2445
checkFile(filename)
2446
2447
retVal = readCachedFileContent(filename)
2448
retVal = re.sub(r"#.+", "", retVal)
2449
retVal = re.sub(r";\s+", "; ", retVal).strip("\r\n")
2450
2451
for _ in variables:
2452
retVal = re.sub(r"%%%s%%" % _, variables[_].replace('\\', r'\\'), retVal)
2453
2454
for _ in re.findall(r"%RANDSTR\d+%", retVal, re.I):
2455
retVal = retVal.replace(_, randomStr())
2456
2457
for _ in re.findall(r"%RANDINT\d+%", retVal, re.I):
2458
retVal = retVal.replace(_, randomInt())
2459
2460
variables = re.findall(r"(?<!\bLIKE ')%(\w+)%", retVal, re.I)
2461
2462
if variables:
2463
errMsg = "unresolved variable%s '%s' in SQL file '%s'" % ("s" if len(variables) > 1 else "", ", ".join(variables), sfile)
2464
logger.error(errMsg)
2465
2466
msg = "do you want to provide the substitution values? [y/N] "
2467
2468
if readInput(msg, default='N', boolean=True):
2469
for var in variables:
2470
msg = "insert value for variable '%s': " % var
2471
val = readInput(msg, default="")
2472
retVal = retVal.replace(r"%%%s%%" % var, val)
2473
2474
return retVal
2475
2476
def readCachedFileContent(filename, mode='r'):
2477
"""
2478
Cached reading of file content (avoiding multiple same file reading)
2479
2480
>>> "readCachedFileContent" in readCachedFileContent(__file__)
2481
True
2482
"""
2483
2484
if filename not in kb.cache.content:
2485
with kb.locks.cache:
2486
if filename not in kb.cache.content:
2487
checkFile(filename)
2488
try:
2489
with openFile(filename, mode) as f:
2490
kb.cache.content[filename] = f.read()
2491
except (IOError, OSError, MemoryError) as ex:
2492
errMsg = "something went wrong while trying "
2493
errMsg += "to read the content of file '%s' ('%s')" % (filename, getSafeExString(ex))
2494
raise SqlmapSystemException(errMsg)
2495
2496
return kb.cache.content[filename]
2497
2498
def average(values):
2499
"""
2500
Computes the arithmetic mean of a list of numbers.
2501
2502
>>> "%.1f" % average([0.9, 0.9, 0.9, 1.0, 0.8, 0.9])
2503
'0.9'
2504
"""
2505
2506
return (1.0 * sum(values) / len(values)) if values else None
2507
2508
@cachedmethod
2509
def stdev(values):
2510
"""
2511
Computes standard deviation of a list of numbers.
2512
2513
# Reference: http://www.goldb.org/corestats.html
2514
2515
>>> "%.3f" % stdev([0.9, 0.9, 0.9, 1.0, 0.8, 0.9])
2516
'0.063'
2517
"""
2518
2519
if not values or len(values) < 2:
2520
return None
2521
else:
2522
avg = average(values)
2523
_ = 1.0 * sum(pow((_ or 0) - avg, 2) for _ in values)
2524
return sqrt(_ / (len(values) - 1))
2525
2526
def calculateDeltaSeconds(start):
2527
"""
2528
Returns elapsed time from start till now
2529
2530
>>> calculateDeltaSeconds(0) > 1151721660
2531
True
2532
"""
2533
2534
return time.time() - start
2535
2536
def initCommonOutputs():
2537
"""
2538
Initializes dictionary containing common output values used by "good samaritan" feature
2539
2540
>>> initCommonOutputs(); "information_schema" in kb.commonOutputs["Databases"]
2541
True
2542
"""
2543
2544
kb.commonOutputs = {}
2545
key = None
2546
2547
with openFile(paths.COMMON_OUTPUTS, 'r') as f:
2548
for line in f:
2549
if line.find('#') != -1:
2550
line = line[:line.find('#')]
2551
2552
line = line.strip()
2553
2554
if len(line) > 1:
2555
if line.startswith('[') and line.endswith(']'):
2556
key = line[1:-1]
2557
elif key:
2558
if key not in kb.commonOutputs:
2559
kb.commonOutputs[key] = set()
2560
2561
if line not in kb.commonOutputs[key]:
2562
kb.commonOutputs[key].add(line)
2563
2564
def getFileItems(filename, commentPrefix='#', unicoded=True, lowercase=False, unique=False):
2565
"""
2566
Returns newline delimited items contained inside file
2567
2568
>>> "SELECT" in getFileItems(paths.SQL_KEYWORDS)
2569
True
2570
"""
2571
2572
retVal = list() if not unique else OrderedDict()
2573
2574
if filename:
2575
filename = filename.strip('"\'')
2576
2577
checkFile(filename)
2578
2579
try:
2580
with openFile(filename, 'r', errors="ignore") if unicoded else open(filename, 'r') as f:
2581
for line in f:
2582
if commentPrefix:
2583
if line.find(commentPrefix) != -1:
2584
line = line[:line.find(commentPrefix)]
2585
2586
line = line.strip()
2587
2588
if line:
2589
if lowercase:
2590
line = line.lower()
2591
2592
if unique and line in retVal:
2593
continue
2594
2595
if unique:
2596
retVal[line] = True
2597
else:
2598
retVal.append(line)
2599
except (IOError, OSError, MemoryError) as ex:
2600
errMsg = "something went wrong while trying "
2601
errMsg += "to read the content of file '%s' ('%s')" % (filename, getSafeExString(ex))
2602
raise SqlmapSystemException(errMsg)
2603
2604
return retVal if not unique else list(retVal.keys())
2605
2606
def goGoodSamaritan(prevValue, originalCharset):
2607
"""
2608
Function for retrieving parameters needed for common prediction (good
2609
samaritan) feature.
2610
2611
prevValue: retrieved query output so far (e.g. 'i').
2612
2613
Returns commonValue if there is a complete single match (in kb.partRun
2614
of txt/common-outputs.txt under kb.partRun) regarding parameter
2615
prevValue. If there is no single value match, but multiple, commonCharset is
2616
returned containing more probable characters (retrieved from matched
2617
values in txt/common-outputs.txt) together with the rest of charset as
2618
otherCharset.
2619
"""
2620
2621
if kb.commonOutputs is None:
2622
initCommonOutputs()
2623
2624
predictionSet = set()
2625
commonValue = None
2626
commonPattern = None
2627
countCommonValue = 0
2628
2629
# If the header (e.g. Databases) we are looking for has common
2630
# outputs defined
2631
if kb.partRun in kb.commonOutputs:
2632
commonPartOutputs = kb.commonOutputs[kb.partRun]
2633
commonPattern = commonFinderOnly(prevValue, commonPartOutputs)
2634
2635
# If the longest common prefix is the same as previous value then
2636
# do not consider it
2637
if commonPattern and commonPattern == prevValue:
2638
commonPattern = None
2639
2640
# For each common output
2641
for item in commonPartOutputs:
2642
# Check if the common output (item) starts with prevValue
2643
# where prevValue is the enumerated character(s) so far
2644
if item.startswith(prevValue):
2645
commonValue = item
2646
countCommonValue += 1
2647
2648
if len(item) > len(prevValue):
2649
char = item[len(prevValue)]
2650
predictionSet.add(char)
2651
2652
# Reset single value if there is more than one possible common
2653
# output
2654
if countCommonValue > 1:
2655
commonValue = None
2656
2657
commonCharset = []
2658
otherCharset = []
2659
2660
# Split the original charset into common chars (commonCharset)
2661
# and other chars (otherCharset)
2662
for ordChar in originalCharset:
2663
if _unichr(ordChar) not in predictionSet:
2664
otherCharset.append(ordChar)
2665
else:
2666
commonCharset.append(ordChar)
2667
2668
commonCharset.sort()
2669
2670
return commonValue, commonPattern, commonCharset, originalCharset
2671
else:
2672
return None, None, None, originalCharset
2673
2674
def getPartRun(alias=True):
2675
"""
2676
Goes through call stack and finds constructs matching
2677
conf.dbmsHandler.*. Returns it or its alias used in 'txt/common-outputs.txt'
2678
"""
2679
2680
retVal = None
2681
commonPartsDict = optDict["Enumeration"]
2682
2683
try:
2684
stack = [item[4][0] if isinstance(item[4], list) else '' for item in inspect.stack()]
2685
2686
# Goes backwards through the stack to find the conf.dbmsHandler method
2687
# calling this function
2688
for i in xrange(0, len(stack) - 1):
2689
for regex in (r"self\.(get[^(]+)\(\)", r"conf\.dbmsHandler\.([^(]+)\(\)"):
2690
match = re.search(regex, stack[i])
2691
2692
if match:
2693
# This is the calling conf.dbmsHandler or self method
2694
# (e.g. 'getDbms')
2695
retVal = match.groups()[0]
2696
break
2697
2698
if retVal is not None:
2699
break
2700
2701
# Reference: http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-06/2267.html
2702
except TypeError:
2703
pass
2704
2705
# Return the INI tag to consider for common outputs (e.g. 'Databases')
2706
if alias:
2707
return commonPartsDict[retVal][1] if isinstance(commonPartsDict.get(retVal), tuple) else retVal
2708
else:
2709
return retVal
2710
2711
def longestCommonPrefix(*sequences):
2712
"""
2713
Returns longest common prefix occuring in given sequences
2714
2715
# Reference: http://boredzo.org/blog/archives/2007-01-06/longest-common-prefix-in-python-2
2716
2717
>>> longestCommonPrefix('foobar', 'fobar')
2718
'fo'
2719
"""
2720
2721
if len(sequences) == 1:
2722
return sequences[0]
2723
2724
sequences = [pair[1] for pair in sorted((len(fi), fi) for fi in sequences)]
2725
2726
if not sequences:
2727
return None
2728
2729
for i, comparison_ch in enumerate(sequences[0]):
2730
for fi in sequences[1:]:
2731
ch = fi[i]
2732
2733
if ch != comparison_ch:
2734
return fi[:i]
2735
2736
return sequences[0]
2737
2738
def commonFinderOnly(initial, sequence):
2739
"""
2740
Returns parts of sequence which start with the given initial string
2741
2742
>>> commonFinderOnly("abcd", ["abcdefg", "foobar", "abcde"])
2743
'abcde'
2744
"""
2745
2746
return longestCommonPrefix(*[_ for _ in sequence if _.startswith(initial)])
2747
2748
def pushValue(value):
2749
"""
2750
Push value to the stack (thread dependent)
2751
"""
2752
2753
exception = None
2754
success = False
2755
2756
for i in xrange(PUSH_VALUE_EXCEPTION_RETRY_COUNT):
2757
try:
2758
getCurrentThreadData().valueStack.append(copy.deepcopy(value))
2759
success = True
2760
break
2761
except Exception as ex:
2762
exception = ex
2763
2764
if not success:
2765
getCurrentThreadData().valueStack.append(None)
2766
2767
if exception:
2768
raise exception
2769
2770
def popValue():
2771
"""
2772
Pop value from the stack (thread dependent)
2773
2774
>>> pushValue('foobar')
2775
>>> popValue()
2776
'foobar'
2777
"""
2778
2779
retVal = None
2780
2781
try:
2782
retVal = getCurrentThreadData().valueStack.pop()
2783
except IndexError:
2784
pass
2785
2786
return retVal
2787
2788
def wasLastResponseDBMSError():
2789
"""
2790
Returns True if the last web request resulted in a (recognized) DBMS error page
2791
"""
2792
2793
threadData = getCurrentThreadData()
2794
return threadData.lastErrorPage and threadData.lastErrorPage[0] == threadData.lastRequestUID
2795
2796
def wasLastResponseHTTPError():
2797
"""
2798
Returns True if the last web request resulted in an erroneous HTTP code (like 500)
2799
"""
2800
2801
threadData = getCurrentThreadData()
2802
return threadData.lastHTTPError and threadData.lastHTTPError[0] == threadData.lastRequestUID
2803
2804
def wasLastResponseDelayed():
2805
"""
2806
Returns True if the last web request resulted in a time-delay
2807
"""
2808
2809
# 99.9999999997440% of all non time-based SQL injection affected
2810
# response times should be inside +-7*stdev([normal response times])
2811
# Math reference: http://www.answers.com/topic/standard-deviation
2812
2813
deviation = stdev(kb.responseTimes.get(kb.responseTimeMode, []))
2814
threadData = getCurrentThreadData()
2815
2816
if deviation and not conf.direct and not conf.disableStats:
2817
if len(kb.responseTimes[kb.responseTimeMode]) < MIN_TIME_RESPONSES:
2818
warnMsg = "time-based standard deviation method used on a model "
2819
warnMsg += "with less than %d response times" % MIN_TIME_RESPONSES
2820
logger.warning(warnMsg)
2821
2822
lowerStdLimit = average(kb.responseTimes[kb.responseTimeMode]) + TIME_STDEV_COEFF * deviation
2823
retVal = (threadData.lastQueryDuration >= max(MIN_VALID_DELAYED_RESPONSE, lowerStdLimit))
2824
2825
if not kb.testMode and retVal:
2826
if kb.adjustTimeDelay is None:
2827
msg = "do you want sqlmap to try to optimize value(s) "
2828
msg += "for DBMS delay responses (option '--time-sec')? [Y/n] "
2829
2830
kb.adjustTimeDelay = ADJUST_TIME_DELAY.DISABLE if not readInput(msg, default='Y', boolean=True) else ADJUST_TIME_DELAY.YES
2831
if kb.adjustTimeDelay is ADJUST_TIME_DELAY.YES:
2832
adjustTimeDelay(threadData.lastQueryDuration, lowerStdLimit)
2833
2834
return retVal
2835
else:
2836
delta = threadData.lastQueryDuration - conf.timeSec
2837
if Backend.getIdentifiedDbms() in (DBMS.MYSQL,): # MySQL's SLEEP(X) lasts 0.05 seconds shorter on average
2838
delta += 0.05
2839
return delta >= 0
2840
2841
def adjustTimeDelay(lastQueryDuration, lowerStdLimit):
2842
"""
2843
Provides tip for adjusting time delay in time-based data retrieval
2844
"""
2845
2846
candidate = (1 if not isHeavyQueryBased() else 2) + int(round(lowerStdLimit))
2847
2848
kb.delayCandidates = [candidate] + kb.delayCandidates[:-1]
2849
2850
if all((_ == candidate for _ in kb.delayCandidates)) and candidate < conf.timeSec:
2851
if lastQueryDuration / (1.0 * conf.timeSec / candidate) > MIN_VALID_DELAYED_RESPONSE: # Note: to prevent problems with fast responses for heavy-queries like RANDOMBLOB
2852
conf.timeSec = candidate
2853
2854
infoMsg = "adjusting time delay to "
2855
infoMsg += "%d second%s due to good response times" % (conf.timeSec, 's' if conf.timeSec > 1 else '')
2856
logger.info(infoMsg)
2857
2858
def getLastRequestHTTPError():
2859
"""
2860
Returns last HTTP error code
2861
"""
2862
2863
threadData = getCurrentThreadData()
2864
return threadData.lastHTTPError[1] if threadData.lastHTTPError else None
2865
2866
def extractErrorMessage(page):
2867
"""
2868
Returns reported error message from page if it founds one
2869
2870
>>> getText(extractErrorMessage(u'<html><title>Test</title>\\n<b>Warning</b>: oci_parse() [function.oci-parse]: ORA-01756: quoted string not properly terminated<br><p>Only a test page</p></html>') )
2871
'oci_parse() [function.oci-parse]: ORA-01756: quoted string not properly terminated'
2872
>>> extractErrorMessage('Warning: This is only a dummy foobar test') is None
2873
True
2874
"""
2875
2876
retVal = None
2877
2878
if isinstance(page, six.string_types):
2879
if wasLastResponseDBMSError():
2880
page = re.sub(r"<[^>]+>", "", page)
2881
2882
for regex in ERROR_PARSING_REGEXES:
2883
match = re.search(regex, page, re.IGNORECASE)
2884
2885
if match:
2886
candidate = htmlUnescape(match.group("result")).replace("<br>", "\n").strip()
2887
if candidate and (1.0 * len(re.findall(r"[^A-Za-z,. ]", candidate)) / len(candidate) > MIN_ERROR_PARSING_NON_WRITING_RATIO):
2888
retVal = candidate
2889
break
2890
2891
if not retVal and wasLastResponseDBMSError():
2892
match = re.search(r"[^\n]*SQL[^\n:]*:[^\n]*", page, re.IGNORECASE)
2893
2894
if match:
2895
retVal = match.group(0)
2896
2897
return retVal
2898
2899
def findLocalPort(ports):
2900
"""
2901
Find the first opened localhost port from a given list of ports (e.g. for Tor port checks)
2902
"""
2903
2904
retVal = None
2905
2906
for port in ports:
2907
try:
2908
try:
2909
s = socket._orig_socket(socket.AF_INET, socket.SOCK_STREAM)
2910
except AttributeError:
2911
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
2912
s.connect((LOCALHOST, port))
2913
retVal = port
2914
break
2915
except socket.error:
2916
pass
2917
finally:
2918
try:
2919
s.close()
2920
except socket.error:
2921
pass
2922
2923
return retVal
2924
2925
def findMultipartPostBoundary(post):
2926
"""
2927
Finds value for a boundary parameter in given multipart POST body
2928
2929
>>> findMultipartPostBoundary("-----------------------------9051914041544843365972754266\\nContent-Disposition: form-data; name=text\\n\\ndefault")
2930
'9051914041544843365972754266'
2931
"""
2932
2933
retVal = None
2934
counts = {}
2935
2936
for match in re.finditer(r"(?m)^--(.+?)(--)?$", post or ""):
2937
boundary = match.group(1).strip().strip('-')
2938
counts[boundary] = counts.get(boundary, 0) + 1
2939
2940
if counts:
2941
sorted_boundaries = sorted(counts.items(), key=lambda x: x[1], reverse=True)
2942
retVal = sorted_boundaries[0][0]
2943
2944
return retVal
2945
2946
def urldecode(value, encoding=None, unsafe="%%?&=;+%s" % CUSTOM_INJECTION_MARK_CHAR, convall=False, spaceplus=True):
2947
"""
2948
URL decodes given value
2949
2950
>>> urldecode('AND%201%3E%282%2B3%29%23', convall=True) == 'AND 1>(2+3)#'
2951
True
2952
>>> urldecode('AND%201%3E%282%2B3%29%23', convall=False) == 'AND 1>(2%2B3)#'
2953
True
2954
>>> urldecode(b'AND%201%3E%282%2B3%29%23', convall=False) == 'AND 1>(2%2B3)#'
2955
True
2956
"""
2957
2958
result = value
2959
2960
if value:
2961
value = getUnicode(value)
2962
2963
if convall:
2964
result = _urllib.parse.unquote_plus(value) if spaceplus else _urllib.parse.unquote(value)
2965
else:
2966
result = value
2967
charset = set(string.printable) - set(unsafe)
2968
2969
def _(match):
2970
char = decodeHex(match.group(1), binary=False)
2971
return char if char in charset else match.group(0)
2972
2973
if spaceplus:
2974
result = result.replace('+', ' ') # plus sign has a special meaning in URL encoded data (hence the usage of _urllib.parse.unquote_plus in convall case)
2975
2976
result = re.sub(r"%([0-9a-fA-F]{2})", _, result or "")
2977
2978
result = getUnicode(result, encoding or UNICODE_ENCODING)
2979
2980
return result
2981
2982
def urlencode(value, safe="%&=-_", convall=False, limit=False, spaceplus=False):
2983
"""
2984
URL encodes given value
2985
2986
>>> urlencode('AND 1>(2+3)#')
2987
'AND%201%3E%282%2B3%29%23'
2988
>>> urlencode("AND COUNT(SELECT name FROM users WHERE name LIKE '%DBA%')>0")
2989
'AND%20COUNT%28SELECT%20name%20FROM%20users%20WHERE%20name%20LIKE%20%27%25DBA%25%27%29%3E0'
2990
>>> urlencode("AND COUNT(SELECT name FROM users WHERE name LIKE '%_SYSTEM%')>0")
2991
'AND%20COUNT%28SELECT%20name%20FROM%20users%20WHERE%20name%20LIKE%20%27%25_SYSTEM%25%27%29%3E0'
2992
>>> urlencode("SELECT NAME FROM TABLE WHERE VALUE LIKE '%SOME%BEGIN%'")
2993
'SELECT%20NAME%20FROM%20TABLE%20WHERE%20VALUE%20LIKE%20%27%25SOME%25BEGIN%25%27'
2994
"""
2995
2996
if conf.get("direct"):
2997
return value
2998
2999
count = 0
3000
result = None if value is None else ""
3001
3002
if value:
3003
value = re.sub(r"\b[$\w]+=", lambda match: match.group(0).replace('$', DOLLAR_MARKER), value)
3004
3005
if Backend.isDbms(DBMS.MSSQL) and not kb.tamperFunctions and any(ord(_) > 255 for _ in value):
3006
warnMsg = "if you experience problems with "
3007
warnMsg += "non-ASCII identifier names "
3008
warnMsg += "you are advised to rerun with '--tamper=charunicodeencode'"
3009
singleTimeWarnMessage(warnMsg)
3010
3011
if convall or safe is None:
3012
safe = ""
3013
3014
# corner case when character % really needs to be
3015
# encoded (when not representing URL encoded char)
3016
# except in cases when tampering scripts are used
3017
if all('%' in _ for _ in (safe, value)) and not kb.tamperFunctions:
3018
value = re.sub(r"(?i)\bLIKE\s+'[^']+'", lambda match: match.group(0).replace('%', "%25"), value)
3019
value = re.sub(r"%(?![0-9a-fA-F]{2})", "%25", value)
3020
3021
while True:
3022
result = _urllib.parse.quote(getBytes(value), safe)
3023
3024
if limit and len(result) > URLENCODE_CHAR_LIMIT:
3025
if count >= len(URLENCODE_FAILSAFE_CHARS):
3026
break
3027
3028
while count < len(URLENCODE_FAILSAFE_CHARS):
3029
safe += URLENCODE_FAILSAFE_CHARS[count]
3030
count += 1
3031
if safe[-1] in value:
3032
break
3033
else:
3034
break
3035
3036
if spaceplus:
3037
result = result.replace(_urllib.parse.quote(' '), '+')
3038
3039
result = result.replace(DOLLAR_MARKER, '$')
3040
3041
return result
3042
3043
def runningAsAdmin():
3044
"""
3045
Returns True if the current process is run under admin privileges
3046
"""
3047
3048
isAdmin = None
3049
3050
if PLATFORM in ("posix", "mac"):
3051
_ = os.geteuid()
3052
3053
isAdmin = isinstance(_, (float, six.integer_types)) and _ == 0
3054
elif IS_WIN:
3055
import ctypes
3056
3057
_ = ctypes.windll.shell32.IsUserAnAdmin()
3058
3059
isAdmin = isinstance(_, (float, six.integer_types)) and _ == 1
3060
else:
3061
errMsg = "sqlmap is not able to check if you are running it "
3062
errMsg += "as an administrator account on this platform. "
3063
errMsg += "sqlmap will assume that you are an administrator "
3064
errMsg += "which is mandatory for the requested takeover attack "
3065
errMsg += "to work properly"
3066
logger.error(errMsg)
3067
3068
isAdmin = True
3069
3070
return isAdmin
3071
3072
def logHTTPTraffic(requestLogMsg, responseLogMsg, startTime=None, endTime=None):
3073
"""
3074
Logs HTTP traffic to the output file
3075
"""
3076
3077
if conf.harFile:
3078
conf.httpCollector.collectRequest(requestLogMsg, responseLogMsg, startTime, endTime)
3079
3080
if conf.trafficFile:
3081
with kb.locks.log:
3082
dataToTrafficFile("%s%s" % (requestLogMsg, os.linesep))
3083
dataToTrafficFile("%s%s" % (responseLogMsg, os.linesep))
3084
dataToTrafficFile("%s%s%s%s" % (os.linesep, 76 * '#', os.linesep, os.linesep))
3085
3086
def getPageTemplate(payload, place): # Cross-referenced function
3087
raise NotImplementedError
3088
3089
@cachedmethod
3090
def getPublicTypeMembers(type_, onlyValues=False):
3091
"""
3092
Useful for getting members from types (e.g. in enums)
3093
3094
>>> [_ for _ in getPublicTypeMembers(OS, True)]
3095
['Linux', 'Windows']
3096
>>> [_ for _ in getPublicTypeMembers(PAYLOAD.TECHNIQUE, True)]
3097
[1, 2, 3, 4, 5, 6]
3098
"""
3099
3100
retVal = []
3101
3102
for name, value in inspect.getmembers(type_):
3103
if not name.startswith("__"):
3104
if not onlyValues:
3105
retVal.append((name, value))
3106
else:
3107
retVal.append(value)
3108
3109
return retVal
3110
3111
def enumValueToNameLookup(type_, value_):
3112
"""
3113
Returns name of a enum member with a given value
3114
3115
>>> enumValueToNameLookup(SORT_ORDER, 100)
3116
'LAST'
3117
"""
3118
3119
retVal = None
3120
3121
for name, value in getPublicTypeMembers(type_):
3122
if value == value_:
3123
retVal = name
3124
break
3125
3126
return retVal
3127
3128
@cachedmethod
3129
def extractRegexResult(regex, content, flags=0):
3130
"""
3131
Returns 'result' group value from a possible match with regex on a given
3132
content
3133
3134
>>> extractRegexResult(r'a(?P<result>[^g]+)g', 'abcdefg')
3135
'bcdef'
3136
>>> extractRegexResult(r'a(?P<result>[^g]+)g', 'ABCDEFG', re.I)
3137
'BCDEF'
3138
"""
3139
3140
retVal = None
3141
3142
if regex and content and "?P<result>" in regex:
3143
if isinstance(content, six.binary_type) and isinstance(regex, six.text_type):
3144
regex = getBytes(regex)
3145
3146
match = re.search(regex, content, flags)
3147
3148
if match:
3149
retVal = match.group("result")
3150
3151
return retVal
3152
3153
def extractTextTagContent(page):
3154
"""
3155
Returns list containing content from "textual" tags
3156
3157
>>> extractTextTagContent('<html><head><title>Title</title></head><body><pre>foobar</pre><a href="#link">Link</a></body></html>')
3158
['Title', 'foobar']
3159
"""
3160
3161
page = page or ""
3162
3163
if REFLECTED_VALUE_MARKER in page:
3164
try:
3165
page = re.sub(r"(?i)[^\s>]*%s[^\s<]*" % REFLECTED_VALUE_MARKER, "", page)
3166
except MemoryError:
3167
page = page.replace(REFLECTED_VALUE_MARKER, "")
3168
3169
return filterNone(_.group("result").strip() for _ in re.finditer(TEXT_TAG_REGEX, page))
3170
3171
def trimAlphaNum(value):
3172
"""
3173
Trims alpha numeric characters from start and ending of a given value
3174
3175
>>> trimAlphaNum('AND 1>(2+3)-- foobar')
3176
' 1>(2+3)-- '
3177
"""
3178
3179
while value and value[-1].isalnum():
3180
value = value[:-1]
3181
3182
while value and value[0].isalnum():
3183
value = value[1:]
3184
3185
return value
3186
3187
def isNumPosStrValue(value):
3188
"""
3189
Returns True if value is a string (or integer) with a positive integer representation
3190
3191
>>> isNumPosStrValue(1)
3192
True
3193
>>> isNumPosStrValue('1')
3194
True
3195
>>> isNumPosStrValue(0)
3196
False
3197
>>> isNumPosStrValue('-2')
3198
False
3199
>>> isNumPosStrValue('100000000000000000000')
3200
False
3201
"""
3202
3203
retVal = False
3204
3205
try:
3206
retVal = ((hasattr(value, "isdigit") and value.isdigit() and int(value) > 0) or (isinstance(value, int) and value > 0)) and int(value) < MAX_INT
3207
except ValueError:
3208
pass
3209
3210
return retVal
3211
3212
@cachedmethod
3213
def aliasToDbmsEnum(dbms):
3214
"""
3215
Returns major DBMS name from a given alias
3216
3217
>>> aliasToDbmsEnum('mssql')
3218
'Microsoft SQL Server'
3219
"""
3220
3221
retVal = None
3222
3223
if dbms:
3224
for key, item in DBMS_DICT.items():
3225
if dbms.lower() in item[0] or dbms.lower() == key.lower():
3226
retVal = key
3227
break
3228
3229
return retVal
3230
3231
def findDynamicContent(firstPage, secondPage):
3232
"""
3233
This function checks if the provided pages have dynamic content. If they
3234
are dynamic, proper markings will be made
3235
3236
>>> findDynamicContent("Lorem ipsum dolor sit amet, congue tation referrentur ei sed. Ne nec legimus habemus recusabo, natum reque et per. Facer tritani reprehendunt eos id, modus constituam est te. Usu sumo indoctum ad, pri paulo molestiae complectitur no.", "Lorem ipsum dolor sit amet, congue tation referrentur ei sed. Ne nec legimus habemus recusabo, natum reque et per. <script src='ads.js'></script>Facer tritani reprehendunt eos id, modus constituam est te. Usu sumo indoctum ad, pri paulo molestiae complectitur no.")
3237
>>> kb.dynamicMarkings
3238
[('natum reque et per. ', 'Facer tritani repreh')]
3239
"""
3240
3241
if not firstPage or not secondPage:
3242
return
3243
3244
infoMsg = "searching for dynamic content"
3245
singleTimeLogMessage(infoMsg)
3246
3247
blocks = list(SequenceMatcher(None, firstPage, secondPage).get_matching_blocks())
3248
kb.dynamicMarkings = []
3249
3250
# Removing too small matching blocks
3251
for block in blocks[:]:
3252
(_, _, length) = block
3253
3254
if length <= 2 * DYNAMICITY_BOUNDARY_LENGTH:
3255
blocks.remove(block)
3256
3257
# Making of dynamic markings based on prefix/suffix principle
3258
if len(blocks) > 0:
3259
blocks.insert(0, None)
3260
blocks.append(None)
3261
3262
for i in xrange(len(blocks) - 1):
3263
prefix = firstPage[blocks[i][0]:blocks[i][0] + blocks[i][2]] if blocks[i] else None
3264
suffix = firstPage[blocks[i + 1][0]:blocks[i + 1][0] + blocks[i + 1][2]] if blocks[i + 1] else None
3265
3266
if prefix is None and blocks[i + 1][0] == 0:
3267
continue
3268
3269
if suffix is None and (blocks[i][0] + blocks[i][2] >= len(firstPage)):
3270
continue
3271
3272
if prefix and suffix:
3273
prefix = prefix[-DYNAMICITY_BOUNDARY_LENGTH:]
3274
suffix = suffix[:DYNAMICITY_BOUNDARY_LENGTH]
3275
3276
for _ in (firstPage, secondPage):
3277
match = re.search(r"(?s)%s(.+)%s" % (re.escape(prefix), re.escape(suffix)), _)
3278
if match:
3279
infix = match.group(1)
3280
if infix[0].isalnum():
3281
prefix = trimAlphaNum(prefix)
3282
if infix[-1].isalnum():
3283
suffix = trimAlphaNum(suffix)
3284
break
3285
3286
kb.dynamicMarkings.append((prefix if prefix else None, suffix if suffix else None))
3287
3288
if len(kb.dynamicMarkings) > 0:
3289
infoMsg = "dynamic content marked for removal (%d region%s)" % (len(kb.dynamicMarkings), 's' if len(kb.dynamicMarkings) > 1 else '')
3290
singleTimeLogMessage(infoMsg)
3291
3292
def removeDynamicContent(page):
3293
"""
3294
Removing dynamic content from supplied page basing removal on
3295
precalculated dynamic markings
3296
"""
3297
3298
if page:
3299
for item in kb.dynamicMarkings:
3300
prefix, suffix = item
3301
3302
if prefix is None and suffix is None:
3303
continue
3304
elif prefix is None:
3305
page = re.sub(r"(?s)^.+%s" % re.escape(suffix), suffix.replace('\\', r'\\'), page)
3306
elif suffix is None:
3307
page = re.sub(r"(?s)%s.+$" % re.escape(prefix), prefix.replace('\\', r'\\'), page)
3308
else:
3309
page = re.sub(r"(?s)%s.+%s" % (re.escape(prefix), re.escape(suffix)), "%s%s" % (prefix.replace('\\', r'\\'), suffix.replace('\\', r'\\')), page)
3310
3311
return page
3312
3313
def filterStringValue(value, charRegex, replacement=""):
3314
"""
3315
Returns string value consisting only of chars satisfying supplied
3316
regular expression (note: it has to be in form [...])
3317
3318
>>> filterStringValue('wzydeadbeef0123#', r'[0-9a-f]')
3319
'deadbeef0123'
3320
"""
3321
3322
retVal = value
3323
3324
if value:
3325
retVal = re.sub(charRegex.replace("[", "[^") if "[^" not in charRegex else charRegex.replace("[^", "["), replacement, value)
3326
3327
return retVal
3328
3329
def filterControlChars(value, replacement=' '):
3330
"""
3331
Returns string value with control chars being supstituted with replacement character
3332
3333
>>> filterControlChars('AND 1>(2+3)\\n--')
3334
'AND 1>(2+3) --'
3335
"""
3336
3337
return filterStringValue(value, PRINTABLE_CHAR_REGEX, replacement)
3338
3339
def filterNone(values):
3340
"""
3341
Emulates filterNone([...]) functionality
3342
3343
>>> filterNone([1, 2, "", None, 3, 0])
3344
[1, 2, 3, 0]
3345
"""
3346
3347
retVal = values
3348
3349
if isinstance(values, _collections.Iterable):
3350
retVal = [_ for _ in values if _ or _ == 0]
3351
3352
return retVal
3353
3354
def isDBMSVersionAtLeast(minimum):
3355
"""
3356
Checks if the recognized DBMS version is at least the version specified
3357
3358
>>> pushValue(kb.dbmsVersion)
3359
>>> kb.dbmsVersion = "2"
3360
>>> isDBMSVersionAtLeast("1.3.4.1.4")
3361
True
3362
>>> isDBMSVersionAtLeast(2.1)
3363
False
3364
>>> isDBMSVersionAtLeast(">2")
3365
False
3366
>>> isDBMSVersionAtLeast(">=2.0")
3367
True
3368
>>> kb.dbmsVersion = "<2"
3369
>>> isDBMSVersionAtLeast("2")
3370
False
3371
>>> isDBMSVersionAtLeast("1.5")
3372
True
3373
>>> kb.dbmsVersion = "MySQL 5.4.3-log4"
3374
>>> isDBMSVersionAtLeast("5")
3375
True
3376
>>> kb.dbmsVersion = popValue()
3377
"""
3378
3379
retVal = None
3380
3381
if not any(isNoneValue(_) for _ in (Backend.getVersion(), minimum)) and Backend.getVersion() != UNKNOWN_DBMS_VERSION:
3382
version = Backend.getVersion().replace(" ", "").rstrip('.')
3383
3384
correction = 0.0
3385
if ">=" in version:
3386
pass
3387
elif '>' in version:
3388
correction = VERSION_COMPARISON_CORRECTION
3389
elif '<' in version:
3390
correction = -VERSION_COMPARISON_CORRECTION
3391
3392
version = extractRegexResult(r"(?P<result>[0-9][0-9.]*)", version)
3393
3394
if version:
3395
if '.' in version:
3396
parts = version.split('.', 1)
3397
parts[1] = filterStringValue(parts[1], '[0-9]')
3398
version = '.'.join(parts)
3399
3400
try:
3401
version = float(filterStringValue(version, '[0-9.]')) + correction
3402
except ValueError:
3403
return None
3404
3405
if isinstance(minimum, six.string_types):
3406
if '.' in minimum:
3407
parts = minimum.split('.', 1)
3408
parts[1] = filterStringValue(parts[1], '[0-9]')
3409
minimum = '.'.join(parts)
3410
3411
correction = 0.0
3412
if minimum.startswith(">="):
3413
pass
3414
elif minimum.startswith(">"):
3415
correction = VERSION_COMPARISON_CORRECTION
3416
3417
minimum = float(filterStringValue(minimum, '[0-9.]')) + correction
3418
3419
retVal = version >= minimum
3420
3421
return retVal
3422
3423
def parseSqliteTableSchema(value):
3424
"""
3425
Parses table column names and types from specified SQLite table schema
3426
3427
>>> kb.data.cachedColumns = {}
3428
>>> parseSqliteTableSchema("CREATE TABLE users(\\n\\t\\tid INTEGER,\\n\\t\\tname TEXT\\n);")
3429
True
3430
>>> tuple(kb.data.cachedColumns[conf.db][conf.tbl].items()) == (('id', 'INTEGER'), ('name', 'TEXT'))
3431
True
3432
>>> parseSqliteTableSchema("CREATE TABLE dummy(`foo bar` BIGINT, \\"foo\\" VARCHAR, 'bar' TEXT)");
3433
True
3434
>>> tuple(kb.data.cachedColumns[conf.db][conf.tbl].items()) == (('foo bar', 'BIGINT'), ('foo', 'VARCHAR'), ('bar', 'TEXT'))
3435
True
3436
>>> parseSqliteTableSchema("CREATE TABLE suppliers(\\n\\tsupplier_id INTEGER PRIMARY KEY DESC,\\n\\tname TEXT NOT NULL\\n);");
3437
True
3438
>>> tuple(kb.data.cachedColumns[conf.db][conf.tbl].items()) == (('supplier_id', 'INTEGER'), ('name', 'TEXT'))
3439
True
3440
>>> parseSqliteTableSchema("CREATE TABLE country_languages (\\n\\tcountry_id INTEGER NOT NULL,\\n\\tlanguage_id INTEGER NOT NULL,\\n\\tPRIMARY KEY (country_id, language_id),\\n\\tFOREIGN KEY (country_id) REFERENCES countries (country_id) ON DELETE CASCADE ON UPDATE NO ACTION,\\tFOREIGN KEY (language_id) REFERENCES languages (language_id) ON DELETE CASCADE ON UPDATE NO ACTION);");
3441
True
3442
>>> tuple(kb.data.cachedColumns[conf.db][conf.tbl].items()) == (('country_id', 'INTEGER'), ('language_id', 'INTEGER'))
3443
True
3444
"""
3445
3446
retVal = False
3447
3448
value = extractRegexResult(r"(?s)\((?P<result>.+)\)", value)
3449
3450
if value:
3451
table = {}
3452
columns = OrderedDict()
3453
3454
value = re.sub(r"\(.+?\)", "", value).strip()
3455
3456
for match in re.finditer(r"(?:\A|,)\s*(([\"'`]).+?\2|\w+)(?:\s+(INT|INTEGER|TINYINT|SMALLINT|MEDIUMINT|BIGINT|UNSIGNED BIG INT|INT2|INT8|INTEGER|CHARACTER|VARCHAR|VARYING CHARACTER|NCHAR|NATIVE CHARACTER|NVARCHAR|TEXT|CLOB|LONGTEXT|BLOB|NONE|REAL|DOUBLE|DOUBLE PRECISION|FLOAT|REAL|NUMERIC|DECIMAL|BOOLEAN|DATE|DATETIME|NUMERIC)\b)?", decodeStringEscape(value), re.I):
3457
column = match.group(1).strip(match.group(2) or "")
3458
if re.search(r"(?i)\A(CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)\b", column.strip()):
3459
continue
3460
retVal = True
3461
3462
columns[column] = match.group(3) or "TEXT"
3463
3464
table[safeSQLIdentificatorNaming(conf.tbl, True)] = columns
3465
if conf.db in kb.data.cachedColumns:
3466
kb.data.cachedColumns[conf.db].update(table)
3467
else:
3468
kb.data.cachedColumns[conf.db] = table
3469
3470
return retVal
3471
3472
def getTechniqueData(technique=None):
3473
"""
3474
Returns injection data for technique specified
3475
"""
3476
3477
return kb.injection.data.get(technique if technique is not None else getTechnique())
3478
3479
def isTechniqueAvailable(technique):
3480
"""
3481
Returns True if there is injection data which sqlmap could use for technique specified
3482
3483
>>> pushValue(kb.injection.data)
3484
>>> kb.injection.data[PAYLOAD.TECHNIQUE.ERROR] = [test for test in getSortedInjectionTests() if "error" in test["title"].lower()][0]
3485
>>> isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR)
3486
True
3487
>>> kb.injection.data = popValue()
3488
"""
3489
3490
if conf.technique and isinstance(conf.technique, list) and technique not in conf.technique:
3491
return False
3492
else:
3493
return getTechniqueData(technique) is not None
3494
3495
def isHeavyQueryBased(technique=None):
3496
"""
3497
Returns True whether current (kb.)technique is heavy-query based
3498
3499
>>> pushValue(kb.injection.data)
3500
>>> setTechnique(PAYLOAD.TECHNIQUE.STACKED)
3501
>>> kb.injection.data[getTechnique()] = [test for test in getSortedInjectionTests() if "heavy" in test["title"].lower()][0]
3502
>>> isHeavyQueryBased()
3503
True
3504
>>> kb.injection.data = popValue()
3505
"""
3506
3507
retVal = False
3508
3509
technique = technique or getTechnique()
3510
3511
if isTechniqueAvailable(technique):
3512
data = getTechniqueData(technique)
3513
if data and "heavy query" in data["title"].lower():
3514
retVal = True
3515
3516
return retVal
3517
3518
def isStackingAvailable():
3519
"""
3520
Returns True whether techniques using stacking are available
3521
3522
>>> pushValue(kb.injection.data)
3523
>>> kb.injection.data[PAYLOAD.TECHNIQUE.STACKED] = [test for test in getSortedInjectionTests() if "stacked" in test["title"].lower()][0]
3524
>>> isStackingAvailable()
3525
True
3526
>>> kb.injection.data = popValue()
3527
"""
3528
3529
retVal = False
3530
3531
if PAYLOAD.TECHNIQUE.STACKED in kb.injection.data:
3532
retVal = True
3533
else:
3534
for technique in getPublicTypeMembers(PAYLOAD.TECHNIQUE, True):
3535
data = getTechniqueData(technique)
3536
if data and "stacked" in data["title"].lower():
3537
retVal = True
3538
break
3539
3540
return retVal
3541
3542
def isInferenceAvailable():
3543
"""
3544
Returns True whether techniques using inference technique are available
3545
3546
>>> pushValue(kb.injection.data)
3547
>>> kb.injection.data[PAYLOAD.TECHNIQUE.BOOLEAN] = getSortedInjectionTests()[0]
3548
>>> isInferenceAvailable()
3549
True
3550
>>> kb.injection.data = popValue()
3551
"""
3552
3553
return any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.STACKED, PAYLOAD.TECHNIQUE.TIME))
3554
3555
def setOptimize():
3556
"""
3557
Sets options turned on by switch '-o'
3558
"""
3559
3560
# conf.predictOutput = True
3561
conf.keepAlive = True
3562
conf.threads = 3 if conf.threads < 3 and cmdLineOptions.threads is None else conf.threads
3563
conf.nullConnection = not any((conf.data, conf.textOnly, conf.titles, conf.string, conf.notString, conf.regexp, conf.tor))
3564
3565
if not conf.nullConnection:
3566
debugMsg = "turning off switch '--null-connection' used indirectly by switch '-o'"
3567
logger.debug(debugMsg)
3568
3569
def saveConfig(conf, filename):
3570
"""
3571
Saves conf to configuration filename
3572
"""
3573
3574
config = UnicodeRawConfigParser()
3575
userOpts = {}
3576
3577
for family in optDict:
3578
userOpts[family] = []
3579
3580
for option, value in conf.items():
3581
for family, optionData in optDict.items():
3582
if option in optionData:
3583
userOpts[family].append((option, value, optionData[option]))
3584
3585
for family, optionData in userOpts.items():
3586
config.add_section(family)
3587
3588
optionData.sort()
3589
3590
for option, value, datatype in optionData:
3591
if datatype and isListLike(datatype):
3592
datatype = datatype[0]
3593
3594
if option in IGNORE_SAVE_OPTIONS:
3595
continue
3596
3597
if value is None:
3598
if datatype == OPTION_TYPE.BOOLEAN:
3599
value = "False"
3600
elif datatype in (OPTION_TYPE.INTEGER, OPTION_TYPE.FLOAT):
3601
if option in defaults:
3602
value = str(defaults[option])
3603
else:
3604
value = '0'
3605
elif datatype == OPTION_TYPE.STRING:
3606
value = ""
3607
3608
if isinstance(value, six.string_types):
3609
value = value.replace("\n", "\n ")
3610
3611
config.set(family, option, value)
3612
3613
with openFile(filename, 'w') as f:
3614
try:
3615
config.write(f)
3616
except IOError as ex:
3617
errMsg = "something went wrong while trying "
3618
errMsg += "to write to the configuration file '%s' ('%s')" % (filename, getSafeExString(ex))
3619
raise SqlmapSystemException(errMsg)
3620
3621
def initTechnique(technique=None):
3622
"""
3623
Prepares data for technique specified
3624
"""
3625
3626
try:
3627
data = getTechniqueData(technique)
3628
resetCounter(technique)
3629
3630
if data:
3631
kb.pageTemplate, kb.errorIsNone = getPageTemplate(data.templatePayload, kb.injection.place)
3632
kb.matchRatio = data.matchRatio
3633
kb.negativeLogic = (technique == PAYLOAD.TECHNIQUE.BOOLEAN) and (data.where == PAYLOAD.WHERE.NEGATIVE)
3634
3635
# Restoring stored conf options
3636
for key, value in kb.injection.conf.items():
3637
if value and (not hasattr(conf, key) or (hasattr(conf, key) and not getattr(conf, key))):
3638
setattr(conf, key, value)
3639
debugMsg = "resuming configuration option '%s' (%s)" % (key, ("'%s'" % value) if isinstance(value, six.string_types) else value)
3640
logger.debug(debugMsg)
3641
3642
if value and key == "optimize":
3643
setOptimize()
3644
else:
3645
warnMsg = "there is no injection data available for technique "
3646
warnMsg += "'%s'" % enumValueToNameLookup(PAYLOAD.TECHNIQUE, technique)
3647
logger.warning(warnMsg)
3648
3649
except SqlmapDataException:
3650
errMsg = "missing data in old session file(s). "
3651
errMsg += "Please use '--flush-session' to deal "
3652
errMsg += "with this error"
3653
raise SqlmapNoneDataException(errMsg)
3654
3655
def arrayizeValue(value):
3656
"""
3657
Makes a list out of value if it is not already a list or tuple itself
3658
3659
>>> arrayizeValue('1')
3660
['1']
3661
"""
3662
3663
if isinstance(value, _collections.KeysView):
3664
value = [_ for _ in value]
3665
elif not isListLike(value):
3666
value = [value]
3667
3668
return value
3669
3670
def unArrayizeValue(value):
3671
"""
3672
Makes a value out of iterable if it is a list or tuple itself
3673
3674
>>> unArrayizeValue(['1'])
3675
'1'
3676
>>> unArrayizeValue('1')
3677
'1'
3678
>>> unArrayizeValue(['1', '2'])
3679
'1'
3680
>>> unArrayizeValue([['a', 'b'], 'c'])
3681
'a'
3682
>>> unArrayizeValue(_ for _ in xrange(10))
3683
0
3684
"""
3685
3686
if isListLike(value):
3687
if not value:
3688
value = None
3689
elif len(value) == 1 and not isListLike(value[0]):
3690
value = value[0]
3691
else:
3692
value = [_ for _ in flattenValue(value) if _ is not None]
3693
value = value[0] if len(value) > 0 else None
3694
elif inspect.isgenerator(value):
3695
value = unArrayizeValue([_ for _ in value])
3696
3697
return value
3698
3699
def flattenValue(value):
3700
"""
3701
Returns an iterator representing flat representation of a given value
3702
3703
>>> [_ for _ in flattenValue([['1'], [['2'], '3']])]
3704
['1', '2', '3']
3705
"""
3706
3707
for i in iter(value):
3708
if isListLike(i):
3709
for j in flattenValue(i):
3710
yield j
3711
else:
3712
yield i
3713
3714
def joinValue(value, delimiter=','):
3715
"""
3716
Returns a value consisting of joined parts of a given value
3717
3718
>>> joinValue(['1', '2'])
3719
'1,2'
3720
>>> joinValue('1')
3721
'1'
3722
>>> joinValue(['1', None])
3723
'1,None'
3724
"""
3725
3726
if isListLike(value):
3727
retVal = delimiter.join(getText(_ if _ is not None else "None") for _ in value)
3728
else:
3729
retVal = value
3730
3731
return retVal
3732
3733
def isListLike(value):
3734
"""
3735
Returns True if the given value is a list-like instance
3736
3737
>>> isListLike([1, 2, 3])
3738
True
3739
>>> isListLike('2')
3740
False
3741
"""
3742
3743
return isinstance(value, (list, tuple, set, OrderedSet, BigArray))
3744
3745
def getSortedInjectionTests():
3746
"""
3747
Returns prioritized test list by eventually detected DBMS from error messages
3748
3749
>>> pushValue(kb.forcedDbms)
3750
>>> kb.forcedDbms = DBMS.SQLITE
3751
>>> [test for test in getSortedInjectionTests() if hasattr(test, "details") and hasattr(test.details, "dbms")][0].details.dbms == kb.forcedDbms
3752
True
3753
>>> kb.forcedDbms = popValue()
3754
"""
3755
3756
retVal = copy.deepcopy(conf.tests)
3757
3758
def priorityFunction(test):
3759
retVal = SORT_ORDER.FIRST
3760
3761
if test.stype == PAYLOAD.TECHNIQUE.UNION:
3762
retVal = SORT_ORDER.LAST
3763
3764
elif "details" in test and "dbms" in (test.details or {}):
3765
if intersect(test.details.dbms, Backend.getIdentifiedDbms()):
3766
retVal = SORT_ORDER.SECOND
3767
else:
3768
retVal = SORT_ORDER.THIRD
3769
3770
return retVal
3771
3772
if Backend.getIdentifiedDbms():
3773
retVal = sorted(retVal, key=priorityFunction)
3774
3775
return retVal
3776
3777
def filterListValue(value, regex):
3778
"""
3779
Returns list with items that have parts satisfying given regular expression
3780
3781
>>> filterListValue(['users', 'admins', 'logs'], r'(users|admins)')
3782
['users', 'admins']
3783
"""
3784
3785
if isinstance(value, list) and regex:
3786
retVal = [_ for _ in value if re.search(regex, _, re.I)]
3787
else:
3788
retVal = value
3789
3790
return retVal
3791
3792
def showHttpErrorCodes():
3793
"""
3794
Shows all HTTP error codes raised till now
3795
"""
3796
3797
if kb.httpErrorCodes:
3798
warnMsg = "HTTP error codes detected during run:\n"
3799
warnMsg += ", ".join("%d (%s) - %d times" % (code, _http_client.responses[code] if code in _http_client.responses else '?', count) for code, count in kb.httpErrorCodes.items())
3800
logger.warning(warnMsg)
3801
if any((str(_).startswith('4') or str(_).startswith('5')) and _ != _http_client.INTERNAL_SERVER_ERROR and _ != kb.originalCode for _ in kb.httpErrorCodes):
3802
msg = "too many 4xx and/or 5xx HTTP error codes "
3803
msg += "could mean that some kind of protection is involved (e.g. WAF)"
3804
logger.debug(msg)
3805
3806
def openFile(filename, mode='r', encoding=UNICODE_ENCODING, errors="reversible", buffering=1): # "buffering=1" means line buffered (Reference: http://stackoverflow.com/a/3168436)
3807
"""
3808
Returns file handle of a given filename
3809
3810
>>> "openFile" in openFile(__file__).read()
3811
True
3812
>>> b"openFile" in openFile(__file__, "rb", None).read()
3813
True
3814
"""
3815
3816
# Reference: https://stackoverflow.com/a/37462452
3817
if 'b' in mode:
3818
buffering = 0
3819
encoding = None
3820
3821
if filename == STDIN_PIPE_DASH:
3822
if filename not in kb.cache.content:
3823
kb.cache.content[filename] = sys.stdin.read()
3824
3825
return contextlib.closing(io.StringIO(readCachedFileContent(filename)))
3826
else:
3827
try:
3828
return codecs_open(filename, mode, encoding, errors, buffering)
3829
except IOError:
3830
errMsg = "there has been a file opening error for filename '%s'. " % filename
3831
errMsg += "Please check %s permissions on a file " % ("write" if mode and ('w' in mode or 'a' in mode or '+' in mode) else "read")
3832
errMsg += "and that it's not locked by another process"
3833
raise SqlmapSystemException(errMsg)
3834
3835
def decodeIntToUnicode(value):
3836
"""
3837
Decodes inferenced integer value to an unicode character
3838
3839
>>> decodeIntToUnicode(35) == '#'
3840
True
3841
>>> decodeIntToUnicode(64) == '@'
3842
True
3843
"""
3844
retVal = value
3845
3846
if isinstance(value, int):
3847
try:
3848
if value > 255:
3849
_ = "%x" % value
3850
3851
if len(_) % 2 == 1:
3852
_ = "0%s" % _
3853
3854
raw = decodeHex(_)
3855
3856
if Backend.isDbms(DBMS.MYSQL):
3857
# Reference: https://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_ord
3858
# Note: https://github.com/sqlmapproject/sqlmap/issues/1531
3859
retVal = getUnicode(raw, conf.encoding or UNICODE_ENCODING)
3860
elif Backend.isDbms(DBMS.MSSQL):
3861
# Reference: https://docs.microsoft.com/en-us/sql/relational-databases/collations/collation-and-unicode-support?view=sql-server-2017 and https://stackoverflow.com/a/14488478
3862
retVal = getUnicode(raw, "UTF-16-BE")
3863
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE): # Note: cases with Unicode code points (e.g. http://www.postgresqltutorial.com/postgresql-ascii/)
3864
retVal = _unichr(value)
3865
else:
3866
retVal = getUnicode(raw, conf.encoding)
3867
else:
3868
retVal = _unichr(value)
3869
except:
3870
retVal = INFERENCE_UNKNOWN_CHAR
3871
3872
return retVal
3873
3874
def getDaysFromLastUpdate():
3875
"""
3876
Get total number of days from last update
3877
3878
>>> getDaysFromLastUpdate() >= 0
3879
True
3880
"""
3881
3882
if not paths:
3883
return
3884
3885
return int(time.time() - os.path.getmtime(paths.SQLMAP_SETTINGS_PATH)) // (3600 * 24)
3886
3887
def unhandledExceptionMessage():
3888
"""
3889
Returns detailed message about occurred unhandled exception
3890
3891
>>> all(_ in unhandledExceptionMessage() for _ in ("unhandled exception occurred", "Operating system", "Command line"))
3892
True
3893
"""
3894
3895
errMsg = "unhandled exception occurred in %s. It is recommended to retry your " % VERSION_STRING
3896
errMsg += "run with the latest development version from official GitHub "
3897
errMsg += "repository at '%s'. If the exception persists, please open a new issue " % GIT_PAGE
3898
errMsg += "at '%s' " % ISSUES_PAGE
3899
errMsg += "with the following text and any other information required to "
3900
errMsg += "reproduce the bug. Developers will try to reproduce the bug, fix it accordingly "
3901
errMsg += "and get back to you\n"
3902
errMsg += "Running version: %s\n" % VERSION_STRING[VERSION_STRING.find('/') + 1:]
3903
errMsg += "Python version: %s\n" % PYVERSION
3904
errMsg += "Operating system: %s\n" % platform.platform()
3905
errMsg += "Command line: %s\n" % re.sub(r".+?\bsqlmap\.py\b", "sqlmap.py", getUnicode(" ".join(sys.argv), encoding=getattr(sys.stdin, "encoding", None)))
3906
errMsg += "Technique: %s\n" % (enumValueToNameLookup(PAYLOAD.TECHNIQUE, getTechnique()) if getTechnique() is not None else ("DIRECT" if conf.get("direct") else None))
3907
errMsg += "Back-end DBMS:"
3908
3909
if Backend.getDbms() is not None:
3910
errMsg += " %s (fingerprinted)" % Backend.getDbms()
3911
3912
if Backend.getIdentifiedDbms() is not None and (Backend.getDbms() is None or Backend.getIdentifiedDbms() != Backend.getDbms()):
3913
errMsg += " %s (identified)" % Backend.getIdentifiedDbms()
3914
3915
if not errMsg.endswith(')'):
3916
errMsg += " None"
3917
3918
return errMsg
3919
3920
def getLatestRevision():
3921
"""
3922
Retrieves latest revision from the offical repository
3923
"""
3924
3925
retVal = None
3926
req = _urllib.request.Request(url="https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/lib/core/settings.py", headers={HTTP_HEADER.USER_AGENT: fetchRandomAgent()})
3927
3928
try:
3929
content = getUnicode(_urllib.request.urlopen(req).read())
3930
retVal = extractRegexResult(r"VERSION\s*=\s*[\"'](?P<result>[\d.]+)", content)
3931
except:
3932
pass
3933
3934
return retVal
3935
3936
def fetchRandomAgent():
3937
"""
3938
Returns random HTTP User-Agent header value
3939
3940
>>> '(' in fetchRandomAgent()
3941
True
3942
"""
3943
3944
if not kb.userAgents:
3945
debugMsg = "loading random HTTP User-Agent header(s) from "
3946
debugMsg += "file '%s'" % paths.USER_AGENTS
3947
logger.debug(debugMsg)
3948
3949
try:
3950
kb.userAgents = getFileItems(paths.USER_AGENTS)
3951
except IOError:
3952
errMsg = "unable to read HTTP User-Agent header "
3953
errMsg += "file '%s'" % paths.USER_AGENTS
3954
raise SqlmapSystemException(errMsg)
3955
3956
return random.sample(kb.userAgents, 1)[0]
3957
3958
def createGithubIssue(errMsg, excMsg):
3959
"""
3960
Automatically create a Github issue with unhandled exception information
3961
"""
3962
3963
try:
3964
issues = getFileItems(paths.GITHUB_HISTORY, unique=True)
3965
except:
3966
issues = []
3967
finally:
3968
issues = set(issues)
3969
3970
_ = re.sub(r"'[^']+'", "''", excMsg)
3971
_ = re.sub(r"\s+line \d+", "", _)
3972
_ = re.sub(r'File ".+?/(\w+\.py)', r"\g<1>", _)
3973
_ = re.sub(r".+\Z", "", _)
3974
_ = re.sub(r"(Unicode[^:]*Error:).+", r"\g<1>", _)
3975
_ = re.sub(r"= _", "= ", _)
3976
3977
key = hashlib.md5(getBytes(_)).hexdigest()[:8]
3978
3979
if key in issues:
3980
return
3981
3982
msg = "\ndo you want to automatically create a new (anonymized) issue "
3983
msg += "with the unhandled exception information at "
3984
msg += "the official Github repository? [y/N] "
3985
try:
3986
choice = readInput(msg, default='N', checkBatch=False, boolean=True)
3987
except:
3988
choice = None
3989
3990
if choice:
3991
_excMsg = None
3992
errMsg = errMsg[errMsg.find("\n"):]
3993
3994
req = _urllib.request.Request(url="https://api.github.com/search/issues?q=%s" % _urllib.parse.quote("repo:sqlmapproject/sqlmap Unhandled exception (#%s)" % key), headers={HTTP_HEADER.USER_AGENT: fetchRandomAgent()})
3995
3996
try:
3997
content = _urllib.request.urlopen(req).read()
3998
_ = json.loads(content)
3999
duplicate = _["total_count"] > 0
4000
closed = duplicate and _["items"][0]["state"] == "closed"
4001
if duplicate:
4002
warnMsg = "issue seems to be already reported"
4003
if closed:
4004
warnMsg += " and resolved. Please update to the latest "
4005
warnMsg += "development version from official GitHub repository at '%s'" % GIT_PAGE
4006
logger.warning(warnMsg)
4007
return
4008
except:
4009
pass
4010
4011
data = {"title": "Unhandled exception (#%s)" % key, "body": "```%s\n```\n```\n%s```" % (errMsg, excMsg)}
4012
token = getText(zlib.decompress(decodeBase64(GITHUB_REPORT_OAUTH_TOKEN[::-1], binary=True))[0::2][::-1])
4013
req = _urllib.request.Request(url="https://api.github.com/repos/sqlmapproject/sqlmap/issues", data=getBytes(json.dumps(data)), headers={HTTP_HEADER.AUTHORIZATION: "token %s" % token, HTTP_HEADER.USER_AGENT: fetchRandomAgent()})
4014
4015
try:
4016
content = getText(_urllib.request.urlopen(req).read())
4017
except Exception as ex:
4018
content = None
4019
_excMsg = getSafeExString(ex)
4020
4021
issueUrl = re.search(r"https://github.com/sqlmapproject/sqlmap/issues/\d+", content or "")
4022
if issueUrl:
4023
infoMsg = "created Github issue can been found at the address '%s'" % issueUrl.group(0)
4024
logger.info(infoMsg)
4025
4026
try:
4027
with openFile(paths.GITHUB_HISTORY, "a+") as f:
4028
f.write("%s\n" % key)
4029
except:
4030
pass
4031
else:
4032
warnMsg = "something went wrong while creating a Github issue"
4033
if _excMsg:
4034
warnMsg += " ('%s')" % _excMsg
4035
if "Unauthorized" in warnMsg:
4036
warnMsg += ". Please update to the latest revision"
4037
logger.warning(warnMsg)
4038
4039
def maskSensitiveData(msg):
4040
"""
4041
Masks sensitive data in the supplied message
4042
4043
>>> maskSensitiveData('python sqlmap.py -u "http://www.test.com/vuln.php?id=1" --banner') == 'python sqlmap.py -u *********************************** --banner'
4044
True
4045
>>> maskSensitiveData('sqlmap.py -u test.com/index.go?id=index --auth-type=basic --auth-creds=foo:bar\\ndummy line') == 'sqlmap.py -u ************************** --auth-type=***** --auth-creds=*******\\ndummy line'
4046
True
4047
"""
4048
4049
retVal = getUnicode(msg)
4050
4051
for item in filterNone(conf.get(_) for _ in SENSITIVE_OPTIONS):
4052
if isListLike(item):
4053
item = listToStrValue(item)
4054
4055
regex = SENSITIVE_DATA_REGEX % re.sub(r"(\W)", r"\\\1", getUnicode(item))
4056
while extractRegexResult(regex, retVal):
4057
value = extractRegexResult(regex, retVal)
4058
retVal = retVal.replace(value, '*' * len(value))
4059
4060
# Just in case (for problematic parameters regarding user encoding)
4061
for match in re.finditer(r"(?im)[ -]-(u|url|data|cookie|auth-\w+|proxy|host|referer|headers?|H)( |=)(.*?)(?= -?-[a-z]|$)", retVal):
4062
retVal = retVal.replace(match.group(3), '*' * len(match.group(3)))
4063
4064
# Fail-safe substitutions
4065
retVal = re.sub(r"(?i)(Command line:.+)\b(https?://[^ ]+)", lambda match: "%s%s" % (match.group(1), '*' * len(match.group(2))), retVal)
4066
retVal = re.sub(r"(?i)(\b\w:[\\/]+Users[\\/]+|[\\/]+home[\\/]+)([^\\/]+)", lambda match: "%s%s" % (match.group(1), '*' * len(match.group(2))), retVal)
4067
4068
if getpass.getuser():
4069
retVal = re.sub(r"(?i)\b%s\b" % re.escape(getpass.getuser()), '*' * len(getpass.getuser()), retVal)
4070
4071
return retVal
4072
4073
def listToStrValue(value):
4074
"""
4075
Flattens list to a string value
4076
4077
>>> listToStrValue([1,2,3])
4078
'1, 2, 3'
4079
"""
4080
4081
if isinstance(value, (set, tuple, types.GeneratorType)):
4082
value = list(value)
4083
4084
if isinstance(value, list):
4085
retVal = value.__str__().lstrip('[').rstrip(']')
4086
else:
4087
retVal = value
4088
4089
return retVal
4090
4091
def intersect(containerA, containerB, lowerCase=False):
4092
"""
4093
Returns intersection of the container-ized values
4094
4095
>>> intersect([1, 2, 3], set([1,3]))
4096
[1, 3]
4097
"""
4098
4099
retVal = []
4100
4101
if containerA and containerB:
4102
containerA = arrayizeValue(containerA)
4103
containerB = arrayizeValue(containerB)
4104
4105
if lowerCase:
4106
containerA = [val.lower() if hasattr(val, "lower") else val for val in containerA]
4107
containerB = [val.lower() if hasattr(val, "lower") else val for val in containerB]
4108
4109
retVal = [val for val in containerA if val in containerB]
4110
4111
return retVal
4112
4113
def decodeStringEscape(value):
4114
"""
4115
Decodes escaped string values (e.g. "\\t" -> "\t")
4116
"""
4117
4118
retVal = value
4119
4120
if value and '\\' in value:
4121
charset = "\\%s" % string.whitespace.replace(" ", "")
4122
for _ in charset:
4123
retVal = retVal.replace(repr(_).strip("'"), _)
4124
4125
return retVal
4126
4127
def encodeStringEscape(value):
4128
"""
4129
Encodes escaped string values (e.g. "\t" -> "\\t")
4130
"""
4131
4132
retVal = value
4133
4134
if value:
4135
charset = "\\%s" % string.whitespace.replace(" ", "")
4136
for _ in charset:
4137
retVal = retVal.replace(_, repr(_).strip("'"))
4138
4139
return retVal
4140
4141
def removeReflectiveValues(content, payload, suppressWarning=False):
4142
"""
4143
Neutralizes reflective values in a given content based on a payload
4144
(e.g. ..search.php?q=1 AND 1=2 --> "...searching for <b>1%20AND%201%3D2</b>..." --> "...searching for <b>__REFLECTED_VALUE__</b>...")
4145
"""
4146
4147
retVal = content
4148
4149
try:
4150
if all((content, payload)) and isinstance(content, six.text_type) and kb.reflectiveMechanism and not kb.heuristicMode:
4151
def _(value):
4152
while 2 * REFLECTED_REPLACEMENT_REGEX in value:
4153
value = value.replace(2 * REFLECTED_REPLACEMENT_REGEX, REFLECTED_REPLACEMENT_REGEX)
4154
return value
4155
4156
payload = getUnicode(urldecode(payload.replace(PAYLOAD_DELIMITER, ""), convall=True))
4157
regex = _(filterStringValue(payload, r"[A-Za-z0-9]", encodeStringEscape(REFLECTED_REPLACEMENT_REGEX)))
4158
4159
# NOTE: special case when part of the result shares the same output as the payload (e.g. ?id=1... and "sqlmap/1.0-dev (http://sqlmap.org)")
4160
preserve = extractRegexResult(r"%s(?P<result>.+?)%s" % (kb.chars.start, kb.chars.stop), content)
4161
if preserve:
4162
content = content.replace(preserve, REPLACEMENT_MARKER)
4163
4164
if regex != payload:
4165
if all(part.lower() in content.lower() for part in filterNone(regex.split(REFLECTED_REPLACEMENT_REGEX))[1:]): # fast optimization check
4166
parts = regex.split(REFLECTED_REPLACEMENT_REGEX)
4167
4168
# Note: naive approach
4169
retVal = content.replace(payload, REFLECTED_VALUE_MARKER)
4170
retVal = retVal.replace(re.sub(r"\A\w+", "", payload), REFLECTED_VALUE_MARKER)
4171
4172
if len(parts) > REFLECTED_MAX_REGEX_PARTS: # preventing CPU hogs
4173
regex = _("%s%s%s" % (REFLECTED_REPLACEMENT_REGEX.join(parts[:REFLECTED_MAX_REGEX_PARTS // 2]), REFLECTED_REPLACEMENT_REGEX, REFLECTED_REPLACEMENT_REGEX.join(parts[-REFLECTED_MAX_REGEX_PARTS // 2:])))
4174
4175
parts = filterNone(regex.split(REFLECTED_REPLACEMENT_REGEX))
4176
4177
if regex.startswith(REFLECTED_REPLACEMENT_REGEX):
4178
regex = r"%s%s" % (REFLECTED_BORDER_REGEX, regex[len(REFLECTED_REPLACEMENT_REGEX):])
4179
else:
4180
regex = r"\b%s" % regex
4181
4182
if regex.endswith(REFLECTED_REPLACEMENT_REGEX):
4183
regex = r"%s%s" % (regex[:-len(REFLECTED_REPLACEMENT_REGEX)], REFLECTED_BORDER_REGEX)
4184
else:
4185
regex = r"%s\b" % regex
4186
4187
_retVal = [retVal]
4188
4189
def _thread(regex):
4190
try:
4191
_retVal[0] = re.sub(r"(?i)%s" % regex, REFLECTED_VALUE_MARKER, _retVal[0])
4192
4193
if len(parts) > 2:
4194
regex = REFLECTED_REPLACEMENT_REGEX.join(parts[1:])
4195
_retVal[0] = re.sub(r"(?i)\b%s\b" % regex, REFLECTED_VALUE_MARKER, _retVal[0])
4196
except KeyboardInterrupt:
4197
raise
4198
except:
4199
pass
4200
4201
thread = threading.Thread(target=_thread, args=(regex,))
4202
thread.daemon = True
4203
thread.start()
4204
thread.join(REFLECTED_REPLACEMENT_TIMEOUT)
4205
4206
if thread.is_alive():
4207
kb.reflectiveMechanism = False
4208
retVal = content
4209
if not suppressWarning:
4210
debugMsg = "turning off reflection removal mechanism (because of timeouts)"
4211
logger.debug(debugMsg)
4212
else:
4213
retVal = _retVal[0]
4214
4215
if retVal != content:
4216
kb.reflectiveCounters[REFLECTIVE_COUNTER.HIT] += 1
4217
if not suppressWarning:
4218
warnMsg = "reflective value(s) found and filtering out"
4219
singleTimeWarnMessage(warnMsg)
4220
4221
if re.search(r"(?i)FRAME[^>]+src=[^>]*%s" % REFLECTED_VALUE_MARKER, retVal):
4222
warnMsg = "frames detected containing attacked parameter values. Please be sure to "
4223
warnMsg += "test those separately in case that attack on this page fails"
4224
singleTimeWarnMessage(warnMsg)
4225
4226
elif not kb.testMode and not kb.reflectiveCounters[REFLECTIVE_COUNTER.HIT]:
4227
kb.reflectiveCounters[REFLECTIVE_COUNTER.MISS] += 1
4228
if kb.reflectiveCounters[REFLECTIVE_COUNTER.MISS] > REFLECTIVE_MISS_THRESHOLD:
4229
kb.reflectiveMechanism = False
4230
if not suppressWarning:
4231
debugMsg = "turning off reflection removal mechanism (for optimization purposes)"
4232
logger.debug(debugMsg)
4233
4234
if preserve and retVal:
4235
retVal = retVal.replace(REPLACEMENT_MARKER, preserve)
4236
4237
except (MemoryError, SystemError):
4238
kb.reflectiveMechanism = False
4239
if not suppressWarning:
4240
debugMsg = "turning off reflection removal mechanism"
4241
logger.debug(debugMsg)
4242
4243
return retVal
4244
4245
def normalizeUnicode(value, charset=string.printable[:string.printable.find(' ') + 1]):
4246
"""
4247
Does an ASCII normalization of unicode strings
4248
4249
# Reference: http://www.peterbe.com/plog/unicode-to-ascii
4250
4251
>>> normalizeUnicode(u'\\u0161u\\u0107uraj') == u'sucuraj'
4252
True
4253
>>> normalizeUnicode(getUnicode(decodeHex("666f6f00626172"))) == u'foobar'
4254
True
4255
"""
4256
4257
retVal = value
4258
4259
if isinstance(value, six.text_type):
4260
retVal = unicodedata.normalize("NFKD", value)
4261
retVal = "".join(_ for _ in retVal if _ in charset)
4262
4263
return retVal
4264
4265
def safeSQLIdentificatorNaming(name, isTable=False):
4266
"""
4267
Returns a safe representation of SQL identificator name (internal data format)
4268
4269
# Reference: http://stackoverflow.com/questions/954884/what-special-characters-are-allowed-in-t-sql-column-retVal
4270
4271
>>> pushValue(kb.forcedDbms)
4272
>>> kb.forcedDbms = DBMS.MSSQL
4273
>>> getText(safeSQLIdentificatorNaming("begin"))
4274
'[begin]'
4275
>>> getText(safeSQLIdentificatorNaming("foobar"))
4276
'foobar'
4277
>>> kb.forceDbms = popValue()
4278
"""
4279
4280
retVal = name
4281
4282
if conf.unsafeNaming:
4283
return retVal
4284
4285
if isinstance(name, six.string_types):
4286
retVal = getUnicode(name)
4287
_ = isTable and Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE)
4288
4289
if _:
4290
retVal = re.sub(r"(?i)\A\[?%s\]?\." % DEFAULT_MSSQL_SCHEMA, "%s." % DEFAULT_MSSQL_SCHEMA, retVal)
4291
4292
# Note: SQL 92 has restrictions for identifiers starting with underscore (e.g. http://www.frontbase.com/documentation/FBUsers_4.pdf)
4293
if retVal.upper() in kb.keywords or (not isTable and (retVal or " ")[0] == '_') or (retVal or " ")[0].isdigit() or not re.match(r"\A[A-Za-z0-9_@%s\$]+\Z" % ('.' if _ else ""), retVal): # MsSQL is the only DBMS where we automatically prepend schema to table name (dot is normal)
4294
if not conf.noEscape:
4295
retVal = unsafeSQLIdentificatorNaming(retVal)
4296
4297
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE): # Note: in SQLite double-quotes are treated as string if column/identifier is non-existent (e.g. SELECT "foobar" FROM users)
4298
retVal = "`%s`" % retVal
4299
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE):
4300
retVal = "\"%s\"" % retVal
4301
elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL):
4302
retVal = "\"%s\"" % retVal.upper()
4303
elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
4304
if isTable:
4305
parts = retVal.split('.', 1)
4306
for i in xrange(len(parts)):
4307
if parts[i] and (re.search(r"\A\d|[^\w]", parts[i], re.U) or parts[i].upper() in kb.keywords):
4308
parts[i] = "[%s]" % parts[i]
4309
retVal = '.'.join(parts)
4310
else:
4311
if re.search(r"\A\d|[^\w]", retVal, re.U) or retVal.upper() in kb.keywords:
4312
retVal = "[%s]" % retVal
4313
4314
if _ and DEFAULT_MSSQL_SCHEMA not in retVal and '.' not in re.sub(r"\[[^]]+\]", "", retVal):
4315
if (conf.db or "").lower() != "information_schema": # NOTE: https://github.com/sqlmapproject/sqlmap/issues/5192
4316
retVal = "%s.%s" % (DEFAULT_MSSQL_SCHEMA, retVal)
4317
4318
return retVal
4319
4320
def unsafeSQLIdentificatorNaming(name):
4321
"""
4322
Extracts identificator's name from its safe SQL representation
4323
4324
>>> pushValue(kb.forcedDbms)
4325
>>> kb.forcedDbms = DBMS.MSSQL
4326
>>> getText(unsafeSQLIdentificatorNaming("[begin]"))
4327
'begin'
4328
>>> getText(unsafeSQLIdentificatorNaming("foobar"))
4329
'foobar'
4330
>>> kb.forceDbms = popValue()
4331
"""
4332
4333
retVal = name
4334
4335
if isinstance(name, six.string_types):
4336
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE):
4337
retVal = name.replace("`", "")
4338
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE):
4339
retVal = name.replace("\"", "")
4340
elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL):
4341
retVal = name.replace("\"", "").upper()
4342
elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
4343
retVal = name.replace("[", "").replace("]", "")
4344
4345
if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
4346
retVal = re.sub(r"(?i)\A\[?%s\]?\." % DEFAULT_MSSQL_SCHEMA, "", retVal)
4347
4348
return retVal
4349
4350
def isNoneValue(value):
4351
"""
4352
Returns whether the value is unusable (None or '')
4353
4354
>>> isNoneValue(None)
4355
True
4356
>>> isNoneValue('None')
4357
True
4358
>>> isNoneValue('')
4359
True
4360
>>> isNoneValue([])
4361
True
4362
>>> isNoneValue([2])
4363
False
4364
"""
4365
4366
if isinstance(value, six.string_types):
4367
return value in ("None", "")
4368
elif isListLike(value):
4369
return all(isNoneValue(_) for _ in value)
4370
elif isinstance(value, dict):
4371
return not any(value)
4372
else:
4373
return value is None
4374
4375
def isNullValue(value):
4376
"""
4377
Returns whether the value contains explicit 'NULL' value
4378
4379
>>> isNullValue(u'NULL')
4380
True
4381
>>> isNullValue(u'foobar')
4382
False
4383
"""
4384
4385
return hasattr(value, "upper") and value.upper() == NULL
4386
4387
def expandMnemonics(mnemonics, parser, args):
4388
"""
4389
Expands mnemonic options
4390
"""
4391
4392
class MnemonicNode(object):
4393
def __init__(self):
4394
self.next = {}
4395
self.current = []
4396
4397
head = MnemonicNode()
4398
pointer = None
4399
4400
for group in parser.option_groups:
4401
for option in group.option_list:
4402
for opt in option._long_opts + option._short_opts:
4403
pointer = head
4404
4405
for char in opt:
4406
if char == "-":
4407
continue
4408
elif char not in pointer.next:
4409
pointer.next[char] = MnemonicNode()
4410
4411
pointer = pointer.next[char]
4412
pointer.current.append(option)
4413
4414
for mnemonic in (mnemonics or "").split(','):
4415
found = None
4416
name = mnemonic.split('=')[0].replace('-', "").strip()
4417
value = mnemonic.split('=')[1] if len(mnemonic.split('=')) > 1 else None
4418
pointer = head
4419
4420
for char in name:
4421
if char in pointer.next:
4422
pointer = pointer.next[char]
4423
else:
4424
pointer = None
4425
break
4426
4427
if pointer in (None, head):
4428
errMsg = "mnemonic '%s' can't be resolved to any parameter name" % name
4429
raise SqlmapSyntaxException(errMsg)
4430
4431
elif len(pointer.current) > 1:
4432
options = {}
4433
4434
for option in pointer.current:
4435
for opt in option._long_opts + option._short_opts:
4436
opt = opt.strip('-')
4437
if opt.startswith(name):
4438
options[opt] = option
4439
4440
if not options:
4441
warnMsg = "mnemonic '%s' can't be resolved" % name
4442
logger.warning(warnMsg)
4443
elif name in options:
4444
found = name
4445
debugMsg = "mnemonic '%s' resolved to %s). " % (name, found)
4446
logger.debug(debugMsg)
4447
else:
4448
found = sorted(options.keys(), key=len)[0]
4449
warnMsg = "detected ambiguity (mnemonic '%s' can be resolved to any of: %s). " % (name, ", ".join("'%s'" % key for key in options))
4450
warnMsg += "Resolved to shortest of those ('%s')" % found
4451
logger.warning(warnMsg)
4452
4453
if found:
4454
found = options[found]
4455
else:
4456
found = pointer.current[0]
4457
debugMsg = "mnemonic '%s' resolved to %s). " % (name, found)
4458
logger.debug(debugMsg)
4459
4460
if found:
4461
try:
4462
value = found.convert_value(found, value)
4463
except OptionValueError:
4464
value = None
4465
4466
if value is not None:
4467
setattr(args, found.dest, value)
4468
elif not found.type: # boolean
4469
setattr(args, found.dest, True)
4470
else:
4471
errMsg = "mnemonic '%s' requires value of type '%s'" % (name, found.type)
4472
raise SqlmapSyntaxException(errMsg)
4473
4474
def safeCSValue(value):
4475
"""
4476
Returns value safe for CSV dumping
4477
4478
# Reference: http://tools.ietf.org/html/rfc4180
4479
4480
>>> safeCSValue('foo, bar')
4481
'"foo, bar"'
4482
>>> safeCSValue('foobar')
4483
'foobar'
4484
"""
4485
4486
retVal = value
4487
4488
if retVal and isinstance(retVal, six.string_types):
4489
if not (retVal[0] == retVal[-1] == '"'):
4490
if any(_ in retVal for _ in (conf.get("csvDel", defaults.csvDel), '"', '\n')):
4491
retVal = '"%s"' % retVal.replace('"', '""')
4492
4493
return retVal
4494
4495
def filterPairValues(values):
4496
"""
4497
Returns only list-like values with length 2
4498
4499
>>> filterPairValues([[1, 2], [3], 1, [4, 5]])
4500
[[1, 2], [4, 5]]
4501
"""
4502
4503
retVal = []
4504
4505
if not isNoneValue(values) and hasattr(values, '__iter__'):
4506
retVal = [value for value in values if isinstance(value, (tuple, list, set)) and len(value) == 2]
4507
4508
return retVal
4509
4510
def randomizeParameterValue(value):
4511
"""
4512
Randomize a parameter value based on occurrences of alphanumeric characters
4513
4514
>>> random.seed(0)
4515
>>> randomizeParameterValue('foobar')
4516
'fupgpy'
4517
>>> randomizeParameterValue('17')
4518
'36'
4519
"""
4520
4521
retVal = value
4522
4523
retVal = re.sub(r"%[0-9a-fA-F]{2}", "", retVal)
4524
4525
def _replace_upper(match):
4526
original = match.group()
4527
while True:
4528
candidate = randomStr(len(original)).upper()
4529
if candidate != original:
4530
return candidate
4531
4532
def _replace_lower(match):
4533
original = match.group()
4534
while True:
4535
candidate = randomStr(len(original)).lower()
4536
if candidate != original:
4537
return candidate
4538
4539
def _replace_digit(match):
4540
original = match.group()
4541
while True:
4542
candidate = str(randomInt(len(original)))
4543
if candidate != original:
4544
return candidate
4545
4546
retVal = re.sub(r"[A-Z]+", _replace_upper, retVal)
4547
retVal = re.sub(r"[a-z]+", _replace_lower, retVal)
4548
retVal = re.sub(r"[0-9]+", _replace_digit, retVal)
4549
4550
if re.match(r"\A[^@]+@.+\.[a-z]+\Z", value):
4551
parts = retVal.split('.')
4552
parts[-1] = random.sample(RANDOMIZATION_TLDS, 1)[0]
4553
retVal = '.'.join(parts)
4554
4555
if not retVal:
4556
retVal = randomStr(lowercase=True)
4557
4558
return retVal
4559
4560
@cachedmethod
4561
def asciifyUrl(url, forceQuote=False):
4562
"""
4563
Attempts to make a unicode URL usable with ``urllib/urllib2``.
4564
4565
More specifically, it attempts to convert the unicode object ``url``,
4566
which is meant to represent a IRI, to an unicode object that,
4567
containing only ASCII characters, is a valid URI. This involves:
4568
4569
* IDNA/Puny-encoding the domain name.
4570
* UTF8-quoting the path and querystring parts.
4571
4572
See also RFC 3987.
4573
4574
# Reference: http://blog.elsdoerfer.name/2008/12/12/opening-iris-in-python/
4575
4576
>>> asciifyUrl(u'http://www.\\u0161u\\u0107uraj.com')
4577
'http://www.xn--uuraj-gxa24d.com'
4578
"""
4579
4580
parts = _urllib.parse.urlsplit(url)
4581
if not all((parts.scheme, parts.netloc, parts.hostname)):
4582
# apparently not an url
4583
return getText(url)
4584
4585
if all(char in string.printable for char in url):
4586
return getText(url)
4587
4588
hostname = parts.hostname
4589
4590
if isinstance(hostname, six.binary_type):
4591
hostname = getUnicode(hostname)
4592
4593
# idna-encode domain
4594
try:
4595
hostname = hostname.encode("idna")
4596
except:
4597
hostname = hostname.encode("punycode")
4598
4599
# UTF8-quote the other parts. We check each part individually if
4600
# if needs to be quoted - that should catch some additional user
4601
# errors, say for example an umlaut in the username even though
4602
# the path *is* already quoted.
4603
def quote(s, safe):
4604
s = s or ''
4605
# Triggers on non-ascii characters - another option would be:
4606
# _urllib.parse.quote(s.replace('%', '')) != s.replace('%', '')
4607
# which would trigger on all %-characters, e.g. "&".
4608
if getUnicode(s).encode("ascii", "replace") != s or forceQuote:
4609
s = _urllib.parse.quote(getBytes(s), safe=safe)
4610
return s
4611
4612
username = quote(parts.username, '')
4613
password = quote(parts.password, safe='')
4614
path = quote(parts.path, safe='/')
4615
query = quote(parts.query, safe="&=")
4616
4617
# put everything back together
4618
netloc = getText(hostname)
4619
if username or password:
4620
netloc = '@' + netloc
4621
if password:
4622
netloc = ':' + password + netloc
4623
netloc = username + netloc
4624
4625
try:
4626
port = parts.port
4627
except:
4628
port = None
4629
4630
if port:
4631
netloc += ':' + str(port)
4632
4633
return getText(_urllib.parse.urlunsplit([parts.scheme, netloc, path, query, parts.fragment]) or url)
4634
4635
def isAdminFromPrivileges(privileges):
4636
"""
4637
Inspects privileges to see if those are coming from an admin user
4638
"""
4639
4640
privileges = privileges or []
4641
4642
# In PostgreSQL the usesuper privilege means that the
4643
# user is DBA
4644
retVal = (Backend.isDbms(DBMS.PGSQL) and "super" in privileges)
4645
4646
# In Oracle the DBA privilege means that the
4647
# user is DBA
4648
retVal |= (Backend.isDbms(DBMS.ORACLE) and "DBA" in privileges)
4649
4650
# In MySQL >= 5.0 the SUPER privilege means
4651
# that the user is DBA
4652
retVal |= (Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema and "SUPER" in privileges)
4653
4654
# In MySQL < 5.0 the super_priv privilege means
4655
# that the user is DBA
4656
retVal |= (Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema and "super_priv" in privileges)
4657
4658
# In Firebird there is no specific privilege that means
4659
# that the user is DBA
4660
retVal |= (Backend.isDbms(DBMS.FIREBIRD) and all(_ in privileges for _ in ("SELECT", "INSERT", "UPDATE", "DELETE", "REFERENCES", "EXECUTE")))
4661
4662
return retVal
4663
4664
def findPageForms(content, url, raiseException=False, addToTargets=False):
4665
"""
4666
Parses given page content for possible forms (Note: still not implemented for Python3)
4667
4668
>>> findPageForms('<html><form action="/input.php" method="POST"><input type="text" name="id" value="1"><input type="submit" value="Submit"></form></html>', 'http://www.site.com') == set([('http://www.site.com/input.php', 'POST', 'id=1', None, None)])
4669
True
4670
"""
4671
4672
class _(six.StringIO, object):
4673
def __init__(self, content, url):
4674
super(_, self).__init__(content)
4675
self._url = url
4676
4677
def geturl(self):
4678
return self._url
4679
4680
if not content:
4681
errMsg = "can't parse forms as the page content appears to be blank"
4682
if raiseException:
4683
raise SqlmapGenericException(errMsg)
4684
else:
4685
logger.debug(errMsg)
4686
4687
forms = None
4688
retVal = set()
4689
response = _(content, url)
4690
4691
try:
4692
forms = ParseResponse(response, backwards_compat=False)
4693
except ParseError:
4694
if re.search(r"(?i)<!DOCTYPE html|<html", content or "") and not re.search(r"(?i)\.js(\?|\Z)", url):
4695
dbgMsg = "badly formed HTML at the given URL ('%s'). Going to filter it" % url
4696
logger.debug(dbgMsg)
4697
filtered = _("".join(re.findall(FORM_SEARCH_REGEX, content)), url)
4698
4699
if filtered and filtered != content:
4700
try:
4701
forms = ParseResponse(filtered, backwards_compat=False)
4702
except:
4703
errMsg = "no success"
4704
if raiseException:
4705
raise SqlmapGenericException(errMsg)
4706
else:
4707
logger.debug(errMsg)
4708
except:
4709
pass
4710
4711
for form in forms or []:
4712
try:
4713
for control in form.controls:
4714
if hasattr(control, "items") and not any((control.disabled, control.readonly)):
4715
# if control has selectable items select first non-disabled
4716
for item in control.items:
4717
if not item.disabled:
4718
if not item.selected:
4719
item.selected = True
4720
break
4721
4722
if conf.crawlExclude and re.search(conf.crawlExclude, form.action or ""):
4723
dbgMsg = "skipping '%s'" % form.action
4724
logger.debug(dbgMsg)
4725
continue
4726
4727
request = form.click()
4728
except (ValueError, TypeError) as ex:
4729
errMsg = "there has been a problem while "
4730
errMsg += "processing page forms ('%s')" % getSafeExString(ex)
4731
if raiseException:
4732
raise SqlmapGenericException(errMsg)
4733
else:
4734
logger.debug(errMsg)
4735
else:
4736
url = urldecode(request.get_full_url(), kb.pageEncoding)
4737
method = request.get_method()
4738
data = unArrayizeValue(request.data)
4739
data = urldecode(data, kb.pageEncoding, spaceplus=False)
4740
4741
if not data and method and method.upper() == HTTPMETHOD.POST:
4742
debugMsg = "invalid POST form with blank data detected"
4743
logger.debug(debugMsg)
4744
continue
4745
4746
# flag to know if we are dealing with the same target host
4747
_ = checkSameHost(response.geturl(), url)
4748
4749
if data:
4750
data = data.lstrip("&=").rstrip('&')
4751
4752
if conf.scope and not re.search(conf.scope, url, re.I):
4753
continue
4754
elif data and not re.sub(r"(%s)=[^&]*&?" % '|'.join(IGNORE_PARAMETERS), "", data):
4755
continue
4756
elif not _:
4757
continue
4758
else:
4759
target = (url, method, data, conf.cookie, None)
4760
retVal.add(target)
4761
4762
for match in re.finditer(r"\.post\(['\"]([^'\"]*)['\"],\s*\{([^}]*)\}", content):
4763
url = _urllib.parse.urljoin(url, htmlUnescape(match.group(1)))
4764
data = ""
4765
4766
for name, value in re.findall(r"['\"]?(\w+)['\"]?\s*:\s*(['\"][^'\"]+)?", match.group(2)):
4767
data += "%s=%s%s" % (name, value, DEFAULT_GET_POST_DELIMITER)
4768
4769
data = data.rstrip(DEFAULT_GET_POST_DELIMITER)
4770
retVal.add((url, HTTPMETHOD.POST, data, conf.cookie, None))
4771
4772
for match in re.finditer(r"(?s)(\w+)\.open\(['\"]POST['\"],\s*['\"]([^'\"]+)['\"]\).*?\1\.send\(([^)]+)\)", content):
4773
url = _urllib.parse.urljoin(url, htmlUnescape(match.group(2)))
4774
data = match.group(3)
4775
4776
data = re.sub(r"\s*\+\s*[^\s'\"]+|[^\s'\"]+\s*\+\s*", "", data)
4777
4778
data = data.strip("['\"]")
4779
retVal.add((url, HTTPMETHOD.POST, data, conf.cookie, None))
4780
4781
if not retVal and not conf.crawlDepth:
4782
errMsg = "there were no forms found at the given target URL"
4783
if raiseException:
4784
raise SqlmapGenericException(errMsg)
4785
else:
4786
logger.debug(errMsg)
4787
4788
if addToTargets and retVal:
4789
for target in retVal:
4790
kb.targets.add(target)
4791
4792
return retVal
4793
4794
def checkSameHost(*urls):
4795
"""
4796
Returns True if all provided urls share that same host
4797
4798
>>> checkSameHost('http://www.target.com/page1.php?id=1', 'http://www.target.com/images/page2.php')
4799
True
4800
>>> checkSameHost('http://www.target.com/page1.php?id=1', 'http://www.target2.com/images/page2.php')
4801
False
4802
"""
4803
4804
if not urls:
4805
return None
4806
elif len(urls) == 1:
4807
return True
4808
else:
4809
def _(value):
4810
if value and not re.search(r"\A\w+://", value):
4811
value = "http://%s" % value
4812
return value
4813
4814
first = _urllib.parse.urlparse(_(urls[0]) or "").hostname or ""
4815
first = re.sub(r"(?i)\Awww\.", "", first)
4816
4817
for url in urls[1:]:
4818
current = _urllib.parse.urlparse(_(url) or "").hostname or ""
4819
current = re.sub(r"(?i)\Awww\.", "", current)
4820
4821
if current != first:
4822
return False
4823
4824
return True
4825
4826
def getHostHeader(url):
4827
"""
4828
Returns proper Host header value for a given target URL
4829
4830
>>> getHostHeader('http://www.target.com/vuln.php?id=1')
4831
'www.target.com'
4832
"""
4833
4834
retVal = url
4835
4836
if url:
4837
retVal = _urllib.parse.urlparse(url).netloc
4838
4839
if re.search(r"http(s)?://\[.+\]", url, re.I):
4840
retVal = extractRegexResult(r"http(s)?://\[(?P<result>.+)\]", url)
4841
elif any(retVal.endswith(':%d' % _) for _ in (80, 443)):
4842
retVal = retVal.split(':')[0]
4843
4844
if retVal and retVal.count(':') > 1 and not any(_ in retVal for _ in ('[', ']')):
4845
retVal = "[%s]" % retVal
4846
4847
return retVal
4848
4849
def checkOldOptions(args):
4850
"""
4851
Checks for obsolete/deprecated options
4852
"""
4853
4854
for _ in args:
4855
_ = _.split('=')[0].strip()
4856
if _ in OBSOLETE_OPTIONS:
4857
errMsg = "switch/option '%s' is obsolete" % _
4858
if OBSOLETE_OPTIONS[_]:
4859
errMsg += " (hint: %s)" % OBSOLETE_OPTIONS[_]
4860
raise SqlmapSyntaxException(errMsg)
4861
elif _ in DEPRECATED_OPTIONS:
4862
warnMsg = "switch/option '%s' is deprecated" % _
4863
if DEPRECATED_OPTIONS[_]:
4864
warnMsg += " (hint: %s)" % DEPRECATED_OPTIONS[_]
4865
logger.warning(warnMsg)
4866
4867
def checkSystemEncoding():
4868
"""
4869
Checks for problematic encodings
4870
"""
4871
4872
if sys.getdefaultencoding() == "cp720":
4873
try:
4874
codecs.lookup("cp720")
4875
except LookupError:
4876
errMsg = "there is a known Python issue (#1616979) related "
4877
errMsg += "to support for charset 'cp720'. Please visit "
4878
errMsg += "'http://blog.oneortheother.info/tip/python-fix-cp720-encoding/index.html' "
4879
errMsg += "and follow the instructions to be able to fix it"
4880
logger.critical(errMsg)
4881
4882
warnMsg = "temporary switching to charset 'cp1256'"
4883
logger.warning(warnMsg)
4884
4885
_reload_module(sys)
4886
sys.setdefaultencoding("cp1256")
4887
4888
def evaluateCode(code, variables=None):
4889
"""
4890
Executes given python code given in a string form
4891
4892
>>> _ = {}; evaluateCode("a = 1; b = 2; c = a", _); _["c"]
4893
1
4894
"""
4895
4896
try:
4897
exec(code, variables)
4898
except KeyboardInterrupt:
4899
raise
4900
except Exception as ex:
4901
errMsg = "an error occurred while evaluating provided code ('%s') " % getSafeExString(ex)
4902
raise SqlmapGenericException(errMsg)
4903
4904
def serializeObject(object_):
4905
"""
4906
Serializes given object
4907
4908
>>> type(serializeObject([1, 2, 3, ('a', 'b')])) == str
4909
True
4910
"""
4911
4912
return base64pickle(object_)
4913
4914
def unserializeObject(value):
4915
"""
4916
Unserializes object from given serialized form
4917
4918
>>> unserializeObject(serializeObject([1, 2, 3])) == [1, 2, 3]
4919
True
4920
>>> unserializeObject('gAJVBmZvb2JhcnEBLg==')
4921
'foobar'
4922
"""
4923
4924
return base64unpickle(value) if value else None
4925
4926
def resetCounter(technique):
4927
"""
4928
Resets query counter for a given technique
4929
"""
4930
4931
kb.counters[technique] = 0
4932
4933
def incrementCounter(technique):
4934
"""
4935
Increments query counter for a given technique
4936
"""
4937
4938
kb.counters[technique] = getCounter(technique) + 1
4939
4940
def getCounter(technique):
4941
"""
4942
Returns query counter for a given technique
4943
4944
>>> resetCounter(PAYLOAD.TECHNIQUE.STACKED); incrementCounter(PAYLOAD.TECHNIQUE.STACKED); getCounter(PAYLOAD.TECHNIQUE.STACKED)
4945
1
4946
"""
4947
4948
return kb.counters.get(technique, 0)
4949
4950
def applyFunctionRecursively(value, function):
4951
"""
4952
Applies function recursively through list-like structures
4953
4954
>>> applyFunctionRecursively([1, 2, [3, 4, [19]], -9], lambda _: _ > 0)
4955
[True, True, [True, True, [True]], False]
4956
"""
4957
4958
if isListLike(value):
4959
retVal = [applyFunctionRecursively(_, function) for _ in value]
4960
else:
4961
retVal = function(value)
4962
4963
return retVal
4964
4965
def decodeDbmsHexValue(value, raw=False):
4966
"""
4967
Returns value decoded from DBMS specific hexadecimal representation
4968
4969
>>> decodeDbmsHexValue('3132332031') == u'123 1'
4970
True
4971
>>> decodeDbmsHexValue('31003200330020003100') == u'123 1'
4972
True
4973
>>> decodeDbmsHexValue('00310032003300200031') == u'123 1'
4974
True
4975
>>> decodeDbmsHexValue('0x31003200330020003100') == u'123 1'
4976
True
4977
>>> decodeDbmsHexValue('313233203') == u'123 ?'
4978
True
4979
>>> decodeDbmsHexValue(['0x31', '0x32']) == [u'1', u'2']
4980
True
4981
>>> decodeDbmsHexValue('5.1.41') == u'5.1.41'
4982
True
4983
"""
4984
4985
retVal = value
4986
4987
def _(value):
4988
retVal = value
4989
if value and isinstance(value, six.string_types):
4990
value = value.strip()
4991
4992
if len(value) % 2 != 0:
4993
retVal = (decodeHex(value[:-1]) + b'?') if len(value) > 1 else value
4994
singleTimeWarnMessage("there was a problem decoding value '%s' from expected hexadecimal form" % value)
4995
else:
4996
retVal = decodeHex(value)
4997
4998
if not raw:
4999
if not kb.binaryField:
5000
if Backend.isDbms(DBMS.MSSQL) and value.startswith("0x"):
5001
try:
5002
retVal = retVal.decode("utf-16-le")
5003
except UnicodeDecodeError:
5004
pass
5005
5006
elif Backend.getIdentifiedDbms() in (DBMS.HSQLDB, DBMS.H2):
5007
try:
5008
retVal = retVal.decode("utf-16-be")
5009
except UnicodeDecodeError:
5010
pass
5011
5012
if not isinstance(retVal, six.text_type):
5013
retVal = getUnicode(retVal, conf.encoding or UNICODE_ENCODING)
5014
5015
if u"\x00" in retVal:
5016
retVal = retVal.replace(u"\x00", u"")
5017
5018
return retVal
5019
5020
try:
5021
retVal = applyFunctionRecursively(value, _)
5022
except:
5023
singleTimeWarnMessage("there was a problem decoding value '%s' from expected hexadecimal form" % value)
5024
5025
return retVal
5026
5027
def extractExpectedValue(value, expected):
5028
"""
5029
Extracts and returns expected value by a given type
5030
5031
>>> extractExpectedValue(['1'], EXPECTED.BOOL)
5032
True
5033
>>> extractExpectedValue(['17'], EXPECTED.BOOL)
5034
True
5035
>>> extractExpectedValue(['0'], EXPECTED.BOOL)
5036
False
5037
>>> extractExpectedValue('1', EXPECTED.INT)
5038
1
5039
>>> extractExpectedValue('7\\xb9645', EXPECTED.INT) is None
5040
True
5041
"""
5042
5043
if expected:
5044
value = unArrayizeValue(value)
5045
5046
if isNoneValue(value):
5047
value = None
5048
elif expected == EXPECTED.BOOL:
5049
if isinstance(value, int):
5050
value = bool(value)
5051
elif isinstance(value, six.string_types):
5052
value = value.strip().lower()
5053
if value in ("true", "false"):
5054
value = value == "true"
5055
elif value in ('t', 'f'):
5056
value = value == 't'
5057
elif value == '0':
5058
value = False
5059
elif re.search(r"\A-?[1-9]\d*\Z", value):
5060
value = True
5061
else:
5062
value = None
5063
elif expected == EXPECTED.INT:
5064
try:
5065
value = int(value)
5066
except:
5067
value = None
5068
5069
return value
5070
5071
def hashDBWrite(key, value, serialize=False):
5072
"""
5073
Helper function for writing session data to HashDB
5074
"""
5075
5076
if conf.hashDB:
5077
_ = '|'.join((str(_) if not isinstance(_, six.string_types) else _) for _ in (conf.hostname, conf.path.strip('/') if conf.path is not None else conf.port, key, HASHDB_MILESTONE_VALUE))
5078
conf.hashDB.write(_, value, serialize)
5079
5080
def hashDBRetrieve(key, unserialize=False, checkConf=False):
5081
"""
5082
Helper function for restoring session data from HashDB
5083
"""
5084
5085
retVal = None
5086
5087
if conf.hashDB:
5088
_ = '|'.join((str(_) if not isinstance(_, six.string_types) else _) for _ in (conf.hostname, conf.path.strip('/') if conf.path is not None else conf.port, key, HASHDB_MILESTONE_VALUE))
5089
retVal = conf.hashDB.retrieve(_, unserialize) if kb.resumeValues and not (checkConf and any((conf.flushSession, conf.freshQueries))) else None
5090
5091
if not kb.inferenceMode and not kb.fileReadMode and isinstance(retVal, six.string_types) and any(_ in retVal for _ in (PARTIAL_VALUE_MARKER, PARTIAL_HEX_VALUE_MARKER)):
5092
retVal = None
5093
5094
return retVal
5095
5096
def resetCookieJar(cookieJar):
5097
"""
5098
Cleans cookies from a given cookie jar
5099
"""
5100
5101
if not conf.loadCookies:
5102
cookieJar.clear()
5103
else:
5104
try:
5105
if not cookieJar.filename:
5106
infoMsg = "loading cookies from '%s'" % conf.loadCookies
5107
logger.info(infoMsg)
5108
5109
content = readCachedFileContent(conf.loadCookies)
5110
content = re.sub("(?im)^#httpOnly_", "", content)
5111
lines = filterNone(line.strip() for line in content.split("\n") if not line.startswith('#'))
5112
handle, filename = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.COOKIE_JAR)
5113
os.close(handle)
5114
5115
# Reference: http://www.hashbangcode.com/blog/netscape-http-cooke-file-parser-php-584.html
5116
with openFile(filename, "w+") as f:
5117
f.write("%s\n" % NETSCAPE_FORMAT_HEADER_COOKIES)
5118
for line in lines:
5119
_ = line.split("\t")
5120
if len(_) == 7:
5121
_[4] = FORCE_COOKIE_EXPIRATION_TIME
5122
f.write("\n%s" % "\t".join(_))
5123
5124
cookieJar.filename = filename
5125
5126
cookieJar.load(cookieJar.filename, ignore_expires=True)
5127
5128
for cookie in cookieJar:
5129
if getattr(cookie, "expires", MAX_INT) < time.time():
5130
warnMsg = "cookie '%s' has expired" % cookie
5131
singleTimeWarnMessage(warnMsg)
5132
5133
cookieJar.clear_expired_cookies()
5134
5135
if not cookieJar._cookies:
5136
errMsg = "no valid cookies found"
5137
raise SqlmapGenericException(errMsg)
5138
5139
except Exception as ex:
5140
errMsg = "there was a problem loading "
5141
errMsg += "cookies file ('%s')" % re.sub(r"(cookies) file '[^']+'", r"\g<1>", getSafeExString(ex))
5142
raise SqlmapGenericException(errMsg)
5143
5144
def decloakToTemp(filename):
5145
"""
5146
Decloaks content of a given file to a temporary file with similar name and extension
5147
5148
NOTE: using in-memory decloak() in docTests because of the "problem" on Windows platform
5149
5150
>>> decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stagers", "stager.asp_")).startswith(b'<%')
5151
True
5152
>>> decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "backdoors", "backdoor.asp_")).startswith(b'<%')
5153
True
5154
>>> b'sys_eval' in decloak(os.path.join(paths.SQLMAP_UDF_PATH, "postgresql", "linux", "64", "11", "lib_postgresqludf_sys.so_"))
5155
True
5156
"""
5157
5158
content = decloak(filename)
5159
5160
parts = os.path.split(filename[:-1])[-1].split('.')
5161
prefix, suffix = parts[0], '.' + parts[-1]
5162
handle, filename = tempfile.mkstemp(prefix=prefix, suffix=suffix)
5163
os.close(handle)
5164
5165
with openFile(filename, "w+b", encoding=None) as f:
5166
f.write(content)
5167
5168
return filename
5169
5170
def prioritySortColumns(columns):
5171
"""
5172
Sorts given column names by length in ascending order while those containing
5173
string 'id' go first
5174
5175
>>> prioritySortColumns(['password', 'userid', 'name', 'id'])
5176
['id', 'userid', 'name', 'password']
5177
"""
5178
5179
recompile = re.compile(r"^id|id$", re.I)
5180
5181
return sorted(columns, key=lambda col: (
5182
not (col and recompile.search(col)),
5183
len(col)
5184
))
5185
5186
def getRequestHeader(request, name):
5187
"""
5188
Solving an issue with an urllib2 Request header case sensitivity
5189
5190
# Reference: http://bugs.python.org/issue2275
5191
5192
>>> _ = lambda _: _
5193
>>> _.headers = {"FOO": "BAR"}
5194
>>> _.header_items = lambda: _.headers.items()
5195
>>> getText(getRequestHeader(_, "foo"))
5196
'BAR'
5197
"""
5198
5199
retVal = None
5200
5201
if request and request.headers and name:
5202
_ = name.upper()
5203
retVal = max(getBytes(value if _ == key.upper() else "") for key, value in request.header_items()) or None
5204
5205
return retVal
5206
5207
def isNumber(value):
5208
"""
5209
Returns True if the given value is a number-like object
5210
5211
>>> isNumber(1)
5212
True
5213
>>> isNumber('0')
5214
True
5215
>>> isNumber('foobar')
5216
False
5217
"""
5218
5219
try:
5220
float(value)
5221
except:
5222
return False
5223
else:
5224
return True
5225
5226
def zeroDepthSearch(expression, value):
5227
"""
5228
Searches occurrences of value inside expression at 0-depth level
5229
regarding the parentheses
5230
5231
>>> _ = "SELECT (SELECT id FROM users WHERE 2>1) AS result FROM DUAL"; _[zeroDepthSearch(_, "FROM")[0]:]
5232
'FROM DUAL'
5233
>>> _ = "a(b; c),d;e"; _[zeroDepthSearch(_, "[;, ]")[0]:]
5234
',d;e'
5235
"""
5236
5237
retVal = []
5238
5239
depth = 0
5240
for index in xrange(len(expression)):
5241
if expression[index] == '(':
5242
depth += 1
5243
elif expression[index] == ')':
5244
depth -= 1
5245
elif depth == 0:
5246
if value.startswith('[') and value.endswith(']'):
5247
if re.search(value, expression[index:index + 1]):
5248
retVal.append(index)
5249
elif expression[index:index + len(value)] == value:
5250
retVal.append(index)
5251
5252
return retVal
5253
5254
def splitFields(fields, delimiter=','):
5255
"""
5256
Returns list of (0-depth) fields splitted by delimiter
5257
5258
>>> splitFields('foo, bar, max(foo, bar)')
5259
['foo', 'bar', 'max(foo,bar)']
5260
"""
5261
5262
fields = fields.replace("%s " % delimiter, delimiter)
5263
commas = [-1, len(fields)]
5264
commas.extend(zeroDepthSearch(fields, ','))
5265
commas = sorted(commas)
5266
5267
return [fields[x + 1:y] for (x, y) in _zip(commas, commas[1:])]
5268
5269
def pollProcess(process, suppress_errors=False):
5270
"""
5271
Checks for process status (prints . if still running)
5272
"""
5273
5274
while process:
5275
dataToStdout(".")
5276
time.sleep(1)
5277
5278
returncode = process.poll()
5279
5280
if returncode is not None:
5281
if not suppress_errors:
5282
if returncode == 0:
5283
dataToStdout(" done\n")
5284
elif returncode < 0:
5285
dataToStdout(" process terminated by signal %d\n" % returncode)
5286
elif returncode > 0:
5287
dataToStdout(" quit unexpectedly with return code %d\n" % returncode)
5288
5289
break
5290
5291
def parseRequestFile(reqFile, checkParams=True):
5292
"""
5293
Parses WebScarab and Burp logs and adds results to the target URL list
5294
5295
>>> handle, reqFile = tempfile.mkstemp(suffix=".req")
5296
>>> content = b"POST / HTTP/1.0\\nUser-agent: foobar\\nHost: www.example.com\\n\\nid=1\\n"
5297
>>> _ = os.write(handle, content)
5298
>>> os.close(handle)
5299
>>> next(parseRequestFile(reqFile)) == ('http://www.example.com:80/', 'POST', 'id=1', None, (('User-agent', 'foobar'), ('Host', 'www.example.com')))
5300
True
5301
"""
5302
5303
def _parseWebScarabLog(content):
5304
"""
5305
Parses WebScarab logs (POST method not supported)
5306
"""
5307
5308
if WEBSCARAB_SPLITTER not in content:
5309
return
5310
5311
reqResList = content.split(WEBSCARAB_SPLITTER)
5312
5313
for request in reqResList:
5314
url = extractRegexResult(r"URL: (?P<result>.+?)\n", request, re.I)
5315
method = extractRegexResult(r"METHOD: (?P<result>.+?)\n", request, re.I)
5316
cookie = extractRegexResult(r"COOKIE: (?P<result>.+?)\n", request, re.I)
5317
5318
if not method or not url:
5319
logger.debug("not a valid WebScarab log data")
5320
continue
5321
5322
if method.upper() == HTTPMETHOD.POST:
5323
warnMsg = "POST requests from WebScarab logs aren't supported "
5324
warnMsg += "as their body content is stored in separate files. "
5325
warnMsg += "Nevertheless you can use -r to load them individually."
5326
logger.warning(warnMsg)
5327
continue
5328
5329
if not (conf.scope and not re.search(conf.scope, url, re.I)):
5330
yield (url, method, None, cookie, tuple())
5331
5332
def _parseBurpLog(content):
5333
"""
5334
Parses Burp logs
5335
"""
5336
5337
if not re.search(BURP_REQUEST_REGEX, content, re.I | re.S):
5338
if re.search(BURP_XML_HISTORY_REGEX, content, re.I | re.S):
5339
reqResList = []
5340
for match in re.finditer(BURP_XML_HISTORY_REGEX, content, re.I | re.S):
5341
port, request = match.groups()
5342
try:
5343
request = decodeBase64(request, binary=False)
5344
except (binascii.Error, TypeError):
5345
continue
5346
_ = re.search(r"%s:.+" % re.escape(HTTP_HEADER.HOST), request)
5347
if _:
5348
host = _.group(0).strip()
5349
if not re.search(r":\d+\Z", host) and int(port) != 80:
5350
request = request.replace(host, "%s:%d" % (host, int(port)))
5351
reqResList.append(request)
5352
else:
5353
reqResList = [content]
5354
else:
5355
reqResList = re.finditer(BURP_REQUEST_REGEX, content, re.I | re.S)
5356
5357
for match in reqResList:
5358
request = match if isinstance(match, six.string_types) else match.group(1)
5359
request = re.sub(r"\A[^\w]+", "", request)
5360
schemePort = re.search(r"(http[\w]*)\:\/\/.*?\:([\d]+).+?={10,}", request, re.I | re.S)
5361
5362
if schemePort:
5363
scheme = schemePort.group(1)
5364
port = schemePort.group(2)
5365
request = re.sub(r"\n=+\Z", "", request.split(schemePort.group(0))[-1].lstrip())
5366
else:
5367
scheme, port = None, None
5368
5369
if "HTTP/" not in request:
5370
continue
5371
5372
if re.search(r"^[\n]*%s[^?]*?\.(%s)\sHTTP\/" % (HTTPMETHOD.GET, "|".join(CRAWL_EXCLUDE_EXTENSIONS)), request, re.I | re.M):
5373
if not re.search(r"^[\n]*%s[^\n]*\*[^\n]*\sHTTP\/" % HTTPMETHOD.GET, request, re.I | re.M):
5374
continue
5375
5376
getPostReq = False
5377
forceBody = False
5378
url = None
5379
host = None
5380
method = None
5381
data = None
5382
cookie = None
5383
params = False
5384
newline = None
5385
lines = request.split('\n')
5386
headers = []
5387
5388
for index in xrange(len(lines)):
5389
line = lines[index]
5390
5391
if not line.strip() and index == len(lines) - 1:
5392
break
5393
5394
line = re.sub(INJECT_HERE_REGEX, CUSTOM_INJECTION_MARK_CHAR, line)
5395
5396
newline = "\r\n" if line.endswith('\r') else '\n'
5397
line = line.strip('\r')
5398
match = re.search(r"\A([A-Z]+) (.+) HTTP/[\d.]+\Z", line) if not method else None
5399
5400
if len(line.strip()) == 0 and method and (method != HTTPMETHOD.GET or forceBody) and data is None:
5401
data = ""
5402
params = True
5403
5404
elif match:
5405
method = match.group(1)
5406
url = match.group(2)
5407
5408
if any(_ in line for _ in ('?', '=', kb.customInjectionMark)):
5409
params = True
5410
5411
getPostReq = True
5412
5413
# POST parameters
5414
elif data is not None and params:
5415
data += "%s%s" % (line, newline)
5416
5417
# GET parameters
5418
elif "?" in line and "=" in line and ": " not in line:
5419
params = True
5420
5421
# Headers
5422
elif re.search(r"\A\S+:", line):
5423
key, value = line.split(":", 1)
5424
value = value.strip().replace("\r", "").replace("\n", "")
5425
5426
# Note: overriding values with --headers '...'
5427
match = re.search(r"(?i)\b(%s): ([^\n]*)" % re.escape(key), conf.headers or "")
5428
if match:
5429
key, value = match.groups()
5430
5431
# Cookie and Host headers
5432
if key.upper() == HTTP_HEADER.COOKIE.upper():
5433
cookie = value
5434
elif key.upper() == HTTP_HEADER.HOST.upper():
5435
if '://' in value:
5436
scheme, value = value.split('://')[:2]
5437
5438
port = extractRegexResult(r":(?P<result>\d+)\Z", value)
5439
if port:
5440
host = value[:-(1 + len(port))]
5441
else:
5442
host = value
5443
5444
# Avoid to add a static content length header to
5445
# headers and consider the following lines as
5446
# POSTed data
5447
if key.upper() == HTTP_HEADER.CONTENT_LENGTH.upper():
5448
forceBody = True
5449
params = True
5450
5451
# Avoid proxy and connection type related headers
5452
elif key not in (HTTP_HEADER.PROXY_CONNECTION, HTTP_HEADER.CONNECTION, HTTP_HEADER.IF_MODIFIED_SINCE, HTTP_HEADER.IF_NONE_MATCH):
5453
headers.append((getUnicode(key), getUnicode(value)))
5454
5455
if kb.customInjectionMark in re.sub(PROBLEMATIC_CUSTOM_INJECTION_PATTERNS, "", value or ""):
5456
params = True
5457
5458
data = data.rstrip("\r\n") if data else data
5459
5460
if getPostReq and (params or cookie or not checkParams):
5461
if not port and hasattr(scheme, "lower") and scheme.lower() == "https":
5462
port = "443"
5463
elif not scheme and port == "443":
5464
scheme = "https"
5465
5466
if conf.forceSSL:
5467
scheme = "https"
5468
port = port or "443"
5469
5470
if not host:
5471
errMsg = "invalid format of a request file"
5472
raise SqlmapSyntaxException(errMsg)
5473
5474
if not url.startswith("http"):
5475
url = "%s://%s:%s%s" % (scheme or "http", host, port or "80", url)
5476
scheme = None
5477
port = None
5478
5479
if not (conf.scope and not re.search(conf.scope, url, re.I)):
5480
yield (url, conf.method or method, data, cookie, tuple(headers))
5481
5482
content = readCachedFileContent(reqFile)
5483
5484
if conf.scope:
5485
logger.info("using regular expression '%s' for filtering targets" % conf.scope)
5486
5487
try:
5488
re.compile(conf.scope)
5489
except Exception as ex:
5490
errMsg = "invalid regular expression '%s' ('%s')" % (conf.scope, getSafeExString(ex))
5491
raise SqlmapSyntaxException(errMsg)
5492
5493
for target in _parseBurpLog(content):
5494
yield target
5495
5496
for target in _parseWebScarabLog(content):
5497
yield target
5498
5499
def getSafeExString(ex, encoding=None):
5500
"""
5501
Safe way how to get the proper exception represtation as a string
5502
5503
>>> getSafeExString(SqlmapBaseException('foobar')) == 'foobar'
5504
True
5505
>>> getSafeExString(OSError(0, 'foobar')) == 'OSError: foobar'
5506
True
5507
"""
5508
5509
retVal = None
5510
5511
if getattr(ex, "message", None):
5512
retVal = ex.message
5513
elif getattr(ex, "msg", None):
5514
retVal = ex.msg
5515
elif getattr(ex, "args", None):
5516
for candidate in ex.args[::-1]:
5517
if isinstance(candidate, six.string_types):
5518
retVal = candidate
5519
break
5520
5521
if retVal is None:
5522
retVal = str(ex)
5523
elif not isinstance(ex, SqlmapBaseException):
5524
retVal = "%s: %s" % (type(ex).__name__, retVal)
5525
5526
return getUnicode(retVal or "", encoding=encoding).strip()
5527
5528
def safeVariableNaming(value):
5529
"""
5530
Returns escaped safe-representation of a given variable name that can be used in Python evaluated code
5531
5532
>>> safeVariableNaming("class.id") == "EVAL_636c6173732e6964"
5533
True
5534
"""
5535
5536
if value in keyword.kwlist or re.search(r"\A[^a-zA-Z]|[^\w]", value):
5537
value = "%s%s" % (EVALCODE_ENCODED_PREFIX, getUnicode(binascii.hexlify(getBytes(value))))
5538
5539
return value
5540
5541
def unsafeVariableNaming(value):
5542
"""
5543
Returns unescaped safe-representation of a given variable name
5544
5545
>>> unsafeVariableNaming("EVAL_636c6173732e6964") == "class.id"
5546
True
5547
"""
5548
5549
if value.startswith(EVALCODE_ENCODED_PREFIX):
5550
value = decodeHex(value[len(EVALCODE_ENCODED_PREFIX):], binary=False)
5551
5552
return value
5553
5554
def firstNotNone(*args):
5555
"""
5556
Returns first not-None value from a given list of arguments
5557
5558
>>> firstNotNone(None, None, 1, 2, 3)
5559
1
5560
"""
5561
5562
retVal = None
5563
5564
for _ in args:
5565
if _ is not None:
5566
retVal = _
5567
break
5568
5569
return retVal
5570
5571
def removePostHintPrefix(value):
5572
"""
5573
Remove POST hint prefix from a given value (name)
5574
5575
>>> removePostHintPrefix("JSON id")
5576
'id'
5577
>>> removePostHintPrefix("id")
5578
'id'
5579
"""
5580
5581
return re.sub(r"\A(%s) " % '|'.join(re.escape(__) for __ in getPublicTypeMembers(POST_HINT, onlyValues=True)), "", value)
5582
5583
5584
def chunkSplitPostData(data):
5585
"""
5586
Convert POST data to chunked transfer-encoded data (Note: splitting done by SQL keywords)
5587
5588
>>> random.seed(0)
5589
>>> chunkSplitPostData("SELECT username,password FROM users")
5590
'5;4Xe90\\r\\nSELEC\\r\\n3;irWlc\\r\\nT u\\r\\n1;eT4zO\\r\\ns\\r\\n5;YB4hM\\r\\nernam\\r\\n9;2pUD8\\r\\ne,passwor\\r\\n3;mp07y\\r\\nd F\\r\\n5;8RKXi\\r\\nROM u\\r\\n4;MvMhO\\r\\nsers\\r\\n0\\r\\n\\r\\n'
5591
"""
5592
5593
length = len(data)
5594
retVal = []
5595
index = 0
5596
5597
while index < length:
5598
chunkSize = randomInt(1)
5599
5600
if index + chunkSize >= length:
5601
chunkSize = length - index
5602
5603
salt = randomStr(5, alphabet=string.ascii_letters + string.digits)
5604
5605
while chunkSize:
5606
candidate = data[index:index + chunkSize]
5607
5608
if re.search(r"\b%s\b" % '|'.join(HTTP_CHUNKED_SPLIT_KEYWORDS), candidate, re.I):
5609
chunkSize -= 1
5610
else:
5611
break
5612
5613
index += chunkSize
5614
5615
# Append to list instead of recreating the string
5616
retVal.append("%x;%s\r\n" % (chunkSize, salt))
5617
retVal.append("%s\r\n" % candidate)
5618
5619
retVal.append("0\r\n\r\n")
5620
5621
return "".join(retVal)
5622
5623
def checkSums():
5624
"""
5625
Validate the content of the digest file (i.e. sha256sums.txt)
5626
>>> checkSums()
5627
True
5628
"""
5629
5630
retVal = True
5631
5632
if paths.get("DIGEST_FILE"):
5633
for entry in getFileItems(paths.DIGEST_FILE):
5634
match = re.search(r"([0-9a-f]+)\s+([^\s]+)", entry)
5635
if match:
5636
expected, filename = match.groups()
5637
filepath = os.path.join(paths.SQLMAP_ROOT_PATH, filename).replace('/', os.path.sep)
5638
if not checkFile(filepath, False):
5639
continue
5640
with open(filepath, "rb") as f:
5641
content = f.read()
5642
if b'\0' not in content:
5643
content = content.replace(b"\r\n", b"\n")
5644
if not hashlib.sha256(content).hexdigest() == expected:
5645
retVal &= False
5646
break
5647
5648
return retVal
5649
5650