mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-06-20 06:28:55 +00:00
Adding new unittest
This commit is contained in:
parent
91bf58b54e
commit
7c401cab64
3 changed files with 124 additions and 2 deletions
|
|
@ -188,7 +188,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch
|
|||
48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py
|
||||
0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py
|
||||
888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py
|
||||
d9180ce5490c781b8f8771b0d5754d27f550aae963ad36731e0d0941a0f8590c lib/core/settings.py
|
||||
36122bca78fe2d2a3b9d2c882ef0ab05a4f4032b3eac7b6c8974871997c24429 lib/core/settings.py
|
||||
cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py
|
||||
bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py
|
||||
70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py
|
||||
|
|
@ -599,6 +599,7 @@ f3a628db8a3e05baee580c02132e95b164695e4b3ee1785707e3ea148702449a tests/test_tam
|
|||
b3e13febe9e0ff6f97334f2868655bfdbaa18755e464a6dc4c6d424f513bad02 tests/test_targeturl.py
|
||||
639851dc68f62b559b200b09c308e64e453f414969940005bac75dc0ab07a6b6 tests/test_texthelpers.py
|
||||
708b3c040f8b677a84020dd6f7c4242f77260b3c6d2697fe8189e1881b0e1365 tests/test_union_engine.py
|
||||
48b0ae4abe0fdde8ce4975c5cbf4c3514a2815021cb2e3a490a189bea5edfe78 tests/test_unpickle_security.py
|
||||
4b646f513c6da1e33200184ed6eabe0aa345eb2e2a19598dc123e191168591bf tests/test_urls.py
|
||||
4f095ebda1b9bddde082ed464e863400cf23e9bf26f081948706213b35069195 tests/_testutils.py
|
||||
2364db35025a53ea4e5a0a80c034997642785f7e6d1566d0d0f1db959fe3c82e tests/test_utils.py
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from lib.core.enums import OS
|
|||
from thirdparty import six
|
||||
|
||||
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
|
||||
VERSION = "1.10.6.111"
|
||||
VERSION = "1.10.6.112"
|
||||
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)
|
||||
|
|
|
|||
121
tests/test_unpickle_security.py
Normal file
121
tests/test_unpickle_security.py
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
|
||||
See the file 'LICENSE' for copying permission
|
||||
|
||||
Locks the RestrictedUnpickler security control (lib/core/patch.py, installed over
|
||||
pickle.loads by dirtyPatches()). sqlmap deserializes pickled blobs out of its own
|
||||
session DB / cache, so the unpickler is an ALLOWLIST: only safe builtin data types
|
||||
and sqlmap's own (lib/plugins/thirdparty) classes may be reconstructed.
|
||||
|
||||
Two directions, both of which must keep holding:
|
||||
- LEGIT round-trips sqlmap actually relies on (AttribDict, BigArray, nested
|
||||
builtins, and - the easy-to-regress one - bytes under PICKLE_PROTOCOL=2, which
|
||||
emits a _codecs.encode global) must survive base64pickle -> base64unpickle.
|
||||
- MALICIOUS / exotic globals (eval, os.system, subprocess.Popen, importlib,
|
||||
operator.attrgetter, and even the non-whitelisted _codecs.lookup) must be
|
||||
REJECTED at find_class time, before the object is ever built.
|
||||
|
||||
A regression in either direction is a security or a data-loss bug, hence the test.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pickle
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from _testutils import bootstrap
|
||||
bootstrap() # installs dirtyPatches(), i.e. the RestrictedUnpickler over pickle.loads
|
||||
|
||||
from lib.core.bigarray import BigArray
|
||||
from lib.core.convert import base64pickle, base64unpickle, encodeBase64
|
||||
from lib.core.datatype import AttribDict
|
||||
from lib.core.settings import PICKLE_PROTOCOL
|
||||
|
||||
|
||||
class _EvilReduce(object):
|
||||
"""On unpickling, __reduce__ asks the loader to resolve (and would call) an arbitrary global."""
|
||||
def __init__(self, func, args):
|
||||
self._func = func
|
||||
self._args = args
|
||||
|
||||
def __reduce__(self):
|
||||
return (self._func, self._args)
|
||||
|
||||
|
||||
def _payload(func, *args):
|
||||
# built with the REAL pickler (only pickle.loads is restricted, not dumps); base64 to mirror
|
||||
# exactly what base64unpickle() consumes from sqlmap's session store
|
||||
return encodeBase64(pickle.dumps(_EvilReduce(func, args), PICKLE_PROTOCOL), binary=False)
|
||||
|
||||
|
||||
class TestUnpicklerIsInstalled(unittest.TestCase):
|
||||
def test_patch_active(self):
|
||||
# if this is False the whole allowlist is bypassed and the negative tests would pass vacuously
|
||||
self.assertTrue(getattr(pickle, "_patched", False))
|
||||
|
||||
|
||||
class TestLegitRoundTrips(unittest.TestCase):
|
||||
def _roundtrip(self, value):
|
||||
return base64unpickle(base64pickle(value))
|
||||
|
||||
def test_nested_builtins(self):
|
||||
value = {"a": [1, 2.5, True, None, complex(1, 2)], "b": (u"x", b"y"), "c": {3, 4}, "d": frozenset([5])}
|
||||
self.assertEqual(self._roundtrip(value), value)
|
||||
|
||||
def test_bytes_protocol2(self):
|
||||
# protocol-2 pickling of bytes on Python 3 emits a _codecs.encode global; this is the
|
||||
# exact case the allowlist explicitly permits, and the one most likely to silently break
|
||||
for value in (b"", b"\x00\x01\x02binary\xff", bytearray(b"abc")):
|
||||
self.assertEqual(self._roundtrip(value), value)
|
||||
|
||||
def test_attribdict(self):
|
||||
value = AttribDict()
|
||||
value.foo = "bar"
|
||||
value.nested = {"k": [1, 2]}
|
||||
restored = self._roundtrip(value)
|
||||
self.assertIsInstance(restored, AttribDict)
|
||||
self.assertEqual(restored.foo, "bar")
|
||||
self.assertEqual(restored.nested, {"k": [1, 2]})
|
||||
|
||||
def test_bigarray(self):
|
||||
restored = self._roundtrip(BigArray([1, 2, 3]))
|
||||
self.assertIsInstance(restored, BigArray)
|
||||
self.assertEqual(list(restored), [1, 2, 3])
|
||||
|
||||
|
||||
class TestMaliciousRejected(unittest.TestCase):
|
||||
def _assert_blocked(self, payload):
|
||||
# find_class() raises ValueError; base64unpickle only swallows TypeError, so it propagates
|
||||
self.assertRaises(ValueError, base64unpickle, payload)
|
||||
|
||||
def test_dangerous_builtins(self):
|
||||
# builtins are allowed ONLY for the safe data-type subset; callables must be refused
|
||||
for func in (eval, getattr, __import__):
|
||||
self._assert_blocked(_payload(func, "1+1") if func is eval else _payload(func, "x"))
|
||||
|
||||
def test_os_system(self):
|
||||
self._assert_blocked(_payload(os.system, "echo pwned"))
|
||||
|
||||
def test_subprocess_popen(self):
|
||||
self._assert_blocked(_payload(subprocess.Popen, "echo pwned"))
|
||||
|
||||
def test_importlib(self):
|
||||
import importlib
|
||||
self._assert_blocked(_payload(importlib.import_module, "os"))
|
||||
|
||||
def test_operator_attrgetter(self):
|
||||
import operator
|
||||
self._assert_blocked(_payload(operator.attrgetter, "system"))
|
||||
|
||||
def test_codecs_lookup_not_whitelisted(self):
|
||||
# only _codecs.encode is allowed (for the bytes round-trip); every other _codecs name stays blocked
|
||||
import codecs
|
||||
self._assert_blocked(_payload(codecs.lookup, "utf-8"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue