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