Fixing CI/CD errors

This commit is contained in:
Miroslav Štampar
2026-08-13 19:27:32 +02:00
parent 92c2ee47ea
commit ebf4b48337
7 changed files with 62 additions and 43 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ from lib.core.enums import OS
from thirdparty import six from thirdparty import six
# sqlmap version (<major>.<minor>.<month>.<monthly commit>) # sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.10.8.32" VERSION = "1.10.8.33"
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} 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) VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)
+31
View File
@@ -114,6 +114,37 @@ def reset_dbms():
kb.dbmsVersion = [UNKNOWN_DBMS_VERSION] kb.dbmsVersion = [UNKNOWN_DBMS_VERSION]
# --- patching shared singletons (agent, unescaper, ...) ---
_MISSING = object()
def save_attrs(obj, *names):
"""Snapshot attributes of a shared singleton so tearDown can restore them EXACTLY.
agent.payload and friends are methods on the CLASS: patching one sets an *instance* attribute
that shadows it, and restoring by assignment leaves that instance attribute behind holding a
bound method. Harmless on its own - but invisible state, and a later module whose cleanup keys
on "did this attribute exist before me?" then reads True, skips its own del, and leaks its stub
into every module that follows (test_techniques + test_checks did exactly that to
test_union_engine, whose real payloads silently became the string "PAYLOAD").
Pair with restore_attrs(); it deletes what was not there before and restores what was.
"""
return [(obj, name, obj.__dict__.get(name, _MISSING)) for name in names]
def restore_attrs(saved):
"""Undo save_attrs(), leaving obj.__dict__ exactly as it was found."""
for obj, name, value in saved:
if value is _MISSING:
obj.__dict__.pop(name, None)
else:
setattr(obj, name, value)
# --- property/fuzz testing harness (shared so individual test files don't each reinvent it) --- # --- property/fuzz testing harness (shared so individual test files don't each reinvent it) ---
_PROPERTY_BASE = 0x51A1 _PROPERTY_BASE = 0x51A1
+5 -10
View File
@@ -27,7 +27,7 @@ import time
import unittest import unittest
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _testutils import bootstrap from _testutils import bootstrap, save_attrs, restore_attrs
bootstrap() bootstrap()
import lib.controller.checks as checks import lib.controller.checks as checks
@@ -88,13 +88,11 @@ class _ChecksTestBase(unittest.TestCase):
def setUp(self): def setUp(self):
self._snap = _snapshot() self._snap = _snapshot()
# remember the real seams so monkeypatches can't leak. agent.payload / # remember the real seams so monkeypatches can't leak. agent.payload /
# addPayloadDelimiters are class methods on a shared singleton: patching # addPayloadDelimiters are class methods on a shared singleton, so they need
# sets an *instance* attribute, so it's restored by deleting that # save_attrs()/restore_attrs() rather than a plain reassignment (see _testutils).
# attribute (reassigning would leave a stale bound method behind).
self._origQueryPage = checks.Request.queryPage self._origQueryPage = checks.Request.queryPage
self._origGetPage = checks.Request.getPage self._origGetPage = checks.Request.getPage
self._agentHadPayload = "payload" in checks.agent.__dict__ self._savedAgent = save_attrs(checks.agent, "payload", "addPayloadDelimiters")
self._agentHadAddDelims = "addPayloadDelimiters" in checks.agent.__dict__
self._origReadInput = checks.readInput self._origReadInput = checks.readInput
self._origDbmsErr = checks.wasLastResponseDBMSError self._origDbmsErr = checks.wasLastResponseDBMSError
self._origHttpErr = checks.wasLastResponseHTTPError self._origHttpErr = checks.wasLastResponseHTTPError
@@ -111,10 +109,7 @@ class _ChecksTestBase(unittest.TestCase):
def tearDown(self): def tearDown(self):
checks.Request.queryPage = self._origQueryPage checks.Request.queryPage = self._origQueryPage
checks.Request.getPage = self._origGetPage checks.Request.getPage = self._origGetPage
if not self._agentHadPayload and "payload" in checks.agent.__dict__: restore_attrs(self._savedAgent)
del checks.agent.payload
if not self._agentHadAddDelims and "addPayloadDelimiters" in checks.agent.__dict__:
del checks.agent.addPayloadDelimiters
checks.readInput = self._origReadInput checks.readInput = self._origReadInput
checks.wasLastResponseDBMSError = self._origDbmsErr checks.wasLastResponseDBMSError = self._origDbmsErr
checks.wasLastResponseHTTPError = self._origHttpErr checks.wasLastResponseHTTPError = self._origHttpErr
+8 -6
View File
@@ -38,7 +38,7 @@ import time
import unittest import unittest
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _testutils import bootstrap, set_dbms, reset_dbms from _testutils import bootstrap, set_dbms, reset_dbms, save_attrs, restore_attrs
bootstrap() bootstrap()
from lib.core.agent import agent from lib.core.agent import agent
@@ -131,7 +131,7 @@ class _DnsCase(unittest.TestCase):
# (agent.prefixQuery/agent.payload, needing a full kb.injection). That plumbing is # (agent.prefixQuery/agent.payload, needing a full kb.injection). That plumbing is
# generic - not DNS logic - and the mock oracle ignores the payload, so stub it to a # generic - not DNS logic - and the mock oracle ignores the payload, so stub it to a
# pass-through; the DNS-specific snippet/substring/chunking still runs for real. # pass-through; the DNS-specific snippet/substring/chunking still runs for real.
self._saved_prefixQuery, self._saved_payload = agent.prefixQuery, agent.payload self._s_agent = save_attrs(agent, "prefixQuery", "payload")
agent.prefixQuery = lambda expression, *a, **k: expression agent.prefixQuery = lambda expression, *a, **k: expression
agent.payload = lambda place=None, parameter=None, value=None, newValue=None, where=None: newValue or "" agent.payload = lambda place=None, parameter=None, value=None, newValue=None, where=None: newValue or ""
set_dbms(self.DBMS_NAME) set_dbms(self.DBMS_NAME)
@@ -148,7 +148,7 @@ class _DnsCase(unittest.TestCase):
dnsmod.randomStr = self._saved_randomStr dnsmod.randomStr = self._saved_randomStr
dnstestmod.randomInt = self._saved_randomInt dnstestmod.randomInt = self._saved_randomInt
dnsmod.hashDBRetrieve, dnsmod.hashDBWrite = self._saved_hdbR, self._saved_hdbW dnsmod.hashDBRetrieve, dnsmod.hashDBWrite = self._saved_hdbR, self._saved_hdbW
agent.prefixQuery, agent.payload = self._saved_prefixQuery, self._saved_payload restore_attrs(self._s_agent)
def _install_oracle(self, secret, working=True, force=None): def _install_oracle(self, secret, working=True, force=None):
""" """
@@ -293,10 +293,12 @@ class TestDnsLabelInvariant(_DnsCase):
# "SUBSTRING((...) FROM 1 FOR 13)"; the substring LENGTH argument (the source's real # "SUBSTRING((...) FROM 1 FOR 13)"; the substring LENGTH argument (the source's real
# chunk_length) is the last integer literal in it. Capture it per iteration so the oracle # chunk_length) is the last integer literal in it. Capture it per iteration so the oracle
# emits a chunk of exactly that size - the source's arithmetic, not a copy of it. # emits a chunk of exactly that size - the source's arithmetic, not a copy of it.
saved_hexConvertField = agent.hexConvertField real_hexConvertField = agent.hexConvertField # the spy delegates to it
saved_hexConvertField = save_attrs(agent, "hexConvertField")
def spy_hexConvertField(field): def spy_hexConvertField(field):
source_chunk_lengths.append(int(re.findall(r"\d+", field)[-1])) source_chunk_lengths.append(int(re.findall(r"\d+", field)[-1]))
return saved_hexConvertField(field) return real_hexConvertField(field)
agent.hexConvertField = spy_hexConvertField agent.hexConvertField = spy_hexConvertField
def oracle(payload=None, *args, **kwargs): def oracle(payload=None, *args, **kwargs):
@@ -328,7 +330,7 @@ class TestDnsLabelInvariant(_DnsCase):
try: try:
result = dnsmod.dnsUse("%s AND %d=%d", "user()") result = dnsmod.dnsUse("%s AND %d=%d", "user()")
finally: finally:
agent.hexConvertField = saved_hexConvertField restore_attrs(saved_hexConvertField)
# round-trip must still work (the source must actually reassemble what it chunked) # round-trip must still work (the source must actually reassemble what it chunked)
self.assertEqual(result, secret) self.assertEqual(result, secret)
+3 -3
View File
@@ -16,7 +16,7 @@ import sys
import unittest import unittest
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _testutils import bootstrap from _testutils import bootstrap, save_attrs, restore_attrs
bootstrap() bootstrap()
from lib.core.settings import (JSON_RECOGNITION_REGEX, JSON_LIKE_RECOGNITION_REGEX, from lib.core.settings import (JSON_RECOGNITION_REGEX, JSON_LIKE_RECOGNITION_REGEX,
@@ -219,7 +219,7 @@ def _drive_hpp(payload, name="id"):
captured["value"] = value captured["value"] = value
raise _Sentinel() raise _Sentinel()
orig_remove = agent.removePayloadDelimiters saved_remove = save_attrs(agent, "removePayloadDelimiters")
agent.removePayloadDelimiters = _capture agent.removePayloadDelimiters = _capture
try: try:
conf.direct = False conf.direct = False
@@ -238,7 +238,7 @@ def _drive_hpp(payload, name="id"):
except _Sentinel: except _Sentinel:
pass pass
finally: finally:
agent.removePayloadDelimiters = orig_remove restore_attrs(saved_remove)
_ = re.escape(PAYLOAD_DELIMITER) _ = re.escape(PAYLOAD_DELIMITER)
return re.search(r"(?s)%s(?P<result>.*?)%s" % (_, _), captured["value"]).group("result") return re.search(r"(?s)%s(?P<result>.*?)%s" % (_, _), captured["value"]).group("result")
+7 -23
View File
@@ -30,7 +30,7 @@ import tempfile
import unittest import unittest
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _testutils import bootstrap, set_dbms, reset_dbms from _testutils import bootstrap, set_dbms, reset_dbms, save_attrs, restore_attrs
bootstrap() bootstrap()
from lib.core.data import conf, kb from lib.core.data import conf, kb
@@ -94,9 +94,7 @@ class _UnionCase(unittest.TestCase):
self._scounters = kb.get("counters") self._scounters = kb.get("counters")
self._sinj_data = kb.injection.data self._sinj_data = kb.injection.data
self._shashdb = conf.get("hashDB") self._shashdb = conf.get("hashDB")
self._s_forge = agent.forgeUnionQuery self._s_agent = save_attrs(agent, "forgeUnionQuery", "concatQuery", "payload")
self._s_concat = agent.concatQuery
self._s_payload = agent.payload
self._s_escape = unescaper.escape self._s_escape = unescaper.escape
for k, v in _UU_CONF.items(): for k, v in _UU_CONF.items():
@@ -131,9 +129,7 @@ class _UnionCase(unittest.TestCase):
kb.counters = self._scounters kb.counters = self._scounters
kb.injection.data = self._sinj_data kb.injection.data = self._sinj_data
conf.hashDB = self._shashdb conf.hashDB = self._shashdb
agent.forgeUnionQuery = self._s_forge restore_attrs(self._s_agent)
agent.concatQuery = self._s_concat
agent.payload = self._s_payload
unescaper.escape = self._s_escape unescaper.escape = self._s_escape
def _install_page(self, page): def _install_page(self, page):
@@ -325,11 +321,8 @@ class _UnionLimitCase(unittest.TestCase):
self._sinj_data = kb.injection.data self._sinj_data = kb.injection.data
self._shashdb = conf.get("hashDB") self._shashdb = conf.get("hashDB")
self._sbatch = conf.get("batch") self._sbatch = conf.get("batch")
self._s_forge = agent.forgeUnionQuery self._s_agent = save_attrs(agent, "forgeUnionQuery", "concatQuery", "payload", "_lastexpr")
self._s_concat = agent.concatQuery
self._s_payload = agent.payload
self._s_escape = unescaper.escape self._s_escape = unescaper.escape
self._s_lastexpr = getattr(agent, "_lastexpr", None)
self._s_initTechnique = uu.initTechnique self._s_initTechnique = uu.initTechnique
for k, v in _UU_CONF_LIMIT.items(): for k, v in _UU_CONF_LIMIT.items():
@@ -372,11 +365,8 @@ class _UnionLimitCase(unittest.TestCase):
kb.counters = self._scounters kb.counters = self._scounters
kb.injection.data = self._sinj_data kb.injection.data = self._sinj_data
conf.hashDB = self._shashdb conf.hashDB = self._shashdb
agent.forgeUnionQuery = self._s_forge restore_attrs(self._s_agent)
agent.concatQuery = self._s_concat
agent.payload = self._s_payload
unescaper.escape = self._s_escape unescaper.escape = self._s_escape
agent._lastexpr = self._s_lastexpr
uu.initTechnique = self._s_initTechnique uu.initTechnique = self._s_initTechnique
if self._s_columns is None: if self._s_columns is None:
@@ -500,10 +490,7 @@ class _ErrorCase(unittest.TestCase):
self._shashdb = conf.get("hashDB") self._shashdb = conf.get("hashDB")
self._sbatch = conf.get("batch") self._sbatch = conf.get("batch")
self._s_prefix = agent.prefixQuery self._s_agent = save_attrs(agent, "prefixQuery", "suffixQuery", "payload", "nullAndCastField")
self._s_suffix = agent.suffixQuery
self._s_payload = agent.payload
self._s_nullcast = agent.nullAndCastField
self._s_escape = unescaper.escape self._s_escape = unescaper.escape
# restore thread state we touch # restore thread state we touch
@@ -557,10 +544,7 @@ class _ErrorCase(unittest.TestCase):
kb.injection.data = self._sinj_data kb.injection.data = self._sinj_data
conf.hashDB = self._shashdb conf.hashDB = self._shashdb
agent.prefixQuery = self._s_prefix restore_attrs(self._s_agent)
agent.suffixQuery = self._s_suffix
agent.payload = self._s_payload
agent.nullAndCastField = self._s_nullcast
unescaper.escape = self._s_escape unescaper.escape = self._s_escape
td = getCurrentThreadData() td = getCurrentThreadData()
+7
View File
@@ -26,6 +26,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _testutils import bootstrap, set_dbms, reset_dbms from _testutils import bootstrap, set_dbms, reset_dbms
bootstrap() bootstrap()
from lib.core.agent import agent
from lib.core.data import conf, kb from lib.core.data import conf, kb
from lib.core.datatype import AttribDict from lib.core.datatype import AttribDict
from lib.core.enums import PAYLOAD, PLACE from lib.core.enums import PAYLOAD, PLACE
@@ -74,6 +75,12 @@ class TestOrderByColumnCount(unittest.TestCase):
ut.Request.queryPage = self._sqp ut.Request.queryPage = self._sqp
def _detect(self, true_count): def _detect(self, true_count):
# canary: a leaked agent.payload stub (another module patching the shared singleton and
# restoring it by assignment) makes every probe below identical, so the ORDER BY oracle turns
# unusable and this test used to fail as a bare 'None != 25'. Name the cause instead.
self.assertNotIn("payload", agent.__dict__,
"agent.payload is stubbed - a test module leaked it (see _testutils.save_attrs)")
def oracle(payload=None, place=None, content=False, raise404=True, **kwargs): def oracle(payload=None, place=None, content=False, raise404=True, **kwargs):
m = re.search(r"ORDER BY (\d+)", payload or "") m = re.search(r"ORDER BY (\d+)", payload or "")
cols = int(m.group(1)) if m else 1 cols = int(m.group(1)) if m else 1