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