#!/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 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 (...) VERSION = "1.10.11.9" 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 = ( "castprefix", "mid2leftright", "odbcbrace", "equaltolike", "between", "greatest", "charencode", "randomcase", "space2comment", "versionedkeywords", "space2hash", ) # Maximum number of candidate tamper (chains) trialled during automatic WAF-bypass WAF_BYPASS_MAX_TRIALS = len(WAF_BYPASS_TAMPERS) # 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 = "rie8kooZ" # 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([\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(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 = ( ("

502 Bad Gateway

", 502), ("

503 Service Unavailable

", 503), ("

504 Gateway Time-out

", 504), ('{"error": "too many requests"}', 429), ("Just a moment...Checking your browser before accessing.", 200), ("We'll be back shortly. Scheduled maintenance in progress.", 200), ("", 200), ) # Regular expression used for extracting results from DuckDuckGo search DUCKDUCKGO_REGEX = r']*>]*href="(?:https?://www\.bing\.com/ck/a\?[^"]*?[?;]u=a1([^&"]+)[^"]*|([^"]+))"' # Markers of search engine abuse/captcha pages (i.e. used IP address got blocked) SEARCH_ABUSE_MARKERS = ("detected unusual traffic", "issue with the Tor Exit Node you are currently using", "anomaly.js", "Verifying your browser", "automated queries") # 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[^<]+)" # 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: () + () 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 = "" # 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"(?P[^<>]+?) on line \d+", r"\bin (?P[^<>'\"]+?)['\"]? on line \d+", r"(?:[>(\[\s'\"])(?P[A-Za-z]:[\\/][\w. \\/-]*)", r"(?:[>(\[\s'\"])(?P/\w[/\w.~-]+)", r"\bhref=['\"]file://(?P/[^'\"]+)", r"\bin (?P[^<]+): line \d+") # Regular expressions used for parsing error messages (--parse-errors) ERROR_PARSING_REGEXES = ( r"\[Microsoft\]\[ODBC SQL Server Driver\]\[SQL Server\](?P[^<]+)", r"[^<]{0,100}(fatal|error|warning|exception)[^<]*:?\s*(?P[^<]+)", r"(?m)^\s{0,100}(fatal|error|warning|exception):?\s*(?P[^\n]+?)$", r"(sql|dbc)[^>'\"]{0,32}(fatal|error|warning|exception)()?:\s*(?P[^<>]+)", r"(?P[^\n>]{0,100}SQL Syntax[^\n<]+)", r"(?s)
  • Error Type:
    (?P.+?)
  • ", r"CDbCommand (?P[^<>\n]*SQL[^<>\n]+)", r"Code: \d+. DB::Exception: (?P[^<>\n]*)", r"error '[0-9a-f]{8}'((<[^>]+>)|\s)+(?P[^<>]+)", r"\[[^\n\]]{1,100}(ODBC|JDBC)[^\n\]]+\](\[[^\]]+\])?(?P[^\n]+(in query expression|\(SQL| at /[^ ]+pdo)[^\n<]+)", r"(?Pquery error: SELECT[^<>]+)", r"(?P(?:(?: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[^"]+)"' # 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 # '(?:(?!).)*?' keeps the meta strictly INSIDE - as the old trailing '.*' 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_CHARSET_REGEX = r"""(?si)]*>(?:(?!).)*?]{0,300}?charset\s*=\s*["']?(?P[^"'> ]+)""" # Regular expression used for parsing refresh info from meta html headers META_REFRESH_REGEX = r'(?i)]+content="?[^">]+;\s*(url=)?["\']?(?P[^\'">]+)' # Regular expression used for parsing Javascript redirect request JAVASCRIPT_HREF_REGEX = r'',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) # Scope is intentionally minimal (repo metadata read + issues write only, nothing else) - # extracting and reusing it to open issues is a known, accepted side effect of this design, # not a vulnerability (see https://github.com/sqlmapproject/sqlmap/issues/6098) 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+\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 # NOTE: the gap has to stay bounded (like in 'Handlebars' below). An unbounded '.*?' turns this # into a quadratic scan of every long line that merely carries the word 'xpath' (e.g. minified # JS/JSON), which took ~25s on a 400KB response - and blew up the regex engine itself (#6105) ("Generic XPath", r"XPath[^\n]{0,100}?(?: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[^\n]{0,100}?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