sqlmap/tests/test_kbchars.py
Miroslav Štampar 848ba68ccf Couple of fixes
2026-08-17 22:20:42 +02:00

91 lines
3.8 KiB
Python

#!/usr/bin/env python
"""
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
See the file 'LICENSE' for copying permission
Invariants of the random markers in kb.chars (lib/core/option.py).
These markers are drawn at random once per run, so a collision between two of them is a
heisenbug: it corrupts extraction only in the fraction of runs that happen to draw the same
value twice. Both collisions below were observed and are pinned here.
start/stop wrap the delimited output. Equal values make the output ambiguous
(a two-row result then carries four identical markers instead of
two pairs), so the parsing returns garbage or nothing at all.
at/space/dollar/hash_ stand in for '@', ' ', '$' and '#' inside a single chained REPLACE()
(see the Oracle XMLType vectors in error_based.xml). Two equal
markers make _errorReplaceChars() restore whichever character it
tries first, so e.g. an '@' in an e-mail column comes back as ' '.
The loop count is what matters: the smaller alphabet collides once every ~512 draws and the
single-letter one every ~5, so a single sample proves nothing.
stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x.
"""
import os
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _testutils import bootstrap
bootstrap()
from lib.core.data import kb
from lib.core.option import _setKnowledgeBaseAttributes
ROUNDS = 3000
MARKERS = ("start", "stop", "at", "space", "dollar", "hash_")
class KbCharsDistinctTest(unittest.TestCase):
"""Re-draws the markers many times. kb is global, so it is snapshotted and put back -
leaking a fresh kb into the rest of the suite is how this bug got noticed in the first place."""
def setUp(self):
self._saved = dict(kb)
def tearDown(self):
kb.clear()
kb.update(self._saved)
def test_markers_never_collide(self):
for i in range(ROUNDS):
_setKnowledgeBaseAttributes()
drawn = [getattr(kb.chars, _) for _ in MARKERS]
self.assertEqual(len(set(drawn)), len(MARKERS),
msg="colliding kb.chars markers on round %d: %s" % (i, dict(zip(MARKERS, drawn))))
def test_markers_never_contain_each_other(self):
# whole-string distinctness is not enough: the boundary character wrapping every marker used
# to be drawn for the inner letters as well, so a start marker could render as 'qzqxq', which
# carries the perfectly legal replacement marker 'qzq' (and 'qxq') inside it
for i in range(ROUNDS):
_setKnowledgeBaseAttributes()
drawn = [getattr(kb.chars, _) for _ in MARKERS]
for one in drawn:
for other in drawn:
if one is not other:
self.assertNotIn(other, one,
msg="kb.chars marker %r contains %r on round %d" % (one, other, i))
def test_markers_keep_their_shape(self):
# the fix must not change the on-the-wire length of a payload
for _ in range(ROUNDS // 100):
_setKnowledgeBaseAttributes()
self.assertEqual(len(kb.chars.start), 5)
self.assertEqual(len(kb.chars.stop), 5)
for name in ("at", "space", "dollar", "hash_"):
self.assertEqual(len(getattr(kb.chars, name)), 3, msg=name)
self.assertEqual(len(kb.chars.delimiter), 6)
def test_kb_is_restored(self):
# guards the snapshot above, so a later failure elsewhere is never blamed on this file
_setKnowledgeBaseAttributes()
self.assertNotEqual(dict(kb).get("chars"), self._saved.get("chars"))
if __name__ == "__main__":
unittest.main(verbosity=2)