Fixing desync error from SLOW_ORDER_COUNT_THRESHOLD

This commit is contained in:
Miroslav Štampar 2026-07-11 09:09:08 +02:00
parent 1cdc69eb0e
commit 40ccd801dc
5 changed files with 58 additions and 24 deletions

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.74"
VERSION = "1.10.7.75"
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)
@ -879,9 +879,6 @@ HASHDB_MILESTONE_VALUE = "MvKpZrBqTn" # python -c 'import random, string; print
# Warn user of possible delay due to large page dump in full UNION query injections
LARGE_OUTPUT_THRESHOLD = 1024 ** 2
# On huge tables there is a considerable slowdown if every row retrieval requires ORDER BY (most noticable in table dumping using ERROR injections)
SLOW_ORDER_COUNT_THRESHOLD = 10000
# Give up on hash recognition if nothing was found in first given number of rows
HASH_RECOGNITION_QUIT_THRESHOLD = 1000

View file

@ -29,7 +29,6 @@ from lib.core.common import initTechnique
from lib.core.common import isListLike
from lib.core.common import isNumPosStrValue
from lib.core.common import listToStrValue
from lib.core.common import readInput
from lib.core.common import unArrayizeValue
from lib.core.common import wasLastResponseHTTPError
from lib.core.compat import xrange
@ -51,7 +50,6 @@ from lib.core.settings import MIN_ERROR_CHUNK_LENGTH
from lib.core.settings import NULL
from lib.core.settings import PARTIAL_VALUE_MARKER
from lib.core.settings import ROTATING_CHARS
from lib.core.settings import SLOW_ORDER_COUNT_THRESHOLD
from lib.core.settings import SQL_SCALAR_REGEX
from lib.core.settings import TURN_OFF_RESUME_INFO_LIMIT
from lib.core.threads import getCurrentThreadData
@ -322,7 +320,7 @@ def errorUse(expression, dump=False):
# entry at a time
# NOTE: we assume that only queries that get data from a table can
# return multiple entries
if (dump and (conf.limitStart or conf.limitStop)) or (" FROM " in expression.upper() and ((Backend.getIdentifiedDbms() not in FROM_DUMMY_TABLE) or (Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and not expression.upper().endswith(FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]))) and ("(CASE" not in expression.upper() or ("(CASE" in expression.upper() and "WHEN use" in expression))) and not re.search(SQL_SCALAR_REGEX, expression, re.I):
if not re.search(SQL_SCALAR_REGEX, expression, re.I) and ((dump and (conf.limitStart or conf.limitStop)) or (" FROM " in expression.upper() and ((Backend.getIdentifiedDbms() not in FROM_DUMMY_TABLE) or (Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and not expression.upper().endswith(FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]))) and ("(CASE" not in expression.upper() or ("(CASE" in expression.upper() and "WHEN use" in expression)))):
expression, limitCond, topLimit, startLimit, stopLimit = agent.limitCondition(expression, dump)
if limitCond:
@ -367,13 +365,10 @@ def errorUse(expression, dump=False):
return value
if isNumPosStrValue(count) and int(count) > 1:
if " ORDER BY " in expression and (stopLimit - startLimit) > SLOW_ORDER_COUNT_THRESHOLD:
message = "due to huge table size do you want to remove "
message += "ORDER BY clause gaining speed over consistency? [y/N] "
if readInput(message, default='N', boolean=True):
expression = expression[:expression.index(" ORDER BY ")]
# NOTE: the ORDER BY clause is deliberately NOT stripped here. This path fetches each column
# of a row in a separate LIMIT/OFFSET query, so dropping ORDER BY would let the per-column
# offsets resolve to different physical rows and silently misalign cells across the row. Huge
# tables are handled cheaply and safely by keyset (seek) pagination (see plugins/generic/entries.py).
numThreads = min(conf.threads, (stopLimit - startLimit))
threadData = getCurrentThreadData()

View file

@ -189,12 +189,12 @@ def _dumpSingle(tbl, colList, count, cursor, tableRef, entries, lengths):
produced = 0
while produced < target:
if pivotValue is None:
query = blind.keyset_first % (field, tableRef)
else:
query = _embed(blind.keyset_next, pivotValue, field, tableRef, field)
query = agent.whereQuery(query)
# Advance with ORDER BY ... LIMIT 1 (like the composite path), NOT MIN(): the value-extraction
# casts the aggregated column to VARCHAR *inside* MIN(), yielding a LEXICAL minimum ('10' after
# '1') that disagrees with the numeric '>' comparison and silently skips rows (2..9, 11..). The
# ORDER BY is on the raw (numeric) column, so the next cursor value is the true successor.
condition = "1=1" if pivotValue is None else "%s>%s" % (field, _lit(pivotValue))
query = agent.whereQuery(blind.keyset_ordered % (field, tableRef, condition, field))
value = unArrayizeValue(inject.getValue(query))
if isNoneValue(value) or value == NULL: