General boolean inference improvements
Some checks are pending
/ build (macos-latest, 3.8) (push) Waiting to run
/ build (ubuntu-latest, pypy-2.7) (push) Waiting to run
/ build (windows-latest, 3.14) (push) Waiting to run

This commit is contained in:
Miroslav Štampar 2026-07-06 12:02:22 +02:00
parent 6597415ab0
commit 50ff3debe5
13 changed files with 14506 additions and 1629 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -23,9 +23,9 @@ a65269dcf3cecd4be0bf6b657cbf49ac77814ac7b0e30afa1cd44bc2fed64c33 data/shell/sta
c52c17f3344707cae4c3694a979e073202bd46866fcc51d99f7e4d0c21cf335b data/shell/stagers/stager.cfm_
8cb4a001efc15bd8022d44df6eb9b2f5f5af1c64caba8f7dffde563ccba76347 data/shell/stagers/stager.jsp_
af4e1f87ec7afd12b7ddb39ff07bf24cd31be2b1de11e1be064e1dd96ff43eac data/shell/stagers/stager.php_
aa4d6396df0abde7560ced7d8f7625dd70d57401db86d205f80064609c4b2772 data/txt/catalog-identifiers.txt
eb86f6ad21e597f9283bb4360129ebc717bc8f063d7ab2298f31118275790484 data/txt/common-columns.txt
63ba15f2ba3df6e55600a2749752c82039add43ed61129febd9221eb1115f240 data/txt/common-files.txt
4d6a32155dd6b570e5cdae8036efd69d8f8ebab79cb82a4d094c15f35af8b13d data/txt/common-outputs.txt
44047281263ef297f27fdd8fa98a0b0438a25989f897ce184cb0e2e442fb6c11 data/txt/common-tables.txt
ccba96624a0176b4c5acd8824db62a8c6856dafa7d32424807f38efed22a6c29 data/txt/keywords.txt
522cce0327de8a5dfb5ade505e8a23bbd37bcabcbb2993f4f787ccdecf24997e data/txt/smalldict.txt
@ -168,7 +168,7 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py
48ffe93d61734e16c3b20153b51595853d9ac1fbcf0b537e0e61e957b0c0bfa6 lib/core/agent.py
c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigarray.py
19989ca19194bf3f7a42a929b153e45c9a2177e01ab6ab63a5372daa5989c0e8 lib/core/common.py
2b0e014869b071a2b6aa4e0c9a23427abdc61a92f09adbcce123fd0fc7ec3aa6 lib/core/common.py
8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py
5301ba2204404d086e9a67271cde00fc10214c63b018a95fc5aa90ff9e0b2ad9 lib/core/convert.py
c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py
@ -181,26 +181,26 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums
5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py
914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py
23852bdfadfb4bd5663302a63bdcc7227c0314fbdea884167d58ca21cda9fb09 lib/core/optiondict.py
0caac9b4af2cc50321a4d8126d92481ad0b092af2075e7efa19bccef529986fb lib/core/option.py
0ba8838a8a8774a8a6baaf75d8122c3fa0cb9a6e4f468a4a94f32eb894feb754 lib/core/optiondict.py
7229352618491ee1d23bbbfd6e7f160b489ce4b1a8d99c1e873d9664382c3f8c lib/core/option.py
21b2b1745107c211fc7593923a3da7a808d40763c00091c28de5f7c129bcf3bc lib/core/patch.py
49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py
0c36a65b6237732eb001d333f80f0c58c088ff01ae80cf07e4dcc6da2a806364 lib/core/readlineng.py
9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py
0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py
888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py
df067f981efe10f6743eba13c48c9c1db158ff4e9d015831e5dbfa2ece80f7bf lib/core/settings.py
b2ce6d9948284621cbfa73d81c97416a98e6cbf6f87e2c9dd0b493030ff85340 lib/core/settings.py
c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py
a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py
69a68894db04695234369eedac71b5a89efc1b4ce89ef0e61ebbbc1895ff32b2 lib/core/target.py
96d107a31bb9647a9b7c26f10beac528bf4edc6e607c8b776c624d494332c7f8 lib/core/testing.py
e08683ba2558b734562a3490caf0bdde4ae8767b81b0d89fe6db346a13a452c1 lib/core/testing.py
95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py
b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py
53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py
2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py
54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py
c9d38a60a85691cdb540e33510dd16228d6afcce0fd2ba39780f71b6da57ebb5 lib/parse/cmdline.py
ea85d5eccc6d6f6f9e4423ea97dc607ef2ca10b8264e7877c128b2252d1340e3 lib/parse/cmdline.py
925a068efa1885fa40671414a887c088f2aafbe8cb76f01286e6bde3f624dac1 lib/parse/configfile.py
c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py
5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py
@ -219,7 +219,7 @@ c968a04d3de9256d56c423d46556441223607e4573627f2af4e772e084aef5fc lib/request/dn
7344978ac1c52060716b7837c88a62768c6a445eafe189ea3232b8a498fdd038 lib/request/http2.py
92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py
7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py
7ad7ac3c7126b3bad5898803a87769f199f6e8ecfb8abdca4fc4a29e63932595 lib/request/inject.py
df97f7ccb437f9fda76b3d87cb5c11a01d09a0fa395c0d6bd555812cf92b70e6 lib/request/interactsh.py
ff15723c82e343eb95f4599d251165d478ca720afc8f5daaed3da44ea923df44 lib/request/keepalive.py
ada4d305d6ce441f79e52ec3f2fc23869ee2fa87c017723e8f3ed0dfa61cdab4 lib/request/methodrequest.py
@ -236,7 +236,7 @@ f522436fbd14bdab090a1d305fcac0361800cb8e36c8cbcb47933298376a71e0 lib/takeover/r
0787f78e6bd9bb21d4267c95c4c99806711bb57c5518485c2e25f10fcf9c41fc lib/takeover/udf.py
23d73af417604dab460b74cdc230896153f018a6c00d144019491053640a172f lib/takeover/web.py
8cc1e226d4150fe8aa1a056e5d32d858ed6444d3d4e2af7fb4bc08f0bbe9d527 lib/takeover/xp_cmdshell.py
a66a4b9df6207dce722c9b71d290ea426723cb4b697b416065dc7dd5db96fe8e lib/techniques/blind/inference.py
da4930b2499270172140ae1f12c4666d42b2f1c0c348fcb021d4dccedc91d0ac lib/techniques/blind/inference.py
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/blind/__init__.py
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/dns/__init__.py
3df9839fb92a81d46b6194d7adacb43f391efb78b071783c132e8d596ecbfaf1 lib/techniques/dns/test.py
@ -499,8 +499,8 @@ e2e20e4707abe9ed8b6208837332d2daa4eaca282f847412063f2484dcca8fbd plugins/dbms/v
2b2dad6ba1d344215cad11b629546eb9f259d7c996c202edf3de5ab22418787e plugins/dbms/virtuoso/takeover.py
51c44048e4b335b306f8ed1323fd78ad6935a8c0d6e9d6efe195a9a5a24e46dc plugins/generic/connector.py
a967f4ebd101c68a5dcc10ff18c882a8f44a5c3bf06613d951a739ecc3abb9b3 plugins/generic/custom.py
6f77b5cae6781a746f8490fe3e85456e575165b38edd280a69c9327af8bee85f plugins/generic/databases.py
13086bfae6022edc2bbd35512fa3bda3402c269e9d6148ffe386ba5b8b4ba461 plugins/generic/entries.py
dc52b79735a2e349dbc599bc7bd3f57b58560aed977f8b053f745e93532d4e13 plugins/generic/databases.py
93c4833046dce00a60cf6ca118e6637d4be23a67aa45714f91d23215d544b023 plugins/generic/entries.py
d2de7fc135cf0db3eb4ac4a509c23ebec5250a5d8043face7f8c546a09f301b5 plugins/generic/enumeration.py
8d5e3eacbd2a3cfec63fcf5bdcc8efc77656f29b11ca652c4ee60c72daea04ab plugins/generic/filesystem.py
efd7177218288f32881b69a7ba3d667dc9178f1009c06a3e1dd4f4a4ee6980db plugins/generic/fingerprint.py

View file

@ -1582,10 +1582,10 @@ def setPaths(rootPath):
paths.SQLMAP_XML_PAYLOADS_PATH = os.path.join(paths.SQLMAP_XML_PATH, "payloads")
# sqlmap files
paths.CATALOG_IDENTIFIERS = os.path.join(paths.SQLMAP_TXT_PATH, "catalog-identifiers.txt")
paths.COMMON_COLUMNS = os.path.join(paths.SQLMAP_TXT_PATH, "common-columns.txt")
paths.COMMON_FILES = os.path.join(paths.SQLMAP_TXT_PATH, "common-files.txt")
paths.COMMON_TABLES = os.path.join(paths.SQLMAP_TXT_PATH, "common-tables.txt")
paths.COMMON_OUTPUTS = os.path.join(paths.SQLMAP_TXT_PATH, 'common-outputs.txt')
paths.DIGEST_FILE = os.path.join(paths.SQLMAP_TXT_PATH, "sha256sums.txt")
paths.SQL_KEYWORDS = os.path.join(paths.SQLMAP_TXT_PATH, "keywords.txt")
paths.SMALL_DICT = os.path.join(paths.SQLMAP_TXT_PATH, "smalldict.txt")
@ -2605,42 +2605,23 @@ def calculateDeltaSeconds(start):
def initCommonOutputs():
"""
Initializes dictionary containing common output values used by "good samaritan" feature
Initializes the per-context dictionary of common identifier names used by the
predictive-inference feature to shortcut blind table/column name enumeration.
Sourced directly from the curated '--common-tables'/'--common-columns' wordlists
(real-world, app-focused names); prediction only ever reorders the charset or
confirms a whole value, so it never penalizes a miss.
>>> initCommonOutputs(); "information_schema" in kb.commonOutputs["Databases"]
>>> initCommonOutputs(); "users" in kb.commonOutputs["Tables"]
True
"""
kb.commonOutputs = {}
key = None
with openFile(paths.COMMON_OUTPUTS, 'r') as f:
for line in f:
if line.find('#') != -1:
line = line[:line.find('#')]
line = line.strip()
if len(line) > 1:
if line.startswith('[') and line.endswith(']'):
key = line[1:-1]
elif key:
if key not in kb.commonOutputs:
kb.commonOutputs[key] = set()
if line not in kb.commonOutputs[key]:
kb.commonOutputs[key].add(line)
# The curated '--common-tables'/'--common-columns' brute-force wordlists are far larger and much
# more app-focused than the built-in [Tables]/[Columns] prediction sections (which are mostly
# system objects), so fold them into the good-samaritan prediction to raise its real-world hit rate.
# The mechanism only reorders the charset, so extra coverage never penalizes a miss.
for _key, _path in (("Tables", paths.COMMON_TABLES), ("Columns", paths.COMMON_COLUMNS)):
for key, path in (("Tables", paths.COMMON_TABLES), ("Columns", paths.COMMON_COLUMNS)):
try:
for _ in getFileItems(_path):
kb.commonOutputs.setdefault(_key, set()).add(_)
kb.commonOutputs[key] = set(getFileItems(path))
except SqlmapSystemException:
pass
kb.commonOutputs[key] = set()
def getFileItems(filename, commentPrefix='#', unicoded=True, lowercase=False, unique=False):
"""
@ -2684,19 +2665,17 @@ def getFileItems(filename, commentPrefix='#', unicoded=True, lowercase=False, un
return retVal if not unique else list(retVal.keys())
def goGoodSamaritan(prevValue, originalCharset):
def predictValue(prevValue, originalCharset):
"""
Function for retrieving parameters needed for common prediction (good
samaritan) feature.
Predictive-inference helper: given the value retrieved so far (prefix), consult the
per-context common-identifier set (kb.commonOutputs[kb.partRun], from the common-
tables/common-columns wordlists) to shortcut blind extraction.
prevValue: retrieved query output so far (e.g. 'i').
Returns commonValue if there is a complete single match (in kb.partRun
of txt/common-outputs.txt under kb.partRun) regarding parameter
prevValue. If there is no single value match, but multiple, commonCharset is
returned containing more probable characters (retrieved from matched
values in txt/common-outputs.txt) together with the rest of charset as
otherCharset.
Returns commonValue when a single wordlist entry matches the prefix (the whole value
can be confirmed in one request); otherwise commonCharset holds the more probable
next characters (reordered ahead of otherCharset) so the bisection converges faster.
"""
if kb.commonOutputs is None:
@ -2755,7 +2734,7 @@ def goGoodSamaritan(prevValue, originalCharset):
def getPartRun(alias=True):
"""
Goes through call stack and finds constructs matching
conf.dbmsHandler.*. Returns it or its alias used in 'txt/common-outputs.txt'
conf.dbmsHandler.*. Returns it or its predictive-inference context alias (e.g. 'Tables'/'Columns')
"""
retVal = None
@ -3692,8 +3671,8 @@ def setOptimize():
Sets options turned on by switch '-o'
"""
# conf.predictOutput = True
# Note: persistent (Keep-Alive) connections are now used by default (see _setHTTPHandlers)
# Note: persistent (Keep-Alive) connections are now used by default (see _setHTTPHandlers); predictive
# inference is now an inherent, always-on part of blind name enumeration (no longer a switch)
conf.threads = 3 if conf.threads < 3 and cmdLineOptions.threads is None else conf.threads
conf.nullConnection = not any((conf.data, conf.textOnly, conf.titles, conf.string, conf.notString, conf.regexp, conf.tor))

View file

@ -2243,6 +2243,11 @@ def _setKnowledgeBaseAttributes(flushAll=True):
kb.disableHuffman = False
kb.huffmanProbes = 0
kb.huffmanEscapes = 0
kb.lowCardCache = {}
kb.dumpCharset = {}
kb.dumpCharsetStable = {}
kb.litmusCounter = 0
kb.reliabilityAlarm = False
kb.httpErrorCodes = {}
kb.inferenceMode = False
kb.ignoreCasted = None
@ -2256,7 +2261,7 @@ def _setKnowledgeBaseAttributes(flushAll=True):
kb.lastParserStatus = None
kb.locks = AttribDict()
for _ in ("cache", "connError", "count", "handlers", "hint", "identYwaf", "index", "io", "limit", "liveCookies", "log", "socket", "redirect", "request", "value"):
for _ in ("cache", "connError", "count", "handlers", "hint", "identYwaf", "index", "io", "limit", "liveCookies", "log", "prediction", "socket", "redirect", "request", "value"):
kb.locks[_] = threading.Lock()
kb.matchRatio = None
@ -2914,10 +2919,6 @@ def _basicOptionValidation():
errMsg = "switch '--dump' is incompatible with switch '--dump-all'"
raise SqlmapSyntaxException(errMsg)
if conf.predictOutput and (conf.threads > 1 or conf.optimize):
errMsg = "switch '--predict-output' is incompatible with option '--threads' and switch '-o'"
raise SqlmapSyntaxException(errMsg)
if conf.threads > MAX_NUMBER_OF_THREADS and not conf.get("skipThreadCheck"):
errMsg = "maximum number of used threads is %d avoiding potential connection issues" % MAX_NUMBER_OF_THREADS
raise SqlmapSyntaxException(errMsg)

View file

@ -79,7 +79,6 @@ optDict = {
"Optimization": {
"optimize": "boolean",
"predictOutput": "boolean",
"keepAlive": "boolean",
"noKeepAlive": "boolean",
"nullConnection": "boolean",

View file

@ -20,7 +20,7 @@ from lib.core.enums import OS
from thirdparty import six
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.10.7.30"
VERSION = "1.10.7.31"
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)
@ -560,11 +560,52 @@ for _weight, _chars in ((6, " etaoinsrhldcumfgypwbvkxjqz"), (4, "0123456789"), (
for _char in _chars:
HUFFMAN_PRIOR_WEIGHTS[ord(_char)] = _weight
# Bounds for feeding extracted values back into the "good samaritan" (--predict-output) common-output
# pool for their enumeration context, so later same-context items that share structure (e.g.
# wp_posts / wp_users / wp_options ...) are predicted faster. MAX_LENGTH keeps large data cells from
# bloating/polluting the pool (identifiers are short); MAX_ITEMS bounds per-context growth so a huge
# enumeration cannot make the per-character prediction scan costly. Misses always fall back to bisection.
# Enumeration contexts (kb.partRun) for which predictive inference is active by default: the identifier
# names retrieved here are drawn from a known, skewed distribution captured by the common-tables/
# common-columns wordlists, so whole-value prediction / charset reordering pays off. Deliberately NOT
# applied to arbitrary dumped data (unknown distribution) or one-shot values (banner, current-user).
NAME_PREDICTION_CONTEXTS = ("Tables", "Columns")
# Order of the character-level Markov model used to seed the Huffman set-membership tree during blind
# name enumeration: warmed from the shipped identifier corpus so it predicts a name from the first
# character (identifiers are short, structured and low-entropy). CATALOG_IDENTIFIERS_PRIOR_PEAK is the
# weight the corpus prior is scaled to (higher -> the predicted next character sits nearer the tree
# root -> closer to one request per character). Data dumps keep the classic order-0 adaptive model.
NAME_MARKOV_ORDER = 3
CATALOG_IDENTIFIERS_PRIOR_PEAK = 20
# Maximum number of distinct values a dumped column may show before it is treated as high-cardinality
# and whole-value guessing is abandoned for it. At or below this, each new cell is first confirmed by
# equality against the values already seen for that column (one request on a hit) before per-character
# extraction. Self-verifying, so it never returns a wrong value; the bound keeps misses cheap.
LOW_CARDINALITY_THRESHOLD = 32
# Oracle-reliability litmus: during bulk blind extraction (dumps / name enumeration) a known-answer
# differential is fired every this-many extracted values - one probe that MUST be TRUE (the value we just
# read equals itself) and one that MUST be FALSE (it equals a deliberately corrupted copy). A healthy
# oracle always answers T/F; an always-true channel (WAF/200-for-everything, reads-everything-true) or a
# flaky/degraded one (timing jitter, lease near end-of-life) trips it - converting SILENT data corruption
# into a one-time "results may be unreliable" warning. The first value is always checked (catch it before
# a whole garbage dump), then every Nth. Cheap and amortized; set to 0 to disable.
ORACLE_LITMUS_CHECK_EVERY = 25
# Whole-value guessing only starts once some value has repeated (proof the column is low-cardinality), so
# an all-unique column - primary key, hash, free text - never wastes a probe. Once armed, at most this
# many candidates (most-frequent first) are tried per cell, so even a column that trips the threshold with
# many near-unique values can only ever waste a small, bounded number of probes before falling back.
LOW_CARDINALITY_MAX_GUESSES = 8
# Number of consecutive dumped rows a column's observed character set must stay unchanged before it is
# trusted as closed and used to restrict the time-based bisection alphabet. A column whose alphabet keeps
# growing (e.g. a monotonic primary key or high-entropy text) never reaches this, so it is never charged
# the speculative restricted-search-then-escalate cost.
DUMP_CHARSET_STABLE_ROWS = 3
# Bounds for feeding extracted values back into the predictive-inference pool for their enumeration
# context, so later same-context items that share structure (e.g. wp_posts / wp_users / wp_options ...)
# are predicted faster. MAX_LENGTH keeps large data cells from bloating/polluting the pool (identifiers
# are short); MAX_ITEMS bounds per-context growth so a huge enumeration cannot make the per-character
# prediction scan costly. Only fed single-threaded (never mutated under value-parallel enumeration).
PREDICTION_FEEDBACK_MAX_LENGTH = 128
PREDICTION_FEEDBACK_MAX_ITEMS = 10000

View file

@ -78,7 +78,7 @@ def vulnTest(tests=None, label="vuln"):
("-u <base> --flush-session -H \"Foo: Bar\" -H \"Sna: Fu\" --data=\"<root><param name=\\\"id\\\" value=\\\"1*\\\"/></root>\" --union-char=1 --mobile --answers=\"smartphone=3\" --banner --smart -v 5", ("might be injectable", "Payload: <root><param name=\"id\" value=\"1", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "banner: '3.", "Nexus", "Sna: Fu", "Foo: Bar")),
("-u <base> --flush-session --technique=BU --method=PUT --data=\"a=1;id=1;b=2\" --param-del=\";\" --skip-static --har=<tmpfile> --dump -T users --start=1 --stop=2", ("might be injectable", "Parameter: id (PUT)", "Type: boolean-based blind", "Type: UNION query", "2 entries")),
("-u <url> --flush-session -H \"id: 1*\" --tables -t <tmpfile>", ("might be injectable", "Parameter: id #1* ((custom) HEADER)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", " users ")),
("-u <url> --flush-session --banner --invalid-logical --technique=B --predict-output --titles --test-filter=\"OR boolean\" --tamper=space2dash", ("banner: '3.", " LIKE ")),
("-u <url> --flush-session --banner --invalid-logical --technique=B --titles --test-filter=\"OR boolean\" --tamper=space2dash", ("banner: '3.", " LIKE ")),
("-u <url> --flush-session --cookie=\"PHPSESSID=d41d8cd98f00b204e9800998ecf8427e; id=1*; id2=2\" --tables --union-cols=3", ("might be injectable", "Cookie #1* ((custom) HEADER)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", " users ")),
("-u <url> --flush-session --null-connection --technique=B --tamper=between,randomcase --banner --count -T users", ("NULL connection is supported with HEAD method", "banner: '3.", "users | 30")),
("-u <base> --data=\"aWQ9MQ==\" --flush-session --base64=POST -v 6", ("aWQ9MTtXQUlURk9SIERFTEFZICcwOjA",)),

View file

@ -318,9 +318,6 @@ def cmdLineParser(argv=None):
optimization.add_argument("-o", dest="optimize", action="store_true",
help="Turn on all optimization switches")
optimization.add_argument("--predict-output", dest="predictOutput", action="store_true",
help="Predict common queries output")
# Note: persistent (Keep-Alive) connections are used by default; this opts out
optimization.add_argument("--no-keep-alive", dest="noKeepAlive", action="store_true",
help="Disable persistent HTTP(s) connections (Keep-Alive)")

View file

@ -13,6 +13,10 @@ import time
from lib.core.agent import agent
from lib.core.bigarray import BigArray
from lib.core.common import applyFunctionRecursively
from lib.core.common import dataToStdout
from lib.core.common import unArrayizeValue
from lib.core.datatype import AttribDict
from lib.utils.safe2bin import safecharencode
from lib.core.common import Backend
from lib.core.common import calculateDeltaSeconds
from lib.core.common import cleanQuery
@ -22,6 +26,7 @@ from lib.core.common import filterNone
from lib.core.common import getPublicTypeMembers
from lib.core.common import getTechnique
from lib.core.common import getTechniqueData
from lib.core.common import incrementCounter
from lib.core.common import hashDBRetrieve
from lib.core.common import hashDBWrite
from lib.core.common import initTechnique
@ -58,10 +63,13 @@ from lib.core.settings import MAX_TECHNIQUES_PER_VALUE
from lib.core.settings import SQL_SCALAR_REGEX
from lib.core.settings import UNICODE_ENCODING
from lib.core.threads import getCurrentThreadData
from lib.core.threads import runThreads
from lib.core.unescaper import unescaper
from lib.request.connect import Connect as Request
from lib.request.direct import direct
from lib.techniques.blind.inference import bisection
from lib.techniques.blind.inference import queryOutputLength
from lib.techniques.blind.inference import valueMatchCondition
from lib.techniques.dns.test import dnsTest
from lib.techniques.dns.use import dnsUse
from lib.techniques.error.use import errorUse
@ -358,6 +366,153 @@ def _goUnion(expression, unpack=True, dump=False):
return output
def _verifyInferredValue(expression, value):
"""
Confirm a value-parallel-inferred name with ONE equality boolean (lock-free forged
query, mirroring the predictive commonValue check). A wrong bisection bit under heavy
concurrent load on a flaky/WAF'd target flips a character; a full-value equality catches
it sharply (a corrupted name != the real one). Returns True when (expression) == value
holds, or on a transient verify error (never discard a value on a hiccup).
"""
if value is None or isNoneValue(value):
return True
value = unArrayizeValue(value)
if not isinstance(value, six.string_types):
return True
if Backend.getDbms():
_, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression)
nulledCastedField = agent.nullAndCastField(fieldToCastStr)
expressionUnescaped = unescaper.escape(expression.replace(fieldToCastStr, nulledCastedField, 1))
else:
expressionUnescaped = unescaper.escape(expression)
matchCondition = valueMatchCondition(expressionUnescaped, value)
if matchCondition is None: # non-ASCII value: no reliable whole-value equality (see valueMatchCondition)
return None # caller confirms these by an independent re-extraction instead
query = getTechniqueData().vector.replace(INFERENCE_MARKER, matchCondition)
query = agent.suffixQuery(agent.prefixQuery(query))
timeBasedCompare = getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)
try:
result = bool(Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False))
incrementCounter(getTechnique())
return result
except Exception:
return True
def _threadedInferenceValues(exprBuilder, indices, context=None, charsetType=None, dump=False):
"""
Value-parallel blind retrieval.
Retrieve many independent values concurrently, ONE whole value per worker thread, each decoded
sequentially via bisection with length=None - so there is NO per-value length probe (unlike the
position-parallel path, which must probe LENGTH() to split a value's characters across threads) and
the sequential prefix lets predictive inference / low-cardinality guessing / the per-column Huffman
model work. This parallelizes across VALUES instead of character positions - the right axis for the
MANY short values of table/column NAME enumeration (context="Tables"/"Columns" tags kb.partRun so
predictValue() consults the wordlist) and, with dump=True, of per-column data dumping (Huffman and
low-cardinality guessing engage). It bypasses getValue()'s @lockedmethod the same way union/error
row-threading calls _oneShotUnionUse directly. `exprBuilder(index)` yields the per-value expression.
Returns a list aligned with `indices` (None where a value could not be retrieved); single-thread is
just sequential retrieval (no worse than the classic loop, and still without the length probe).
"""
indices = list(indices)
savedTechnique = getTechnique()
if isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN):
setTechnique(PAYLOAD.TECHNIQUE.BOOLEAN)
elif isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME):
setTechnique(PAYLOAD.TECHNIQUE.TIME)
else:
return None
initTechnique(getTechnique())
payload = agent.payload(newValue=agent.suffixQuery(agent.prefixQuery(getTechniqueData().vector)))
results = [None] * len(indices)
cursor = iter(xrange(len(indices)))
def inferenceThread():
threadData = getCurrentThreadData()
# Each per-value bisection streams its characters to stdout and mirrors them into
# threadData.shared.value - which is a PROCESS-GLOBAL object. Left as-is, concurrent
# workers interleave their character output (garbled console) and stomp each other's
# partial value. So suppress the per-char streaming here and give each worker a private
# shared-state object; a single clean line/counter is printed per completed value below.
threadData.disableStdOut = True
threadData.shared = AttribDict()
while kb.threadContinue:
with kb.locks.limit:
try:
slot = next(cursor)
except StopIteration:
break
expression = exprBuilder(indices[slot])
try:
_, value = bisection(payload, expression, length=None, charsetType=charsetType, dump=dump)
# Self-verify each value: sustained concurrent boolean load on a flaky/WAF'd target can flip
# a bisection bit (raw retrieval has no per-char validation), so confirm the whole value and
# re-extract on mismatch. ASCII values use ONE fast equality probe; a value carrying non-ASCII
# (which a quoted literal may not round-trip, AND which is itself a common corruption symptom)
# is instead confirmed by an INDEPENDENT re-extraction having to agree - a random flip will not
# reproduce the same bytes twice. Bounded to a few tries; correctness over a marginal request.
tries = 0
while not isNoneValue(value) and not threadData.lowCardHit and tries < 3:
verdict = _verifyInferredValue(expression, value)
if verdict is True:
break
tries += 1
_, other = bisection(payload, expression, length=None, charsetType=charsetType, dump=dump)
if verdict is None and other == value: # two independent extractions agree -> trust it
break
value = other # equality said wrong, or the two disagree -> adopt fresh, recheck
except Exception as ex:
logger.debug("parallel retrieval worker failed at slot %d ('%s')" % (slot, ex))
value = None
with kb.locks.value:
results[slot] = value
# Stream each retrieved value as it completes (they arrive out of order under threads, exactly
# like the error/union dumps), so a dump shows its data live rather than a silent counter.
if conf.verbose >= 1 and not kb.bruteMode and not isNoneValue(value):
with kb.locks.io:
rendered = safecharencode(unArrayizeValue(value))
dataToStdout("[%s] [INFO] retrieved: %s\n" % (time.strftime("%X"), "'%s'" % rendered if dump else rendered), forceOutput=True)
# Save/restore the calling thread's state: with a single thread runThreads runs the worker
# INLINE on this thread, so the worker's disableStdOut/shared mutations must not leak out.
savedPartRun = kb.partRun
mainThreadData = getCurrentThreadData()
savedStdOut, savedShared = mainThreadData.disableStdOut, mainThreadData.shared
kb.partRun = context
try:
runThreads(min(conf.threads or 1, len(indices)) or 1, inferenceThread)
finally:
kb.partRun = savedPartRun
mainThreadData.disableStdOut = savedStdOut
mainThreadData.shared = savedShared
if savedTechnique is not None:
setTechnique(savedTechnique)
# Robustness: any slot a worker could not retrieve (None, i.e. a transient per-cell failure) is
# re-extracted serially via the classic getValue() path - full error handling, and a persistent error
# surfaces there - rather than being silently returned as an empty value.
for slot in xrange(len(results)):
if results[slot] is None and kb.threadContinue:
results[slot] = getValue(exprBuilder(indices[slot]), union=False, error=False, dump=dump, charsetType=charsetType)
return results
@lockedmethod
@stackedmethod
def getValue(expression, blind=True, union=True, error=True, time=True, fromUser=False, expected=None, batch=False, unpack=True, resumeValue=True, charsetType=None, firstChar=None, lastChar=None, dump=False, suppressOutput=None, expectingNone=False, safeCharEncode=True):

View file

@ -20,10 +20,12 @@ from lib.core.common import decodeIntToUnicode
from lib.core.common import filterControlChars
from lib.core.common import getCharset
from lib.core.common import getCounter
from lib.core.common import getFileItems
from lib.core.common import getPartRun
from lib.core.common import getTechnique
from lib.core.common import getTechniqueData
from lib.core.common import goGoodSamaritan
from lib.core.common import openFile
from lib.core.common import predictValue
from lib.core.common import hashDBRetrieve
from lib.core.common import hashDBWrite
from lib.core.common import incrementCounter
@ -34,6 +36,7 @@ from lib.core.common import singleTimeWarnMessage
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.data import paths
from lib.core.data import queries
from lib.core.enums import ADJUST_TIME_DELAY
from lib.core.enums import CHARSET_TYPE
@ -44,6 +47,13 @@ from lib.core.exception import SqlmapUnsupportedFeatureException
from lib.core.settings import CHAR_INFERENCE_MARK
from lib.core.settings import HUFFMAN_PROBE_LIMIT
from lib.core.settings import HUFFMAN_PRIOR_WEIGHTS
from lib.core.settings import CATALOG_IDENTIFIERS_PRIOR_PEAK
from lib.core.settings import DUMP_CHARSET_STABLE_ROWS
from lib.core.settings import LOW_CARDINALITY_MAX_GUESSES
from lib.core.settings import LOW_CARDINALITY_THRESHOLD
from lib.core.settings import NAME_PREDICTION_CONTEXTS
from lib.core.settings import NAME_MARKOV_ORDER
from lib.core.settings import ORACLE_LITMUS_CHECK_EVERY
from lib.core.settings import PREDICTION_FEEDBACK_MAX_ITEMS
from lib.core.settings import PREDICTION_FEEDBACK_MAX_LENGTH
from lib.core.settings import INFERENCE_BLANK_BREAK
@ -73,6 +83,174 @@ from thirdparty import six
# outside the ASCII model (e.g. multi-byte/Unicode) - defer to the classic bisection".
_HUFFMAN_FALLBACK = object()
# Cache of character-level Markov priors keyed by (order, scale, dbms); built once per process
_huffmanPriorCache = {}
def normalizedExpression(expression):
"""
Row-independent form of a per-row retrieval expression: the paginated offset/limit that varies
from row to row is masked so every row of the same column maps to a single key. Used to group a
column's values for low-cardinality guessing and for its per-column online Huffman model.
>>> normalizedExpression("SELECT name FROM users LIMIT 3,1") == normalizedExpression("SELECT name FROM users LIMIT 7,1")
True
"""
retVal = expression
for pattern in (r"\bLIMIT\s+\d+\s*,\s*\d+", r"\bLIMIT\s+\d+\s+OFFSET\s+\d+", r"\bOFFSET\s+\d+", r"\bLIMIT\s+\d+", r"\bROWNUM\b\s*[<>=]+\s*\d+", r"\bTOP\s+\d+", r"\bFETCH\s+(?:FIRST|NEXT)\s+\d+"):
retVal = re.sub(pattern, lambda match: re.sub(r"\d+", "?", match.group(0)), retVal, flags=re.I)
return retVal
def getHuffmanPrior(order, scale, dbms=None):
"""
Character-level order-N Markov model {context: {ordinal: count}} used to warm the Huffman
set-membership tree during blind NAME enumeration (so it predicts from the first character rather
than cold). Trained on the app-identifier wordlists (common-tables/common-columns) plus, when the
back-end is fingerprinted, the system/catalog identifiers harvested for that DBMS (from the matching
[<DBMS>] section of catalog-identifiers.txt - a single global model dilutes across dialects).
Per-context counts are scaled to a peak of `scale`. Retrieval is correct regardless of this model.
"""
if (order, scale, dbms) in _huffmanPriorCache:
return _huffmanPriorCache[(order, scale, dbms)]
prior = {}
names = []
for path in (paths.COMMON_COLUMNS, paths.COMMON_TABLES):
try:
names.extend(getFileItems(path))
except Exception:
pass
if dbms:
try:
with openFile(paths.CATALOG_IDENTIFIERS, "r", errors="ignore") as f:
section = None
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
if line.startswith('[') and line.endswith(']'):
section = line[1:-1]
elif section == dbms:
names.append(line)
except Exception:
pass
for name in names:
terminated = name + "\x00"
for i in xrange(len(terminated)):
ordinal = ord(terminated[i])
if ordinal < 128:
counts = prior.setdefault(terminated[max(0, i - order):i], {})
counts[ordinal] = counts.get(ordinal, 0) + 1
for counts in prior.values():
peak = max(counts.values()) or 1
for ordinal in counts:
counts[ordinal] = max(1, int(round(counts[ordinal] * float(scale) / peak)))
_huffmanPriorCache[(order, scale, dbms)] = prior
return prior
def contextWeights(model, prior, order, prefix):
"""
Combined next-character weights P(next | last `order` chars) from the per-run online `model` plus
the optional shipped Markov `prior`, backing off to shorter contexts (Katz-style) when the deepest
context has not been seen yet. The online model is snapshotted under kb.locks.prediction because
value-parallel workers may mutate it concurrently (a bare iteration could otherwise raise).
"""
weights = {}
context = prefix[-order:] if order > 0 else ""
while True:
with kb.locks.prediction:
online = dict(model.get(context) or ())
for source in (online, prior.get(context) if prior is not None else None):
if source:
for symbol, count in source.items():
weights[symbol] = weights.get(symbol, 0) + count
if weights or not context:
break
context = context[1:]
return weights
def valueMatchCondition(expressionUnescaped, value):
"""
Boolean SQL that is TRUE iff (expressionUnescaped) equals the whole `value` (extracted so far as a
string), or None when a whole-value equality cannot be trusted and the caller must fall back to
per-character extraction. Used by low-cardinality guessing and by the value-parallel self-verification.
Returns None for values containing non-ASCII characters: those are extracted correctly byte-wise by
the classic bisection, but a single quoted/CHAR()-encoded literal may not round-trip to the same
bytes on every back-end, so a whole-value "=" could spuriously miss (and, for verification, drive a
needless re-extraction). ASCII values compare reliably.
On SQLite (dynamically typed) the dump's COALESCE(col, ...) wrapper loses column affinity, so for a
numeric column "1 = '1'" is FALSE and the quoted form would never hit; there we ALSO test the bare-
number form. That extra form is emitted ONLY for SQLite: on strictly-typed engines (e.g. PostgreSQL)
"text = 1" is a hard type error that would abort the whole boolean, and there the expression is already
text-cast so the quoted form matches anyway. Correctness is unaffected either way - this only decides
whether a whole-value shortcut hits or falls back to per-character extraction.
>>> valueMatchCondition("q", "abc").count("OR")
0
>>> valueMatchCondition("q", u"caf\\xe9") is None
True
"""
if value is None or any(ord(_) >= 128 for _ in value):
return None
quoted = unescaper.escape("'%s'" % value) if "'" not in value else unescaper.escape("%s" % value, quote=False)
condition = "(%s)%s%s" % (expressionUnescaped, INFERENCE_EQUALS_CHAR, quoted)
if re.match(r"\A-?\d+(\.\d+)?\Z", value) and Backend.getIdentifiedDbms() == DBMS.SQLITE:
condition = "(%s OR (%s)%s%s)" % (condition, expressionUnescaped, INFERENCE_EQUALS_CHAR, value)
return condition
def oracleReliabilityLitmus(expressionUnescaped, value, timeBasedCompare):
"""
Known-answer differential health-check on the inference oracle, using the value just extracted.
Fires TWO probes on the SAME cell: "(expr) = value" (must be TRUE) and "(expr) = <value with one
character corrupted>" (must be FALSE). A healthy oracle answers TRUE/FALSE; an always-true channel
(e.g. a WAF returning 200 for everything, a reads-everything-true endpoint) trips the FALSE probe,
and a flaky/degraded one trips either - so silent data corruption becomes a detectable signal.
Returns True if the oracle behaved consistently (or the check is not applicable), False on a detected
inconsistency. Skips (returns True) for values valueMatchCondition() cannot reliably compare (non-ASCII).
"""
if not value or valueMatchCondition(expressionUnescaped, value) is None:
return True
# a definitely-different copy: flip the last character to a neighbour that cannot equal it
corrupt = value[:-1] + ("a" if value[-1] != "a" else "b")
corruptCondition = valueMatchCondition(expressionUnescaped, corrupt)
if corruptCondition is None:
return True
try:
truthy = agent.suffixQuery(agent.prefixQuery(getTechniqueData().vector.replace(INFERENCE_MARKER, valueMatchCondition(expressionUnescaped, value))))
mustBeTrue = Request.queryPage(agent.payload(newValue=truthy), timeBasedCompare=timeBasedCompare, raise404=False)
incrementCounter(getTechnique())
falsy = agent.suffixQuery(agent.prefixQuery(getTechniqueData().vector.replace(INFERENCE_MARKER, corruptCondition)))
mustBeFalse = Request.queryPage(agent.payload(newValue=falsy), timeBasedCompare=timeBasedCompare, raise404=False)
incrementCounter(getTechnique())
except Exception:
return True # a transient hiccup is not evidence of an unreliable oracle
return bool(mustBeTrue) and not bool(mustBeFalse)
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False):
"""
Bisection algorithm that can be used to perform blind SQL injection
@ -84,6 +262,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
partialValue = u""
finalValue = None
retrievedLength = 0
columnKey = None
if payload is None:
return 0, None
@ -97,6 +276,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
asciiTbl = getCharset(charsetType)
threadData = getCurrentThreadData()
threadData.lowCardHit = False # set when this value is confirmed by the (self-verifying) low-card guess
timeBasedCompare = (getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED))
retVal = hashDBRetrieve(expression, checkConf=True)
@ -139,14 +319,14 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
expression = match.group(2).strip()
try:
# Set kb.partRun in case "common prediction" feature (a.k.a. "good samaritan") is used, or the
# engine is called from the API, or a JSON report is being collected (so enumeration output is tagged)
if conf.predictOutput:
kb.partRun = getPartRun()
elif conf.api or conf.reportJson:
kb.partRun = getPartRun(alias=False)
else:
kb.partRun = None
# kb.partRun tags the enumeration context so predictive inference (predictValue) fires for BOTH
# the value-parallel and the classic serial name-enumeration paths. It is derived from the call
# stack here (alias form for prediction; raw for API/JSON tagging); the derivation only overwrites
# when it finds a match, so it does NOT clobber the context the value-parallel helper set for its
# worker threads (whose call stack does not include the enumeration method -> getPartRun is None).
derivedPartRun = getPartRun(alias=not (conf.api or conf.reportJson))
if derivedPartRun is not None:
kb.partRun = derivedPartRun
if partialValue:
firstChar = len(partialValue)
@ -180,6 +360,60 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
else:
expressionUnescaped = unescaper.escape(expression)
# Row-independent key for this column (pagination offset masked), grouping all of a column's
# rows for low-cardinality guessing and for its own per-column online Huffman model.
columnKey = normalizedExpression(expression) if dump else None
# Low-cardinality whole-value guessing: when the distinct values already seen for this column are
# few (<= LOW_CARDINALITY_THRESHOLD), confirm the current cell by equality against each of them
# (one request on a hit) before per-character extraction - a large win on the enum/flag/status/
# category/type columns that dominate real tables. Self-verifying (a wrong candidate simply fails).
# Especially valuable for TIME-BASED blind: a hit confirms the whole value in a single delayed
# request instead of ~7 delays/char x N chars. The repetition gate below ensures it only ever fires
# on genuinely low-cardinality columns, so unique identifier names never pay a wasted probe/delay.
if columnKey is not None and not partialValue:
# Snapshot the shared cache under the lock (value-parallel workers may mutate it concurrently).
with kb.locks.prediction:
seen = dict(kb.lowCardCache.get(columnKey) or ())
# Arm only once SOME value has repeated (max count >= 2): that is the proof the column is
# low-cardinality, so an all-unique column (primary key, hash, free text) never spends a probe.
# Once armed, try at most LOW_CARDINALITY_MAX_GUESSES candidates (most frequent first), so a
# column that trips the threshold with many near-unique values wastes only a bounded number of
# probes. A wrong guess costs one probe (self-verifying); a right one confirms the whole value.
if seen and len(seen) <= LOW_CARDINALITY_THRESHOLD and max(seen.values()) >= 2:
for candidate in sorted(seen, key=lambda value: -seen[value])[:LOW_CARDINALITY_MAX_GUESSES]:
matchCondition = valueMatchCondition(expressionUnescaped, candidate)
if matchCondition is None: # non-ASCII: no reliable whole-value equality, extract per-char
continue
forgedQuery = agent.suffixQuery(agent.prefixQuery(getTechniqueData().vector.replace(INFERENCE_MARKER, matchCondition)))
hit = Request.queryPage(agent.payload(newValue=forgedQuery), timeBasedCompare=timeBasedCompare, raise404=False)
incrementCounter(getTechnique())
if hit and timeBasedCompare:
# A single time-based boolean is noisy; confirm the whole-value hit with a
# not-equals check (validateChar spirit) before trusting it, so timing jitter can
# never ship a wrong low-cardinality value. Still ~2 delayed requests/value vs the
# ~7-delays/char x N of full extraction.
notEqualsQuery = agent.suffixQuery(agent.prefixQuery(getTechniqueData().vector.replace(INFERENCE_MARKER, "NOT(%s)" % matchCondition)))
hit = not Request.queryPage(agent.payload(newValue=notEqualsQuery), timeBasedCompare=timeBasedCompare, raise404=False)
incrementCounter(getTechnique())
if hit:
threadData.lowCardHit = True
return getCounter(getTechnique()), candidate
# Model driving the Huffman set-membership tree. Name enumeration keys on the enumeration context
# and is seeded with the fingerprinted back-end's identifier prior, so the tree predicts a name
# from the first character (structured, low-entropy identifiers). A data dump uses a PER-COLUMN
# order-0 model: each column learns its own character distribution, so a column restricted to few
# characters (hex/uuid, digits, dates, a constant/NULL placeholder) is forced from those alone
# (e.g. ~4 requests/char on hex instead of ~6, ~1 on a constant) with no cross-column dilution.
# Order 0 needs no sequential prefix, so it works under the position-parallel (per-value) threads
# too; a higher-order per-column model was measured to lose to its own cold-start, so order 0 it is.
if kb.partRun in NAME_PREDICTION_CONTEXTS:
huffmanKey, huffmanOrder = kb.partRun, NAME_MARKOV_ORDER
huffmanPrior = getHuffmanPrior(NAME_MARKOV_ORDER, CATALOG_IDENTIFIERS_PRIOR_PEAK, Backend.getIdentifiedDbms())
else:
huffmanKey, huffmanOrder, huffmanPrior = columnKey, 0, None
if isinstance(length, six.string_types) and isDigit(length) or isinstance(length, int):
length = int(length)
else:
@ -211,7 +445,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
else:
numThreads = 1
if conf.threads == 1 and not any((timeBasedCompare, conf.predictOutput)):
if conf.threads == 1 and not timeBasedCompare:
warnMsg = "running in a single-thread mode. Please consider "
warnMsg += "usage of option '--threads' for faster data retrieval"
singleTimeWarnMessage(warnMsg)
@ -295,12 +529,21 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
back to the classic bisection. Returns the character, or None to fall back.
"""
ESCAPE = -1
model = kb.huffmanModel
model = kb.huffmanModel.setdefault(huffmanKey, {})
threadData = getCurrentThreadData()
# Next-character weights P(next | last huffmanOrder chars) from this retrieval's own online
# model plus, for name enumeration, the shipped identifier prior (so the tree is warm from the
# first character); order 0 collapses to the classic single-context adaptive model. Retrieval
# is correct regardless of the weights (the tree spans the whole range plus an ESCAPE leaf), so
# the model - even raced under threads - only ever affects speed, never the returned value.
context = partialValue[-huffmanOrder:] if huffmanOrder > 0 else ""
weights = contextWeights(model, huffmanPrior, huffmanOrder, partialValue)
heap = []
for order, ordinal in enumerate(xrange(128)):
heapq.heappush(heap, (model.get(ordinal, 0) + HUFFMAN_PRIOR_WEIGHTS.get(ordinal, 1), order, (ordinal,)))
heapq.heappush(heap, (max(model.get(ESCAPE, 0), 1), 128, (ESCAPE,)))
heapq.heappush(heap, (weights.get(ordinal, 0) + HUFFMAN_PRIOR_WEIGHTS.get(ordinal, 1), order, (ordinal,)))
heapq.heappush(heap, (max(weights.get(ESCAPE, 0), 1), 128, (ESCAPE,)))
counter = 129
while len(heap) > 1:
@ -337,12 +580,23 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False)
incrementCounter(getTechnique())
# Guard against target-side length limits / WAFs that reject the (potentially long)
# "IN (...)" list: an HTTP error code that is not the technique's own true/false code means
# this membership query was rejected (e.g. 414 URI Too Long, 413, 400, 403), so the walk
# cannot be trusted. Abandon it and hand the character to the classic short-query ('>' / '=')
# bisection, which re-extracts and validates it; the escape counter in getChar() latches
# Huffman off (kb.disableHuffman) if the rejection keeps happening. Gated on >= 400 so a
# normal content-based (200/200) response never trips it.
if not timeBasedCompare and threadData.lastCode is not None and threadData.lastCode >= 400 and (getTechniqueData() is None or threadData.lastCode not in (getTechniqueData().falseCode, getTechniqueData().trueCode)):
return _HUFFMAN_FALLBACK
node = testNode if result else otherNode
value = node[0]
if value == ESCAPE:
model[ESCAPE] = model.get(ESCAPE, 0) + 1
with kb.locks.prediction:
model.setdefault(context, {})[ESCAPE] = model.setdefault(context, {}).get(ESCAPE, 0) + 1
return _HUFFMAN_FALLBACK
if value == 0:
@ -365,13 +619,17 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
kb.disableHuffman = True
return _HUFFMAN_FALLBACK
model[value] = model.get(value, 0) + 1
with kb.locks.prediction:
model.setdefault(context, {})[value] = model.setdefault(context, {}).get(value, 0) + 1
return decodeIntToUnicode(value)
def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None, retried=None):
def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None, retried=None, restricted=False):
"""
continuousOrder means that distance between each two neighbour's
numerical values is exactly 1
restricted means charTbl is a narrowed per-column observed range (time-based only): a character
landing outside it fails validateChar and is re-extracted over the full charset.
"""
threadData = getCurrentThreadData()
@ -381,7 +639,11 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
if result:
return result
if (not conf.noHuffman and not kb.disableHuffman and dump and continuousOrder and charsetType is None and not timeBasedCompare
# Huffman set-membership applies to boolean-based dumps and name enumeration. It stays off for
# time-based, where each membership step is timing-noisy and lacks per-character validation
# (measured to trade accuracy for little/no gain there); time-based relies on plain bisection
# plus low-cardinality whole-value guessing instead.
if (not conf.noHuffman and not kb.disableHuffman and (dump or kb.partRun in NAME_PREDICTION_CONTEXTS) and continuousOrder and charsetType is None and not timeBasedCompare
and ("%s%s" % (INFERENCE_GREATER_CHAR, "%d")) in payload
and ("'%s'" % CHAR_INFERENCE_MARK) not in payload):
kb.huffmanProbes = (kb.huffmanProbes or 0) + 1
@ -545,6 +807,10 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload):
if (timeBasedCompare or unexpectedCode) and not validateChar(idx, retVal):
if restricted:
# the character fell outside this column's observed range - re-extract
# over the full charset (not timing noise, so no delay increase / retry count)
return getChar(idx, asciiTbl, True, retried=retried)
if not kb.originalTimeDelay:
kb.originalTimeDelay = conf.timeSec
@ -625,6 +891,11 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
return None
else:
return decodeIntToUnicode(candidates[0])
elif restricted:
# the self-validating '=' failed: the character is outside this column's observed set
# (or is end-of-string) - re-extract over the full charset, which validates the value
# and detects end-of-string correctly
return getChar(idx, asciiTbl, True, retried=retried)
# Go multi-threading (--threads > 1)
if numThreads > 1 and isinstance(length, int) and length > 1:
@ -732,11 +1003,11 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
# Common prediction feature (a.k.a. "good samaritan")
# NOTE: to be used only when multi-threading is not set for
# the moment
if conf.predictOutput and len(partialValue) > 0 and kb.partRun is not None:
if kb.partRun in NAME_PREDICTION_CONTEXTS and len(partialValue) > 0:
val = None
commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(partialValue, asciiTbl)
commonValue, commonPattern, commonCharset, otherCharset = predictValue(partialValue, asciiTbl)
# If there is one single output in common-outputs, check
# If a single wordlist entry matches the prefix, confirm
# it via equal against the query output
if commonValue is not None:
# One-shot query containing equals commonValue
@ -778,19 +1049,45 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
val = commonPattern[index - 1:]
index += len(val) - 1
# Otherwise if there is no commonValue (single match from
# txt/common-outputs.txt) and no commonPattern
# (common pattern) use the returned common charset only
# to retrieve the query output
if not val and commonCharset:
val = getChar(index, commonCharset, False)
# If we had no luck with commonValue and common charset,
# use the returned other charset
# Char-by-char fallback. When Huffman is actually active it is driven over the full
# (continuous) charset: the corpus-Markov-seeded tree puts the single likeliest next
# character at its root (~1 request), subsuming the common/other charset split. When
# Huffman is unavailable (--no-huffman, latched off after repeated escapes, or TIME-BASED
# where getChar disables it) the classic reordered-charset bisection is used instead - so
# the predicted commonCharset ordering is not thrown away (time-based would otherwise pay
# full-charset bisection for every character).
if not val:
val = getChar(index, otherCharset, otherCharset == asciiTbl)
if not conf.noHuffman and not kb.disableHuffman and not timeBasedCompare:
val = getChar(index, asciiTbl, True)
else:
if commonCharset:
val = getChar(index, commonCharset, False)
if not val:
val = getChar(index, otherCharset, otherCharset == asciiTbl)
else:
val = getChar(index, asciiTbl, not (charsetType is None and conf.charset))
# Time-based dump: once a column's character set has proven closed (unchanged for
# DUMP_CHARSET_STABLE_ROWS consecutive rows), search only those
# observed ordinals via the bit-search (continuousOrder=False), whose final '=' equality
# self-validates the character (no separate validateChar). A narrow-charset column (hex,
# digits, dates, decimals) collapses from ~log2(full charset)+1 toward ~log2(set)+1
# delayed requests/char. A character outside the observed set makes that '=' fail and is
# re-extracted over the full charset (see the restricted escalation in getChar). Time-based
# only: boolean has no per-character validation to catch such a miss (and uses Huffman).
restrictedTbl = None
if (dump and timeBasedCompare and columnKey is not None and charsetType is None and not conf.charset
and kb.dumpCharsetStable.get(columnKey, 0) >= DUMP_CHARSET_STABLE_ROWS):
with kb.locks.prediction:
observed = set(kb.dumpCharset.get(columnKey) or ()) # snapshot (value-parallel safe)
if observed and len(observed) <= 64:
# include the 0 end-of-string sentinel so end is detected in-band (the bit-search
# returns None on 0), avoiding a full-charset escalation at the end of every value
restrictedTbl = sorted(observed | set((0,)))
if restrictedTbl is not None:
val = getChar(index, restrictedTbl, False, expand=False, restricted=True)
else:
val = getChar(index, asciiTbl, not (charsetType is None and conf.charset))
if val is None:
finalValue = partialValue
@ -831,11 +1128,11 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
if not (conf.firstChar or conf.lastChar): # Note: --first/--last give a range-limited (non-complete) output; caching it unmarked would let a later resume serve the truncated value as the full one
hashDBWrite(expression, finalValue)
# Adaptive intra-run prediction (good samaritan / --predict-output): remember this extracted
# value for its enumeration context so later same-context items sharing structure are predicted
# faster. Length-capped (identifiers are short -> large data cells never bloat/pollute the pool);
# a wrong prediction only ever costs a probe and falls back to bisection.
if (conf.predictOutput and kb.partRun and kb.commonOutputs is not None
# Adaptive intra-run prediction: remember this extracted name for its enumeration context so
# later same-context items sharing structure (e.g. wp_posts / wp_users ...) are predicted faster.
# Fed ONLY single-threaded (not kb.multiThreadMode) so it never mutates the pool while a
# value-parallel worker is iterating it. Length-capped; a wrong prediction only costs a probe.
if (kb.partRun in NAME_PREDICTION_CONTEXTS and not kb.multiThreadMode and kb.commonOutputs is not None
and 0 < len(finalValue) <= PREDICTION_FEEDBACK_MAX_LENGTH
and len(kb.commonOutputs.get(kb.partRun) or ()) < PREDICTION_FEEDBACK_MAX_ITEMS):
kb.commonOutputs.setdefault(kb.partRun, set()).add(finalValue)
@ -861,6 +1158,42 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
_ = finalValue or partialValue
# Record this cell for the column's low-cardinality guessing cache (frequency-tracked so the most
# common values are probed first; bounded so a clearly high-cardinality column stops accumulating).
if columnKey is not None and finalValue:
# Track the column's low-cardinality cache and observed character set. Guarded by the prediction
# lock because value-parallel dump workers update these concurrently.
ordinals = set(ord(_c) for _c in finalValue if ord(_c) < 128)
with kb.locks.prediction:
seen = kb.lowCardCache.setdefault(columnKey, {})
if finalValue in seen or len(seen) <= LOW_CARDINALITY_THRESHOLD + 2:
seen[finalValue] = seen.get(finalValue, 0) + 1
if ordinals:
existing = kb.dumpCharset.setdefault(columnKey, set())
grew = not ordinals.issubset(existing) # did this row introduce a never-seen character?
existing.update(ordinals)
# Trust the observed alphabet as closed only after it stays unchanged for several consecutive
# rows. A column that keeps growing (monotonic PK, high-entropy text) resets the counter and
# never triggers the restricted search, so it is never charged the miss-then-escalate cost.
kb.dumpCharsetStable[columnKey] = 0 if grew else kb.dumpCharsetStable.get(columnKey, 0) + 1
# Oracle-reliability litmus: on bulk extraction (dumps / name enumeration) periodically fire a
# known-answer differential so an always-true / flaky / degraded channel that would otherwise dump
# SILENT garbage instead raises a one-time "results may be unreliable" warning. First value is always
# checked (catch it before a whole bad dump), then every ORACLE_LITMUS_CHECK_EVERY-th.
if (ORACLE_LITMUS_CHECK_EVERY and finalValue and not kb.reliabilityAlarm and not kb.bruteMode
and (columnKey is not None or kb.partRun in NAME_PREDICTION_CONTEXTS)):
with kb.locks.prediction:
kb.litmusCounter += 1
due = (kb.litmusCounter == 1 or kb.litmusCounter % ORACLE_LITMUS_CHECK_EVERY == 0)
if due and not oracleReliabilityLitmus(expressionUnescaped, finalValue, timeBasedCompare):
kb.reliabilityAlarm = True
warnMsg = "the target's responses are inconsistent for known-true/known-false probes "
warnMsg += "(reads-everything-true, WAF, or a flaky/degraded channel); extracted data may "
warnMsg += "be unreliable. Consider raising '--time-sec', lowering '--threads', or retrying"
singleTimeWarnMessage(warnMsg)
return getCounter(getTechnique()), safecharencode(_) if kb.safeCharEncode else _
def queryOutputLength(expression, payload):

View file

@ -401,26 +401,39 @@ class Databases(object):
plusOne = Backend.getIdentifiedDbms() in PLUS_ONE_DBMSES
indexRange = getLimitRange(count, plusOne=plusOne)
for index in indexRange:
if Backend.isDbms(DBMS.SYBASE):
query = _query % (db, (kb.data.cachedTables[-1] if kb.data.cachedTables else " "))
elif Backend.getIdentifiedDbms() in (DBMS.MAXDB, DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB):
query = _query % (kb.data.cachedTables[-1] if kb.data.cachedTables else " ")
elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD):
query = _query % index
elif Backend.getIdentifiedDbms() in (DBMS.HSQLDB, DBMS.INFORMIX, DBMS.FRONTBASE, DBMS.VIRTUOSO):
query = _query % (index, unsafeSQLIdentificatorNaming(db))
elif Backend.getIdentifiedDbms() in (DBMS.SPANNER,):
query = _query % (unsafeSQLIdentificatorNaming(db), unsafeSQLIdentificatorNaming(db), index)
else:
query = _query % (unsafeSQLIdentificatorNaming(db), index)
# Value-parallel, prediction-assisted name enumeration for the DBMSes using the
# generic "<db>, <index>" blind template. Retrieves whole names concurrently (one per
# worker, each decoded sequentially so wordlist prediction applies). Used only with
# '--threads' (like getColumns below); single-thread stays on the classic getValue loop,
# which still gets predictive inference via getPartRun. The special templates stay serial.
genericTemplate = Backend.getIdentifiedDbms() not in (DBMS.SYBASE, DBMS.MAXDB, DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB, DBMS.SQLITE, DBMS.FIREBIRD, DBMS.HSQLDB, DBMS.INFORMIX, DBMS.FRONTBASE, DBMS.VIRTUOSO, DBMS.SPANNER)
table = unArrayizeValue(inject.getValue(query, union=False, error=False))
if genericTemplate and conf.threads > 1 and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN):
for table in (inject._threadedInferenceValues(lambda index: _query % (unsafeSQLIdentificatorNaming(db), index), indexRange, context="Tables") or []):
if not isNoneValue(table):
kb.hintValue = table
tables.append(safeSQLIdentificatorNaming(table, True))
else:
for index in indexRange:
if Backend.isDbms(DBMS.SYBASE):
query = _query % (db, (kb.data.cachedTables[-1] if kb.data.cachedTables else " "))
elif Backend.getIdentifiedDbms() in (DBMS.MAXDB, DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB):
query = _query % (kb.data.cachedTables[-1] if kb.data.cachedTables else " ")
elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD):
query = _query % index
elif Backend.getIdentifiedDbms() in (DBMS.HSQLDB, DBMS.INFORMIX, DBMS.FRONTBASE, DBMS.VIRTUOSO):
query = _query % (index, unsafeSQLIdentificatorNaming(db))
elif Backend.getIdentifiedDbms() in (DBMS.SPANNER,):
query = _query % (unsafeSQLIdentificatorNaming(db), unsafeSQLIdentificatorNaming(db), index)
else:
query = _query % (unsafeSQLIdentificatorNaming(db), index)
if not isNoneValue(table):
kb.hintValue = table
table = safeSQLIdentificatorNaming(table, True)
tables.append(table)
table = unArrayizeValue(inject.getValue(query, union=False, error=False))
if not isNoneValue(table):
kb.hintValue = table
table = safeSQLIdentificatorNaming(table, True)
tables.append(table)
if tables:
kb.data.cachedTables[db] = tables
@ -841,7 +854,7 @@ class Databases(object):
logger.error(errMsg)
continue
for index in getLimitRange(count):
def columnNameQuery(index):
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE, DBMS.FRONTBASE, DBMS.VIRTUOSO):
query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db))
query += condQuery
@ -880,8 +893,22 @@ class Databases(object):
query += condQuery
field = condition
query = agent.limitQuery(index, query, field, field)
column = unArrayizeValue(inject.getValue(query, union=False, error=False))
return agent.limitQuery(index, query, field, field)
indexList = list(getLimitRange(count))
# Value-parallel column-NAME enumeration: the same axis/mechanism as getTables (one name per
# worker, decoded sequentially - no length probe, predictive inference applies, names stream
# live). Serial fallback for single-thread and when also fetching per-column comments.
columnNames = None
if conf.threads > 1 and not conf.getComments and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN):
columnNames = inject._threadedInferenceValues(columnNameQuery, indexList, context="Columns")
for position, index in enumerate(indexList):
if columnNames is not None:
column = unArrayizeValue(columnNames[position])
else:
column = unArrayizeValue(inject.getValue(columnNameQuery(index), union=False, error=False))
if not isNoneValue(column):
if conf.getComments:

View file

@ -418,41 +418,70 @@ class Entries(object):
debugMsg += "dumped as it appears to be empty"
logger.debug(debugMsg)
def cellQuery(column, index):
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.CLICKHOUSE, DBMS.SNOWFLAKE, DBMS.SPANNER):
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, conf.tbl, prioritySortColumns(colList)[0], index)
elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE,):
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), index)
elif Backend.getIdentifiedDbms() in (DBMS.MIMERSQL,):
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), prioritySortColumns(colList)[0], index)
elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.EXTREMEDB):
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl, index)
elif Backend.isDbms(DBMS.FIREBIRD):
query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), tbl)
elif Backend.getIdentifiedDbms() in (DBMS.INFORMIX, DBMS.VIRTUOSO):
query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl, prioritySortColumns(colList)[0])
elif Backend.isDbms(DBMS.FRONTBASE):
query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl)
else:
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, tbl, index)
return agent.whereQuery(query)
try:
for index in indexRange:
# Value-parallel dumping: one whole cell per worker, decoded sequentially, so there
# is NO per-cell LENGTH() probe (the position-parallel path needs one to split a
# value's characters across threads) and the per-column Huffman model + low-cardinality
# guessing engage under concurrency. Used for the boolean channel with '--threads'; the
# classic per-character-parallel loop stays for single-thread and time-based.
if conf.threads > 1 and not conf.dnsDomain and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN):
# One value-parallel pass over every (non-empty) cell, so there is a single
# thread pool and values stream live as they complete - out of order, exactly
# like the error/union dumps - instead of a silent progress counter.
nonEmpty = [_ for _ in colList if _ not in emptyColumns]
tasks = [(column, index) for column in nonEmpty for index in indexRange]
retrieved = inject._threadedInferenceValues(lambda pair: cellQuery(pair[0], pair[1]), tasks, charsetType=None, dump=True) if tasks else []
retrieved = retrieved if retrieved is not None else [None] * len(tasks)
offset = 0
for column in colList:
value = ""
entries[column] = BigArray()
lengths.setdefault(column, 0)
if column not in lengths:
lengths[column] = 0
if column not in entries:
entries[column] = BigArray()
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.CLICKHOUSE, DBMS.SNOWFLAKE, DBMS.SPANNER):
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, conf.tbl, prioritySortColumns(colList)[0], index)
elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE,):
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), index)
elif Backend.getIdentifiedDbms() in (DBMS.MIMERSQL,):
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), prioritySortColumns(colList)[0], index)
elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.EXTREMEDB):
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl, index)
elif Backend.isDbms(DBMS.FIREBIRD):
query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), tbl)
elif Backend.getIdentifiedDbms() in (DBMS.INFORMIX, DBMS.VIRTUOSO):
query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl, prioritySortColumns(colList)[0])
elif Backend.isDbms(DBMS.FRONTBASE):
query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl)
if column in emptyColumns:
values = [NULL] * len(indexRange)
else:
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, tbl, index)
values = retrieved[offset:offset + len(indexRange)]
offset += len(indexRange)
query = agent.whereQuery(query)
for value in values:
value = '' if value is None else value
lengths[column] = max(lengths[column], getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value))))
entries[column].append(value)
else:
for index in indexRange:
for column in colList:
if column not in lengths:
lengths[column] = 0
value = NULL if column in emptyColumns else inject.getValue(query, union=False, error=False, dump=True)
value = '' if value is None else value
if column not in entries:
entries[column] = BigArray()
lengths[column] = max(lengths[column], getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value))))
entries[column].append(value)
value = NULL if column in emptyColumns else inject.getValue(cellQuery(column, index), union=False, error=False, dump=True)
value = '' if value is None else value
lengths[column] = max(lengths[column], getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value))))
entries[column].append(value)
except KeyboardInterrupt:
kb.dumpKeyboardInterrupt = True