-
-
Notifications
You must be signed in to change notification settings - Fork 6.3k
Expand file tree
/
Copy path_testutils.py
More file actions
177 lines (136 loc) · 7.28 KB
/
Copy path_testutils.py
File metadata and controls
177 lines (136 loc) · 7.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#!/usr/bin/env python
"""
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
See the file 'LICENSE' for copying permission
Shared bootstrap for the sqlmap unit/regression test suite.
Brings sqlmap's global state (conf/kb, the 'reversible' codec, cross-references,
option defaults) up far enough that pure/near-pure library functions can be
exercised in isolation - WITHOUT a live target, network, or DBMS.
stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x.
"""
import os
import sys
import warnings
# Quieten import-time noise before any sqlmap/3rd-party module is imported by bootstrap():
# e.g. cryptography's "Python 2 is no longer supported" CryptographyDeprecationWarning via pymysql.
warnings.filterwarnings("ignore", message=".*Python 2 is no longer supported.*")
warnings.filterwarnings("ignore", category=DeprecationWarning)
# sqlmap reconfigures stdout at startup; py3 emits a benign RuntimeWarning about line buffering
warnings.filterwarnings("ignore", message=".*line buffering.*binary mode.*")
_BOOTSTRAPPED = False
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def bootstrap():
"""Idempotently initialize sqlmap global state for testing."""
global _BOOTSTRAPPED
if _BOOTSTRAPPED:
return
if ROOT not in sys.path:
sys.path.insert(0, ROOT)
# a dummy target so cmdLineParser() populates ALL option defaults without erroring;
# save/restore the real argv so the unittest runner isn't confused by it
_orig_argv = list(sys.argv)
sys.argv = ["sqlmap.py", "-u", "http://test.invalid/?id=1"]
from lib.core.common import setPaths
from lib.core.patch import dirtyPatches, resolveCrossReferences
setPaths(ROOT)
dirtyPatches() # registers the 'reversible' codec error handler, etc.
resolveCrossReferences()
from lib.core.option import _setConfAttributes, _setKnowledgeBaseAttributes, _loadQueries
_setConfAttributes()
_setKnowledgeBaseAttributes()
_loadQueries() # populate the `queries` dict from queries.xml (needed by dialect builders)
from lib.core.data import conf, kb
from lib.core.defaults import defaults
from lib.parse.cmdline import cmdLineParser
args = cmdLineParser()
parsed = args.__dict__ if hasattr(args, "__dict__") else dict(args)
for k, v in parsed.items():
conf[k] = v
# overlay canonical defaults for options left None (sqlmap does this during init)
for k, v in defaults.items():
if conf.get(k) is None:
conf[k] = v
kb.binaryField = False # normally set lazily during extraction
# Silence sqlmap's application logger - tests assert on results, not log output, and the
# INFO/WARNING/ERROR chatter (column counts, reflective-value notices, an intentionally
# malformed-deflate error, etc.) just clutters the unittest report.
import logging
logging.getLogger("sqlmapLog").setLevel(logging.CRITICAL + 1)
# Some console output bypasses the logger entirely and goes straight through dataToStdout():
# the \r-progress lines ("[INFO] retrieved: ...", "[INFO] cracked password ..."), and the echo
# of batch-auto-answered readInput() prompts (the fingerprint-mismatch prompt, the LIKE/exact
# and common-wordlist choices, ...). dataToStdout() only writes forced output or when
# kb.wizardMode is False, and readInput() echoes with forceOutput=not kb.wizardMode - so setting
# wizardMode keeps the unittest report to just dots. wizardMode is read ONLY by dataToStdout/
# readInput (plus the interactive wizard flow, unused here), so this has no effect on results.
kb.wizardMode = True
sys.argv = _orig_argv # restore so unittest's arg parsing works
_BOOTSTRAPPED = True
def set_dbms(name):
"""Force the identified back-end DBMS for dialect-dependent functions.
Uses forceDbms (not setDbms) so switching DBMS repeatedly in one process does
not trigger the interactive fingerprint-mismatch prompt.
"""
from lib.core.common import Backend
from lib.core.data import kb
kb.stickyDBMS = False
Backend.forceDbms(name)
def reset_dbms():
"""Clear any DBMS forced via set_dbms()/Backend, restoring the clean post-bootstrap state.
A forced DBMS lives on the global `kb` singleton and is read by every dialect/agent path, so a
module that forces one without clearing it would leak that back-end into later test modules
(order-dependent flakiness). Modules that call set_dbms() should expose this as their
`tearDownModule` so the leak can never cross a module boundary.
"""
from lib.core.common import Backend
from lib.core.data import kb
from lib.core.settings import UNKNOWN_DBMS_VERSION
Backend.flushForcedDbms(force=True) # kb.forcedDbms = None; kb.stickyDBMS = False
kb.resolutionDbms = None
kb.dbmsVersion = [UNKNOWN_DBMS_VERSION]
# --- property/fuzz testing harness (shared so individual test files don't each reinvent it) ---
_PROPERTY_BASE = 0x51A1
class Rng(object):
"""Deterministic, cross-version-identical PRNG (a pure-integer LCG, no global state).
sqlmap runs on Python 2.7 and 3.x, whose stdlib `random` yield DIFFERENT sequences
for the same seed - and `random.Random` instance methods are not unified by
patch.unisonRandom() (which only patches the module-level random.choice/randint/
sample/seed). Property tests need inputs that are byte-for-byte identical on every
interpreter so a CI-only failure reproduces everywhere; integer math is identical
across versions, so this LCG (same constants as unisonRandom) guarantees it by
construction. Draw ONLY through these methods - never random.random()/shuffle()/etc.
"""
def __init__(self, seed):
self.x = seed & 0xFFFFFF
def _next(self):
self.x = (1140671485 * self.x + 128201163) % (2 ** 24)
return self.x
def randint(self, a, b):
return a + self._next() % (b - a + 1)
def choice(self, seq):
return seq[self.randint(0, len(seq) - 1)]
def sample(self, seq, k):
# Note: with replacement (matches unisonRandom's _sample); fine for input generation
return [self.choice(seq) for _ in range(k)]
def blob(self, n):
return bytes(bytearray(self.randint(0, 255) for _ in range(n)))
def _label_offset(label):
# stable across versions/runs (unlike hash(), which varies with PYTHONHASHSEED): just sum bytes
return sum(bytearray((label or "").encode("utf-8"))) * 7919
def for_all(testcase, generator, prop, n=400, label=""):
"""Property runner: draw `n` cases from generator(rng) and assert prop(case) holds.
`prop` passes by returning True/None, fails by returning False or raising. On any
failure the EXACT offending input and its case index are reported; the same input
is reproducible (and identical on every interpreter) via Rng(seed_for(label, i)).
"""
base = _PROPERTY_BASE + _label_offset(label)
for i in range(n):
case = generator(Rng(base + i))
try:
ok = prop(case)
except Exception as ex:
testcase.fail("%s: raised %r on input %r (case %d)" % (label or "property", ex, case, i))
return
if ok is False:
testcase.fail("%s: property does not hold on input %r (case %d)" % (label or "property", case, i))
return