mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-06-20 14:40:36 +00:00
141 lines
6.4 KiB
Python
141 lines
6.4 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
|
||
See the file 'LICENSE' for copying permission
|
||
|
||
Encoding / decoding / serialization round-trips and known vectors.
|
||
Covers: hex, base64 (std + url-safe), DBMS hex decode, byte<->text conversion,
|
||
JSON (de)serialization, restricted base64-pickle.
|
||
"""
|
||
|
||
import os
|
||
import random
|
||
import sys
|
||
import unittest
|
||
|
||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||
from _testutils import bootstrap, set_dbms
|
||
bootstrap()
|
||
|
||
from lib.core.convert import (decodeHex, encodeHex, decodeBase64, encodeBase64,
|
||
getBytes, getText, getUnicode, getOrds,
|
||
jsonize, dejsonize, base64pickle, base64unpickle)
|
||
from lib.core.common import decodeDbmsHexValue
|
||
from lib.core.enums import DBMS
|
||
|
||
RND = random.Random(0xC0FFEE)
|
||
|
||
|
||
def _rand_bytes(maxlen=48):
|
||
return bytes(bytearray(RND.randint(0, 255) for _ in range(RND.randint(0, maxlen))))
|
||
|
||
|
||
class TestHex(unittest.TestCase):
|
||
def test_known_vectors(self):
|
||
self.assertEqual(decodeHex("31323334", binary=True), b"1234")
|
||
self.assertEqual(getText(encodeHex(b"1234", binary=False)), "31323334")
|
||
|
||
def test_roundtrip_property(self):
|
||
for _ in range(3000):
|
||
raw = _rand_bytes()
|
||
self.assertEqual(decodeHex(encodeHex(raw, binary=False), binary=True), raw)
|
||
|
||
|
||
class TestBase64(unittest.TestCase):
|
||
def test_known_vectors(self):
|
||
self.assertEqual(decodeBase64("MTIz", binary=True), b"123")
|
||
self.assertEqual(decodeBase64("MTIzNA", binary=True), b"1234") # missing padding
|
||
self.assertEqual(decodeBase64("MTIzNA==", binary=True), b"1234")
|
||
self.assertEqual(getText(encodeBase64(b"123", binary=False)), "MTIz")
|
||
# url-safe and standard alphabets must decode equivalently
|
||
self.assertEqual(decodeBase64("A-B_CDE", binary=True), decodeBase64("A+B/CDE", binary=True))
|
||
|
||
def test_roundtrip_property(self):
|
||
for _ in range(3000):
|
||
raw = _rand_bytes()
|
||
self.assertEqual(decodeBase64(encodeBase64(raw, binary=True), binary=True), raw)
|
||
self.assertEqual(decodeBase64(encodeBase64(raw, binary=True, safe=True), binary=True), raw)
|
||
self.assertEqual(decodeBase64(encodeBase64(raw, binary=True, padding=False), binary=True), raw)
|
||
|
||
|
||
class TestDecodeDbmsHexValue(unittest.TestCase):
|
||
# authoritative vectors taken from the function's own doctests
|
||
def test_known_vectors(self):
|
||
self.assertEqual(decodeDbmsHexValue("3132332031"), u"123 1")
|
||
self.assertEqual(decodeDbmsHexValue("31003200330020003100"), u"123 1") # utf-16-le shaped
|
||
self.assertEqual(decodeDbmsHexValue("00310032003300200031"), u"123 1") # utf-16-be shaped
|
||
self.assertEqual(decodeDbmsHexValue("0x31003200330020003100"), u"123 1")
|
||
self.assertEqual(decodeDbmsHexValue("313233203"), u"123 ?") # odd length
|
||
self.assertEqual(decodeDbmsHexValue(["0x31", "0x32"]), [u"1", u"2"]) # list input
|
||
|
||
def test_ascii_roundtrip_property(self):
|
||
for _ in range(1000):
|
||
s = "".join(chr(RND.randint(0x20, 0x7e)) for _ in range(RND.randint(1, 30)))
|
||
if len(s) % 2 == 0: # avoid the deliberate odd-length '?' behavior
|
||
self.assertEqual(decodeDbmsHexValue(getText(encodeHex(getBytes(s), binary=False))), s)
|
||
|
||
|
||
class TestByteTextConversion(unittest.TestCase):
|
||
def test_ascii_roundtrip(self):
|
||
for _ in range(1000):
|
||
s = u"".join(unichr(RND.randint(0x20, 0x7e)) if sys.version_info[0] < 3 else chr(RND.randint(0x20, 0x7e)) for _ in range(RND.randint(0, 30)))
|
||
self.assertEqual(getUnicode(getBytes(s)), s)
|
||
|
||
def test_unicode_roundtrip(self):
|
||
samples = [u"café", u"你好", u"\U0001F600", u"a’b™c"]
|
||
for s in samples:
|
||
self.assertEqual(getUnicode(getBytes(s)), s)
|
||
|
||
def test_getords(self):
|
||
self.assertEqual(getOrds(b"AB"), [65, 66])
|
||
|
||
|
||
class TestJson(unittest.TestCase):
|
||
def test_roundtrip(self):
|
||
for obj in [{"a": 1, "b": [1, 2, 3]}, [1, "x", None], {"nested": {"k": "v"}}, "str", 123]:
|
||
self.assertEqual(dejsonize(jsonize(obj)), obj)
|
||
|
||
def test_jsonize_produces_text_not_identity(self):
|
||
# anchor: jsonize must serialize to a JSON string, not pass the object through
|
||
out = jsonize({"a": 1})
|
||
self.assertIsInstance(out, str)
|
||
self.assertIn('"a"', out)
|
||
self.assertEqual(jsonize(123), "123") # int -> textual "123"
|
||
|
||
|
||
class TestBase64Pickle(unittest.TestCase):
|
||
# Types sqlmap actually serializes (injection objects, cached values, BigArray).
|
||
def test_roundtrip_allowed_types(self):
|
||
for obj in [[1, 2, 3], {"a": 1}, (1, 2), "text", 42, 3.14, True, None, {"k": [1, {"n": "v"}]}]:
|
||
self.assertEqual(base64unpickle(base64pickle(obj)), obj)
|
||
|
||
# REGRESSION: under Python 3 + PICKLE_PROTOCOL=2 a raw `bytes` value is pickled via the
|
||
# `_codecs.encode` global. The RestrictedUnpickler allowlist (patch.py) once rejected that,
|
||
# so any serialized session value containing bytes failed to load on py3. The fix allows
|
||
# exactly `_codecs.encode` (a benign codec call). Bytes MUST round-trip on both py2 and py3.
|
||
def test_bytes_roundtrip(self):
|
||
for raw in [b"x", b"\x00\x01\xff", b"\xde\xad\xbe\xef"]:
|
||
self.assertEqual(base64unpickle(base64pickle(raw)), raw, msg="bytes round-trip %r" % raw)
|
||
|
||
def test_bytes_nested_in_container_roundtrip(self):
|
||
for obj in [{"a": b"bytes"}, [b"ab", "s", 1, None], ("t", b"\xde\xad")]:
|
||
self.assertEqual(base64unpickle(base64pickle(obj)), obj, msg="nested-bytes round-trip %r" % (obj,))
|
||
|
||
def test_dangerous_globals_still_blocked(self):
|
||
# bootstrap() installs sqlmap's RestrictedUnpickler over pickle.loads. These are VALID
|
||
# pickles that reference os.system / builtins.eval - stdlib would import them happily; the
|
||
# allowlist must reject them. Assert the SPECIFIC "forbidden" ValueError (not just any
|
||
# error) so the test proves the allowlist fired, not that the bytes failed to parse.
|
||
import pickle
|
||
for payload in (b"cos\nsystem\n.", b"c__builtin__\neval\n."):
|
||
try:
|
||
pickle.loads(payload)
|
||
self.fail("dangerous global was NOT blocked: %r" % payload)
|
||
except ValueError as ex:
|
||
self.assertIn("forbidden", str(ex), msg="unexpected error for %r: %s" % (payload, ex))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main(verbosity=2)
|