Minor patch

This commit is contained in:
Miroslav Štampar
2026-08-08 01:07:07 +02:00
parent aa8d9b9841
commit 7d2c33cd32
6 changed files with 252 additions and 4 deletions
+1 -1
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.8.15"
VERSION = "1.10.8.16"
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)
+4 -1
View File
@@ -22,6 +22,7 @@ from lib.utils.nonsql import InconclusiveError
from lib.utils.nonsql import INCONCLUSIVE_MARK
from lib.utils.nonsql import userDecision
from lib.utils.nonsql import resolveBit
from lib.utils.nonsql import stripReflection
from lib.utils.nonsql import sqlErrorPresent
from lib.utils.nonsql import blockedStatus
from lib.utils.nonsql import ratio as _ratio
@@ -179,7 +180,9 @@ def _send(place, parameter, value):
# true/false differential without a single expression being evaluated - that reported plain
# reflective search pages as injectable. On a blind target the payload is not in the page, so
# this is a no-op.
return removeReflectiveValues(page, value, suppressWarning=True) or ""
# Two layers on purpose: sqlmap's scan-wide heuristic (which can switch itself off) AND a plain
# deterministic strip that cannot. See stripReflection().
return stripReflection(removeReflectiveValues(page, value, suppressWarning=True), value) or ""
except Exception as ex:
logger.debug("LDAP probe request failed: %s" % getUnicode(ex))
return None
+11 -1
View File
@@ -23,7 +23,9 @@ from lib.utils.nonsql import ratio as _ratio
from lib.utils.nonsql import userOracleActive
from lib.utils.nonsql import InconclusiveError
from lib.utils.nonsql import INCONCLUSIVE_MARK
from thirdparty import six
from lib.utils.nonsql import resolveBit
from lib.utils.nonsql import stripReflection
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
@@ -372,7 +374,15 @@ def _send(place, parameter, segment=None, jsonValue=_UNSET):
# differential - and the wildcard always-true check - without a single operator being interpreted.
# That reported a plain reflective search page as a "Lucene query_string-compatible back-end". On a
# blind target the payload is not in the page, so this is a no-op.
return removeReflectiveValues(page, payload, suppressWarning=True) or ""
# Two layers on purpose: sqlmap's scan-wide heuristic (which can switch itself off) AND a plain
# deterministic strip that cannot. See stripReflection().
# Strip the VALUE as well as the whole parameter string: `payload` here is "q=*", while the page
# echoes just "*", so removing only the former leaves the reflection in place.
page = removeReflectiveValues(page, payload, suppressWarning=True)
for _reflected in (payload, segment, jsonValue if jsonValue is not _UNSET else None):
if isinstance(_reflected, six.string_types) and _reflected:
page = stripReflection(page, _reflected)
return page or ""
def _isError(page):
# a server-error status, a recognizable NoSQL error body, OR a recognized SQL/DBMS error marks a
+4 -1
View File
@@ -24,6 +24,7 @@ from lib.utils.nonsql import INCONCLUSIVE_MARK
from lib.utils.nonsql import userDecision
from lib.utils.nonsql import InconclusiveError
from lib.utils.nonsql import resolveBit
from lib.utils.nonsql import stripReflection
from lib.utils.nonsql import sqlErrorPresent
from lib.utils.nonsql import blockedStatus
from lib.utils.nonsql import ratio as _ratio
@@ -165,7 +166,9 @@ def _send(place, parameter, value):
# true/false differential, and even the XPath-only confirm battery, without a single expression
# ever being evaluated. That reported XSLT (and plain reflective) endpoints as XPath-injectable.
# On a genuinely blind target the payload is not in the page, so this is a no-op.
page = removeReflectiveValues(page, value, suppressWarning=True)
# Two layers on purpose: sqlmap's scan-wide heuristic (which can switch itself off) AND a plain
# deterministic strip that cannot. See stripReflection().
page = stripReflection(removeReflectiveValues(page, value, suppressWarning=True), value)
# A transport failure or a BLOCKED/ERROR status (5xx, 403/429 WAF/rate-limit) is NOT a usable
# oracle sample: returning "" for it would let a one-sided failure fake a true/false divergence
# (an empty body cannot be told apart from a dead connection). Signal it as None -> the boolean
+40
View File
@@ -14,7 +14,11 @@ in one place rather than drifting across six files.
import difflib
import re
from lib.core.common import urldecode
from lib.core.common import urlencode
from lib.core.convert import getUnicode
from lib.core.data import conf
from lib.core.settings import REFLECTED_VALUE_MARKER
from lib.core.settings import UPPER_RATIO_BOUND
from lib.parse.html import htmlParser
@@ -38,6 +42,42 @@ def ratio(first, second):
return difflib.SequenceMatcher(None, first or "", second or "").quick_ratio()
def stripReflection(page, payload):
"""
Remove the payload from the page before any two responses are compared.
An endpoint that merely ECHOES the parameter returns a different page for every different payload, so
a true/false differential is satisfied without a single expression, filter or operator ever being
interpreted. That is not injection, and it is how plain reflective search pages were reported as
XPath / LDAP / NoSQL injectable.
Deliberately NOT lib.core.common.removeReflectiveValues: that one is a scan-wide heuristic which
switches ITSELF OFF after REFLECTIVE_MISS_THRESHOLD misses, after a regex timeout, and during
heuristic mode - so a detection guard built on it silently stops guarding mid-scan. This is a plain,
deterministic substring removal with no global state and no failure mode. The two are complementary,
and the engines apply both.
The raw, URL-decoded and URL-encoded forms are all removed: a payload travels encoded, and an
application may echo whichever of the three it happened to hold.
"""
if not page or not payload:
return page
retVal = getUnicode(page)
forms = set()
for form in (payload, urldecode(payload, convall=True), urlencode(payload, safe="")):
try:
forms.add(getUnicode(form))
except Exception:
pass
# longest first, so a shorter form cannot chop a longer one into unremovable pieces
for form in sorted(filter(None, forms), key=len, reverse=True):
if form in retVal:
retVal = retVal.replace(form, REFLECTED_VALUE_MARKER)
return retVal
def blockedStatus(code):
"""True when an HTTP status means the response is blocked/errored (a 5xx, or a WAF/rate-limit
403/429) and so is not a usable oracle sample. `_send()` implementations return None for these