From a4c983ae0685adf461124bcb06f898f86cfa7c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 25 Jul 2026 00:37:19 +0200 Subject: [PATCH] Adding native Brotli (br) and Zstandard (zstd) response decoding --- data/txt/brotli-dictionary.tx_ | Bin 0 -> 58679 bytes lib/core/common.py | 1 + lib/core/settings.py | 14 +- lib/request/basic.py | 17 +- lib/request/connect.py | 9 +- lib/utils/brotli.py | 652 +++++++++++++++++++++++++++++++++ tests/test_brotli.py | 69 ++++ 7 files changed, 755 insertions(+), 7 deletions(-) create mode 100644 data/txt/brotli-dictionary.tx_ create mode 100644 lib/utils/brotli.py create mode 100644 tests/test_brotli.py diff --git a/data/txt/brotli-dictionary.tx_ b/data/txt/brotli-dictionary.tx_ new file mode 100644 index 0000000000000000000000000000000000000000..b8354d0bdb5c5cac8e367138ff53378eaf50a345 GIT binary patch literal 58679 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ÐL%yg@L$To_9W%Ck1S@( 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 literal 0 HcmV?d00001 diff --git a/lib/core/common.py b/lib/core/common.py index 9220dcb9d..ab5edf895 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1594,6 +1594,7 @@ def setPaths(rootPath): paths.SMALL_DICT = os.path.join(paths.SQLMAP_TXT_PATH, "smalldict.txt") paths.USER_AGENTS = os.path.join(paths.SQLMAP_TXT_PATH, "user-agents.txt") paths.WORDLIST = os.path.join(paths.SQLMAP_TXT_PATH, "wordlist.tx_") + paths.BROTLI_DICTIONARY = os.path.join(paths.SQLMAP_TXT_PATH, "brotli-dictionary.tx_") paths.ERRORS_XML = os.path.join(paths.SQLMAP_XML_PATH, "errors.xml") paths.BOUNDARIES_XML = os.path.join(paths.SQLMAP_XML_PATH, "boundaries.xml") paths.QUERIES_XML = os.path.join(paths.SQLMAP_XML_PATH, "queries.xml") diff --git a/lib/core/settings.py b/lib/core/settings.py index 34d2b93d5..3cde0d5ca 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.183" +VERSION = "1.10.7.186" 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) @@ -250,8 +250,16 @@ TIME_DELAY_CANDIDATES = 3 # Default value for HTTP Accept header HTTP_ACCEPT_HEADER_VALUE = "*/*" -# Default value for HTTP Accept-Encoding header -HTTP_ACCEPT_ENCODING_HEADER_VALUE = "gzip,deflate" +# Whether the interpreter can decode Zstandard responses (stdlib 'compression.zstd', Python 3.14+ / PEP 784) +try: + import compression.zstd as _zstdModule +except ImportError: + _zstdModule = None +HTTP_ZSTD_AVAILABLE = _zstdModule is not None + +# Default value for HTTP Accept-Encoding header (browser-realistic; 'br' via the in-tree decoder, 'zstd' +# only when the stdlib provides it - never advertise a content-coding we cannot decode) +HTTP_ACCEPT_ENCODING_HEADER_VALUE = "gzip, deflate, br%s" % (", zstd" if HTTP_ZSTD_AVAILABLE else "") # Default timeout for running commands over backdoor BACKDOOR_RUN_CMD_TIMEOUT = 5 diff --git a/lib/request/basic.py b/lib/request/basic.py index f3541b60c..8ed3c06de 100644 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -12,6 +12,13 @@ import logging import re import zlib +from lib.utils import brotli as _brotli + +try: + from compression import zstd as _zstd # Python 3.14+ stdlib (PEP 784); no third-party dependency +except ImportError: + _zstd = None + from lib.core.common import Backend from lib.core.common import extractErrorMessage from lib.core.common import extractRegexResult @@ -297,7 +304,7 @@ def decodePage(page, contentEncoding, contentType, percentDecode=True): contentEncoding = getText(contentEncoding).lower() if contentEncoding else "" contentType = getText(contentType).lower() if contentType else "" - if contentEncoding in ("gzip", "x-gzip", "deflate"): + if contentEncoding in ("gzip", "x-gzip", "deflate", "br", "zstd"): if not kb.pageCompress: return None @@ -313,6 +320,14 @@ def decodePage(page, contentEncoding, contentType, percentDecode=True): page += obj.flush() if len(page) > MAX_CONNECTION_TOTAL_SIZE: raise Exception("size too large") + elif contentEncoding == "br": + page = _brotli.decompress(page, MAX_CONNECTION_TOTAL_SIZE) # in-tree RFC 7932 decoder (bomb-capped) + elif contentEncoding == "zstd": + if _zstd is None: + raise Exception("no Zstandard decoder available") + page = _zstd.decompress(page) + if len(page) > MAX_CONNECTION_TOTAL_SIZE: + raise Exception("size too large") else: data = gzip.GzipFile("", "rb", 9, io.BytesIO(page)) page = data.read(MAX_CONNECTION_TOTAL_SIZE + 1) diff --git a/lib/request/connect.py b/lib/request/connect.py index e3cd05b47..de374ed40 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -102,6 +102,7 @@ from lib.core.settings import DEFAULT_GET_POST_DELIMITER from lib.core.settings import DEFAULT_USER_AGENT from lib.core.settings import EVALCODE_ENCODED_PREFIX from lib.core.settings import HTTP_ACCEPT_ENCODING_HEADER_VALUE +from lib.core.settings import HTTP_ZSTD_AVAILABLE from lib.core.settings import HTTP_ACCEPT_HEADER_VALUE from lib.core.settings import IPS_WAF_CHECK_PAYLOAD from lib.core.settings import IS_WIN @@ -557,9 +558,11 @@ class Connect(object): for key, value in list(headers.items()): if key.upper() == HTTP_HEADER.ACCEPT_ENCODING.upper(): # keep only content-codings sqlmap can actually decode (see decodePage): a browser-pasted - # 'Accept-Encoding' (e.g. "gzip, deflate, br, zstd") must not make the server return a body - # we cannot read. Anything else (br, zstd, *, ...) is dropped, falling back to "identity". - value = ','.join(_ for _ in re.split(r"\s*,\s*", value) if _.split(';', 1)[0].strip().lower() in ("gzip", "x-gzip", "deflate", "identity")) or "identity" + # 'Accept-Encoding' must not make the server return a body we cannot read. 'br' is always + # decodable (in-tree decoder), 'zstd' only on interpreters that ship it; anything else is + # dropped, falling back to "identity". + decodable = ["gzip", "x-gzip", "deflate", "br", "identity"] + (["zstd"] if HTTP_ZSTD_AVAILABLE else []) + value = ','.join(_ for _ in re.split(r"\s*,\s*", value) if _.split(';', 1)[0].strip().lower() in decodable) or "identity" del headers[key] if isinstance(value, six.string_types): diff --git a/lib/utils/brotli.py b/lib/utils/brotli.py new file mode 100644 index 000000000..631a97ae1 --- /dev/null +++ b/lib/utils/brotli.py @@ -0,0 +1,652 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +# Native, dependency-free Brotli (RFC 7932) decompressor, so sqlmap can advertise a browser-realistic +# 'Accept-Encoding: gzip, deflate, br' and read 'Content-Encoding: br' responses (common behind CDNs) +# without pulling in the 'brotli'/'brotlicffi' third-party module. Decode-only: it is used solely to +# inflate server responses (see lib/request/basic.py::decodePage). Validated byte-for-byte against the +# reference encoder across every quality/window/size. The 122 KB static dictionary + context-lookup +# table live ZIP-packed in data/txt/brotli-dictionary.tx_ (same convention as wordlist.tx_). Py 2.7 / 3.x. + +import os +import zipfile + +_DICTIONARY = None # 122784-byte static dictionary (lazy-loaded) +_CONTEXT = None # 2048-byte context-lookup table (4 modes x 2 halves x 256) + +# RFC 7932 Appendix A: words are bucketed by length (4..24); size_bits gives the index width per bucket, +# offsets the cumulative byte offset of each bucket (derived from size_bits; last bucket end == 122784). +_SIZE_BITS = [0, 0, 0, 0, 10, 10, 11, 11, 10, 10, 10, 10, 10, 9, 9, 8, 7, 7, 8, 7, 7, 6, 6, 5, 5] +_OFFSETS = [0] * 25 +for _i in range(24): + _OFFSETS[_i + 1] = _OFFSETS[_i] + ((_i << _SIZE_BITS[_i]) if _SIZE_BITS[_i] else 0) + +# insert-length and copy-length codes (RFC 7932 section 5): (extra bits, base) per code 0..23 +_INS_EXTRA = [0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10, 12, 14, 24] +_INS_BASE = [0, 1, 2, 3, 4, 5, 6, 8, 10, 14, 18, 26, 34, 50, 66, 98, 130, 194, 322, 578, 1090, 2114, 6210, 22594] +_COPY_EXTRA = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10, 24] +_COPY_BASE = [2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 18, 22, 30, 38, 54, 70, 102, 134, 198, 326, 582, 1094, 2118] +# block-length code (RFC 7932 section 6): (extra bits, base) per code 0..25 +_BLEN_EXTRA = [2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8, 9, 10, 11, 12, 13, 24] +_BLEN_BASE = [1, 5, 9, 13, 17, 25, 33, 41, 49, 65, 81, 97, 113, 145, 177, 209, 241, 305, 369, 497, 753, 1265, 2289, 4337, 8433, 16625] + +# insert-and-copy command split (RFC 7932 section 5): per command range (code >> 6), the insert/copy +# sub-code base and whether the distance is implicit (codes 0..127 reuse the last distance) +_CMD_RANGE = [(0, 0, True), (0, 8, True), (0, 0, False), (0, 8, False), (8, 0, False), (8, 8, False), + (0, 16, False), (16, 0, False), (8, 16, False), (16, 8, False), (16, 16, False)] + +# code-length-code order and the fixed prefix used to read the 18 code-length code lengths (section 3.5) +_CL_ORDER = [1, 2, 3, 4, 0, 5, 17, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15] +_CLP_LEN = [2, 2, 2, 3, 2, 2, 2, 4, 2, 2, 2, 3, 2, 2, 2, 4] +_CLP_VAL = [0, 4, 3, 2, 0, 4, 3, 1, 0, 4, 3, 2, 0, 4, 3, 5] + +# distance short codes (RFC 7932 section 4): index into the 4-entry distance ring + a signed delta +_DIST_IDX_OFF = [3, 2, 1, 0, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2] +_DIST_VAL_OFF = [0, 0, 0, 0, -1, 1, -2, 2, -3, 3, -1, 1, -2, 2, -3, 3] + +# transform table (RFC 7932 Appendix B): (prefix, transform id, suffix); ids 0=identity, 1..9=omit-last-N, +# 10=uppercase-first, 11=uppercase-all, 12..20=omit-first-N +_TRANSFORMS = [ + (b"", 0, b""), + (b"", 0, b" "), + (b" ", 0, b" "), + (b"", 12, b""), + (b"", 10, b" "), + (b"", 0, b" the "), + (b" ", 0, b""), + (b"s ", 0, b" "), + (b"", 0, b" of "), + (b"", 10, b""), + (b"", 0, b" and "), + (b"", 13, b""), + (b"", 1, b""), + (b", ", 0, b" "), + (b"", 0, b", "), + (b" ", 10, b" "), + (b"", 0, b" in "), + (b"", 0, b" to "), + (b"e ", 0, b" "), + (b"", 0, b"\""), + (b"", 0, b"."), + (b"", 0, b"\">"), + (b"", 0, b"\x0a"), + (b"", 3, b""), + (b"", 0, b"]"), + (b"", 0, b" for "), + (b"", 14, b""), + (b"", 2, b""), + (b"", 0, b" a "), + (b"", 0, b" that "), + (b" ", 10, b""), + (b"", 0, b". "), + (b".", 0, b""), + (b" ", 0, b", "), + (b"", 15, b""), + (b"", 0, b" with "), + (b"", 0, b"'"), + (b"", 0, b" from "), + (b"", 0, b" by "), + (b"", 16, b""), + (b"", 17, b""), + (b" the ", 0, b""), + (b"", 4, b""), + (b"", 0, b". The "), + (b"", 11, b""), + (b"", 0, b" on "), + (b"", 0, b" as "), + (b"", 0, b" is "), + (b"", 7, b""), + (b"", 1, b"ing "), + (b"", 0, b"\x0a\x09"), + (b"", 0, b":"), + (b" ", 0, b". "), + (b"", 0, b"ed "), + (b"", 20, b""), + (b"", 18, b""), + (b"", 6, b""), + (b"", 0, b"("), + (b"", 10, b", "), + (b"", 8, b""), + (b"", 0, b" at "), + (b"", 0, b"ly "), + (b" the ", 0, b" of "), + (b"", 5, b""), + (b"", 9, b""), + (b" ", 10, b", "), + (b"", 10, b"\""), + (b".", 0, b"("), + (b"", 11, b" "), + (b"", 10, b"\">"), + (b"", 0, b"=\""), + (b" ", 0, b"."), + (b".com/", 0, b""), + (b" the ", 0, b" of the "), + (b"", 10, b"'"), + (b"", 0, b". This "), + (b"", 0, b","), + (b".", 0, b" "), + (b"", 10, b"("), + (b"", 10, b"."), + (b"", 0, b" not "), + (b" ", 0, b"=\""), + (b"", 0, b"er "), + (b" ", 11, b" "), + (b"", 0, b"al "), + (b" ", 11, b""), + (b"", 0, b"='"), + (b"", 11, b"\""), + (b"", 10, b". "), + (b" ", 0, b"("), + (b"", 0, b"ful "), + (b" ", 10, b". "), + (b"", 0, b"ive "), + (b"", 0, b"less "), + (b"", 11, b"'"), + (b"", 0, b"est "), + (b" ", 10, b"."), + (b"", 11, b"\">"), + (b" ", 0, b"='"), + (b"", 10, b","), + (b"", 0, b"ize "), + (b"", 11, b"."), + (b"\xc2\xa0", 0, b""), + (b" ", 0, b","), + (b"", 10, b"=\""), + (b"", 11, b"=\""), + (b"", 0, b"ous "), + (b"", 11, b", "), + (b"", 10, b"='"), + (b" ", 10, b","), + (b" ", 11, b"=\""), + (b" ", 11, b", "), + (b"", 11, b","), + (b"", 11, b"("), + (b"", 11, b". "), + (b" ", 11, b"."), + (b"", 11, b"='"), + (b" ", 11, b". "), + (b" ", 10, b"=\""), + (b" ", 11, b"='"), + (b" ", 10, b"='"), +] + + +class BrotliError(Exception): + pass + + +def _loadTables(): + global _DICTIONARY, _CONTEXT + if _DICTIONARY is not None: + return + + path = None + try: + from lib.core.data import paths + path = getattr(paths, "BROTLI_DICTIONARY", None) + except ImportError: + pass + if not path or not os.path.isfile(path): + path = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, "data", "txt", "brotli-dictionary.tx_") + + archive = zipfile.ZipFile(path) # ZIP-packed like wordlist.tx_ / catalog-identifiers.tx_ + try: + raw = archive.read(archive.namelist()[0]) + finally: + archive.close() + if len(raw) != 122784 + 2048: + raise BrotliError("invalid Brotli dictionary table") + _DICTIONARY = raw[:122784] + _CONTEXT = bytearray(raw[122784:]) + + +class _BitReader(object): + __slots__ = ("data", "size", "pos", "acc", "bits") + + def __init__(self, data): + self.data = bytearray(data) + self.size = len(self.data) + self.pos = 0 + self.acc = 0 + self.bits = 0 + + def _fill(self): + while self.bits <= 24 and self.pos < self.size: + self.acc |= self.data[self.pos] << self.bits + self.pos += 1 + self.bits += 8 + + def readBits(self, count): + if count == 0: + return 0 + if self.bits < count: + self._fill() + value = self.acc & ((1 << count) - 1) + self.acc >>= count + self.bits -= count + return value + + def peek(self, count): + if self.bits < count: + self._fill() + return self.acc & ((1 << count) - 1) + + def drop(self, count): + self.acc >>= count + self.bits -= count + + def alignToByte(self): + drop = self.bits & 7 + if drop: + self.acc >>= drop + self.bits -= drop + + def readBytes(self, count): + out = bytearray() + while count > 0 and self.bits >= 8: + out.append(self.acc & 0xff) + self.acc >>= 8 + self.bits -= 8 + count -= 1 + if count > 0: + out += self.data[self.pos:self.pos + count] + self.pos += count + return bytes(out) + + +def _reverseBits(value, count): + result = 0 + for _ in range(count): + result = (result << 1) | (value & 1) + value >>= 1 + return result + + +class _Huffman(object): + __slots__ = ("maxLength", "table", "single") + + def __init__(self, lengths): + self.single = None + self.table = None + self.maxLength = max(lengths) if lengths else 0 + if self.maxLength == 0: + self.single = 0 + for symbol, length in enumerate(lengths): + if length > 0: + self.single = symbol + return + + counts = [0] * (self.maxLength + 1) + for length in lengths: + if length: + counts[length] += 1 + nextCode = [0] * (self.maxLength + 2) + code = 0 + for bits in range(1, self.maxLength + 1): + code = (code + counts[bits - 1]) << 1 + nextCode[bits] = code + + self.table = [(0, 0)] * (1 << self.maxLength) + for symbol, length in enumerate(lengths): + if length: + reversed_ = _reverseBits(nextCode[length], length) + nextCode[length] += 1 + step = 1 << length + for index in range(reversed_, 1 << self.maxLength, step): + self.table[index] = (symbol, length) + + def decode(self, reader): + if self.table is None: + return self.single + symbol, length = self.table[reader.peek(self.maxLength)] + reader.drop(length) + return symbol + + +def _readSimplePrefix(reader, alphabetSize): + count = reader.readBits(2) + 1 + symbolBits = (alphabetSize - 1).bit_length() or 1 + symbols = [reader.readBits(symbolBits) for _ in range(count)] + lengths = [0] * alphabetSize + if count == 1: + huffman = _Huffman([]) + huffman.single = symbols[0] + return huffman + if count == 2: + pairs = [(symbols[0], 1), (symbols[1], 1)] + elif count == 3: + pairs = [(symbols[0], 1), (symbols[1], 2), (symbols[2], 2)] + elif reader.readBits(1): + pairs = [(symbols[0], 1), (symbols[1], 2), (symbols[2], 3), (symbols[3], 3)] + else: + pairs = [(symbols[0], 2), (symbols[1], 2), (symbols[2], 2), (symbols[3], 2)] + for symbol, length in pairs: + lengths[symbol] = length + return _Huffman(lengths) + + +def _readComplexPrefix(reader, alphabetSize, skip): + codeLengths = [0] * 18 + space = 32 + for symbol in _CL_ORDER[skip:]: + index = reader.peek(4) + codeLengths[symbol] = _CLP_VAL[index] + reader.drop(_CLP_LEN[index]) + if codeLengths[symbol]: + space -= 32 >> codeLengths[symbol] + if space <= 0: + break + codeLengthHuffman = _Huffman(codeLengths) + + lengths = [0] * alphabetSize + symbol = 0 + previous = 8 + repeat = 0 + repeatLength = 0 + space = 32768 + while symbol < alphabetSize and space > 0: + code = codeLengthHuffman.decode(reader) + if code < 16: + lengths[symbol] = code + symbol += 1 + if code: + previous = code + space -= 32768 >> code + repeat = 0 + else: + extra = 2 if code == 16 else 3 + newLength = previous if code == 16 else 0 + if repeatLength != newLength: + repeat = 0 + repeatLength = newLength + old = repeat + delta = reader.readBits(extra) + if repeat > 0: + repeat = (repeat - 2) << extra + repeat += delta + 3 + emit = repeat - old + for _ in range(emit): + if symbol >= alphabetSize: + break + lengths[symbol] = repeatLength + symbol += 1 + if repeatLength: + space -= emit << (15 - repeatLength) + return _Huffman(lengths) + + +def _readPrefix(reader, alphabetSize): + header = reader.readBits(2) + if header == 1: + return _readSimplePrefix(reader, alphabetSize) + return _readComplexPrefix(reader, alphabetSize, header) + + +def _readBlockTypeCount(reader): + if not reader.readBits(1): + return 1 + bits = reader.readBits(3) + return (1 << bits) + 1 + reader.readBits(bits) + + +def _readContextMap(reader, treeCount, size): + maxRun = reader.readBits(4) + 1 if reader.readBits(1) else 0 + huffman = _readPrefix(reader, treeCount + maxRun) + contextMap = [] + while len(contextMap) < size: + code = huffman.decode(reader) + if code == 0: + contextMap.append(0) + elif code <= maxRun: + contextMap.extend([0] * ((1 << code) + reader.readBits(code))) + else: + contextMap.append(code - maxRun) + del contextMap[size:] + if reader.readBits(1): # inverse move-to-front + moveToFront = list(range(256)) + for i in range(len(contextMap)): + index = contextMap[i] + value = moveToFront[index] + contextMap[i] = value + del moveToFront[index] + moveToFront.insert(0, value) + return contextMap + + +def _toUpperCase(word, offset): + char = word[offset] + if char < 0xc0: # ASCII: flip case of a-z + if 97 <= char <= 122: + word[offset] = char ^ 32 + return 1 + if char < 0xe0: # 2-byte UTF-8 + if offset + 1 < len(word): + word[offset + 1] ^= 32 + return 2 + if offset + 2 < len(word): # 3-byte UTF-8 + word[offset + 2] ^= 5 + return 3 + + +def _applyTransform(transformId, word): + prefix, kind, suffix = _TRANSFORMS[transformId] + result = bytearray(word) + if kind == 0: + pass + elif 1 <= kind <= 9: # omit last N + result = result[:len(result) - kind] if len(result) >= kind else bytearray() + elif 12 <= kind <= 20: # omit first N + count = kind - 11 + result = result[count:] if len(result) >= count else bytearray() + elif kind == 10: # uppercase first + if result: + _toUpperCase(result, 0) + elif kind == 11: # uppercase all + offset = 0 + while offset < len(result): + offset += _toUpperCase(result, offset) + return prefix + bytes(result) + suffix + + +def decompress(data, maxOutput=100 * 1024 * 1024): + """Decompress a Brotli (RFC 7932) stream, returning the original bytes. Raises BrotliError on a + malformed stream or if the output would exceed 'maxOutput' (an anti-decompression-bomb cap).""" + + _loadTables() + dictionary = _DICTIONARY + context = _CONTEXT + + try: + reader = _BitReader(data) + header = reader.readBits(1) + if header == 0: + windowBits = 16 + else: + header = reader.readBits(3) + if header: + windowBits = 17 + header + else: + header = reader.readBits(3) + windowBits = (8 + header) if header else 17 + maxBackward = (1 << windowBits) - 16 + + out = bytearray() + distRing = [16, 15, 11, 4] + distIndex = 0 + + while True: + isLast = reader.readBits(1) + if isLast and reader.readBits(1): # ISLASTEMPTY + break + + nibbles = reader.readBits(2) + if nibbles == 3: # metadata block (no output) + if reader.readBits(1): + raise BrotliError("reserved bit set") + skipBytes = reader.readBits(2) + if skipBytes: + skipLength = reader.readBits(skipBytes * 8) + 1 + reader.alignToByte() + reader.readBytes(skipLength) + if isLast: + break + continue + + metaLength = reader.readBits((nibbles + 4) * 4) + 1 + if len(out) + metaLength > maxOutput: # reject an over-large block up front (anti-bomb) + raise BrotliError("output too large") + if not isLast and reader.readBits(1): # ISUNCOMPRESSED + reader.alignToByte() + out += reader.readBytes(metaLength) + if len(out) > maxOutput: + raise BrotliError("output too large") + continue + + typesL = _readBlockTypeCount(reader) + blockL, typeHuffmanL, lengthHuffmanL, prevTypeL = 1 << 28, None, None, 1 + typeL = 0 + if typesL >= 2: + typeHuffmanL = _readPrefix(reader, typesL + 2) + lengthHuffmanL = _readPrefix(reader, 26) + code = lengthHuffmanL.decode(reader) + blockL = _BLEN_BASE[code] + reader.readBits(_BLEN_EXTRA[code]) + + typesI = _readBlockTypeCount(reader) + blockI, typeHuffmanI, lengthHuffmanI, prevTypeI = 1 << 28, None, None, 1 + typeI = 0 + if typesI >= 2: + typeHuffmanI = _readPrefix(reader, typesI + 2) + lengthHuffmanI = _readPrefix(reader, 26) + code = lengthHuffmanI.decode(reader) + blockI = _BLEN_BASE[code] + reader.readBits(_BLEN_EXTRA[code]) + + typesD = _readBlockTypeCount(reader) + blockD, typeHuffmanD, lengthHuffmanD, prevTypeD = 1 << 28, None, None, 1 + typeD = 0 + if typesD >= 2: + typeHuffmanD = _readPrefix(reader, typesD + 2) + lengthHuffmanD = _readPrefix(reader, 26) + code = lengthHuffmanD.decode(reader) + blockD = _BLEN_BASE[code] + reader.readBits(_BLEN_EXTRA[code]) + + postfix = reader.readBits(2) + direct = reader.readBits(4) << postfix + contextModes = [reader.readBits(2) for _ in range(typesL)] + + treesL = _readBlockTypeCount(reader) + contextMapL = _readContextMap(reader, treesL, typesL * 64) if treesL >= 2 else [0] * (typesL * 64) + treesD = _readBlockTypeCount(reader) + contextMapD = _readContextMap(reader, treesD, typesD * 4) if treesD >= 2 else [0] * (typesD * 4) + + huffmanL = [_readPrefix(reader, 256) for _ in range(treesL)] + huffmanI = [_readPrefix(reader, 704) for _ in range(typesI)] + distanceAlphabet = 16 + direct + (48 << postfix) + huffmanD = [_readPrefix(reader, distanceAlphabet) for _ in range(treesD)] + + produced = 0 + while produced < metaLength: + if blockI == 0: + code = typeHuffmanI.decode(reader) + nextType = prevTypeI if code == 0 else ((typeI + 1) % typesI if code == 1 else code - 2) + prevTypeI, typeI = typeI, nextType + code = lengthHuffmanI.decode(reader) + blockI = _BLEN_BASE[code] + reader.readBits(_BLEN_EXTRA[code]) + blockI -= 1 + + command = huffmanI[typeI].decode(reader) + insertBase, copyBase, implicit = _CMD_RANGE[command >> 6] + insertCode = insertBase + ((command >> 3) & 7) + copyCode = copyBase + (command & 7) + insertLength = _INS_BASE[insertCode] + reader.readBits(_INS_EXTRA[insertCode]) + copyLength = _COPY_BASE[copyCode] + reader.readBits(_COPY_EXTRA[copyCode]) + # a well-formed command never inserts beyond the meta-block; bounding here keeps a hostile + # stream from spinning the literal loop far past the output cap before it is caught (the + # copy length is checked in the back-reference branch, and dictionary copies are <= 24) + if produced + insertLength > metaLength: + raise BrotliError("insert exceeds meta-block length") + + for _ in range(insertLength): + if blockL == 0: + code = typeHuffmanL.decode(reader) + nextType = prevTypeL if code == 0 else ((typeL + 1) % typesL if code == 1 else code - 2) + prevTypeL, typeL = typeL, nextType + code = lengthHuffmanL.decode(reader) + blockL = _BLEN_BASE[code] + reader.readBits(_BLEN_EXTRA[code]) + blockL -= 1 + mode = contextModes[typeL] * 512 + p1 = out[-1] if out else 0 + p2 = out[-2] if len(out) >= 2 else 0 + contextId = context[mode + p1] | context[mode + 256 + p2] + out.append(huffmanL[contextMapL[64 * typeL + contextId]].decode(reader)) + produced += 1 + + if produced >= metaLength: + break + + if implicit: + distanceCode = 0 + else: + if blockD == 0: + code = typeHuffmanD.decode(reader) + nextType = prevTypeD if code == 0 else ((typeD + 1) % typesD if code == 1 else code - 2) + prevTypeD, typeD = typeD, nextType + code = lengthHuffmanD.decode(reader) + blockD = _BLEN_BASE[code] + reader.readBits(_BLEN_EXTRA[code]) + blockD -= 1 + distanceContext = min(copyLength - 2, 3) if copyLength >= 2 else 0 + distanceCode = huffmanD[contextMapD[4 * typeD + distanceContext]].decode(reader) + + if distanceCode < 16: + distance = distRing[(distIndex + _DIST_IDX_OFF[distanceCode]) & 3] + _DIST_VAL_OFF[distanceCode] + else: + value = distanceCode - 16 + if value < direct: + distance = value + 1 + else: + value -= direct + extraBits = 1 + (value >> (postfix + 1)) + extra = reader.readBits(extraBits) + high = value >> postfix + low = value & ((1 << postfix) - 1) + distance = ((((2 + (high & 1)) << extraBits) - 4 + extra) << postfix) + low + direct + 1 + + maxDistance = min(len(out), maxBackward) + if distanceCode != 0 and distance <= maxDistance: + distRing[distIndex & 3] = distance + distIndex += 1 + + if distance <= maxDistance: # ordinary back-reference (may overlap) + if produced + copyLength > metaLength: # can't copy past the block (also bounds the loop) + raise BrotliError("copy exceeds meta-block length") + source = len(out) - distance + for i in range(copyLength): + out.append(out[source + i]) + produced += 1 + else: # static-dictionary reference + offset = distance - maxDistance - 1 + if not (4 <= copyLength <= 24) or _SIZE_BITS[copyLength] == 0: + raise BrotliError("invalid dictionary reference") + bits = _SIZE_BITS[copyLength] + index = offset & ((1 << bits) - 1) + transformId = offset >> bits + if transformId >= len(_TRANSFORMS): + raise BrotliError("invalid dictionary transform") + start = _OFFSETS[copyLength] + index * copyLength + word = _applyTransform(transformId, dictionary[start:start + copyLength]) + out += word + produced += len(word) + + if len(out) > maxOutput: + raise BrotliError("output too large") + + if isLast: + break + return bytes(out) + except BrotliError: + raise + except Exception as ex: + raise BrotliError("malformed Brotli stream (%s)" % ex) diff --git a/tests/test_brotli.py b/tests/test_brotli.py new file mode 100644 index 000000000..425250366 --- /dev/null +++ b/tests/test_brotli.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Tests for the dependency-free Brotli (RFC 7932) decompressor under lib/utils/brotli.py. The compressed +fixtures were produced by the reference encoder at various quality levels; the expected plaintext is +reconstructed here by construction, so the suite validates the decoder fully offline (no third-party +'brotli' module at test time) on Python 2.7 / 3.x. Cases deliberately exercise the static dictionary + +word transforms, long overlapping copies, UTF-8, the low-quality (near-uniform tree) path and the +repetitive content that relies on the higher insert-and-copy command ranges. +""" + +import binascii +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.utils.brotli import decompress +from lib.utils.brotli import BrotliError + + +# (expected plaintext, reference-compressed stream in hex) +_CASES = [ + (b"", "3b"), + (b"The quick brown fox jumps over the lazy dog.", + "8b158054686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f672e03"), + (b"the time of the data on the site is now and the code" * 3, + "1b9b000004e164a9be171b85c00636e080bd799109f64571f090852e78334d90cb20ac9c346c190ff171965a3d2f7a90171c"), + (b"the quick brown fox " * 30, + "1b570200047463a92ee78362f22082d628041695d90acef9a3f135e9c701"), + (b"AB" * 400, "1b1f0300a48284a2b230b009"), + ((u"caf\xe9 na\xefve \u4f60\u597d ").encode("utf-8") * 12, + "1bef00004427477ad6d60ac38c93200a288ab462c2a06461d22d186dbbe0263e0707"), + (b"hello hello hello world world foo bar baz " * 6, + "8b7d000080aaaaaaeaff74e5f355048415f8c0000c201701d0ffbbeadf736f75cfa82e6f63b82b5e2c2c2c6c6cacea654675f0e1c38fc160308e33595583c16030180ce65067442a4aa370586827d97b828968074727f5b21e97eebd045d8baeefef94c3fca4fb1e"), +] + + +class TestBrotli(unittest.TestCase): + def test_known_fixtures(self): + for expected, hexstream in _CASES: + self.assertEqual(decompress(binascii.unhexlify(hexstream)), expected) + + def test_empty_stream(self): + self.assertEqual(decompress(binascii.unhexlify("3b")), b"") + + def test_malformed_raises(self): + # a hostile/truncated stream must surface as BrotliError, never a raw exception + for blob in (b"\xff", b"\x00\x00\x00", b"\x1b\xff\xff\xff\xff", os.urandom(32)): + try: + decompress(blob) + except BrotliError: + pass + except Exception as ex: + self.fail("non-BrotliError on malformed input: %s" % ex) + + def test_bomb_cap(self): + # a small stream must not be allowed to expand past the output cap + self.assertRaises(BrotliError, decompress, binascii.unhexlify("1b1f0300a48284a2b230b009"), 16) + + +if __name__ == "__main__": + unittest.main()