#!/usr/bin/env python """ Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission Coverage for the XSLT injection engine (lib/techniques/xslt/inject.py) and the XQuery tier the XPath engine gained (lib/techniques/xpath/inject.py). Network-free: the payload builders, engine fingerprinting and charset construction are pure functions, so they are asserted directly. The parts that decide a VERDICT get the most attention - a detection tier that rests on "the page changed" is exactly the kind that invents findings, so its guards are pinned here. stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x. """ import os import re import sys import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from _testutils import bootstrap bootstrap() from lib.core.settings import XSLT_ERROR_REGEX from lib.core.settings import XSLT_RCE_PROBES from lib.core.settings import XSLT_VENDOR_PROPERTIES from lib.core.settings import XQUERY_CAPABILITY_PROBES from lib.core.settings import XQUERY_FILE_READ from lib.techniques.xslt import inject as _xslt from lib.techniques.xpath import inject as _xpath def _source(*parts): with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", *parts)) as f: return f.read() class XsltPayloadTest(unittest.TestCase): def test_element_payload_is_a_whole_instruction(self): payload = _xslt._elementPayload("'a'") self.assertTrue(payload.startswith("luther", "", None, "no results found"): self.assertFalse(_xslt._isError(page), repr(page)) def test_error_regex_is_anchored_to_xslt_vocabulary(self): """This regex also drives the GLOBAL heuristic hint, so a generic compiler/regex/SQL failure must not suggest '--xslt' on a target that has nothing to do with XSLT.""" for page in ("You have an error in your SQL syntax", "Traceback (most recent call last)", "org.postgresql.util.PSQLException", "500 Internal Server Error", "gcc: compilation error", "javac: compilation error", "Invalid expression at line 4", "regex: Invalid expression"): self.assertIsNone(re.search(XSLT_ERROR_REGEX, page), page) def test_error_regex_still_matches_every_real_engine(self): for page in ("xsltParseStylesheet : problem", "lxml.etree.XSLTParseError: bad", "XSLTProcessor::importStylesheet(): compilation error", "System.Xml.Xsl.XslLoadException", "net.sf.saxon.trans.XPathException: XTDE1260", "javax.xml.transform.TransformerConfigurationException"): self.assertIsNotNone(re.search(XSLT_ERROR_REGEX, page), page) def test_unquotable_paths_do_not_break_the_literal(self): """XPath 1.0 has no escape character, so a path with both quote kinds needs concat().""" self.assertEqual(_xslt._quote("/etc/passwd"), "'/etc/passwd'") self.assertEqual(_xslt._quote("/tmp/o'brien"), '"/tmp/o\'brien"') self.assertTrue(_xslt._quote("/tmp/bo\"th's").startswith("concat(")) def test_vendor_properties_are_xslt_defined(self): self.assertIn("xsl:vendor", XSLT_VENDOR_PROPERTIES) for prop in XSLT_VENDOR_PROPERTIES: self.assertTrue(prop.startswith("xsl:"), prop) class XsltRcePolicyTest(unittest.TestCase): """The extension surfaces are reported, never invoked - the probes must only ASK whether a function exists, never call it.""" def test_probes_only_test_availability(self): for label, expression in XSLT_RCE_PROBES: self.assertTrue(expression.startswith("string(function-available(") or expression.startswith("string(element-available("), expression) self.assertNotIn("Runtime.exec", expression) self.assertNotIn("system(", expression) def test_labels_say_what_the_primitive_is(self): joined = " ".join(label for label, _ in XSLT_RCE_PROBES).lower() for expected in ("php", "exsl", "saxon", "java"): self.assertIn(expected, joined) class XQueryTierTest(unittest.TestCase): def test_capability_probes_are_absent_from_xpath_1_0(self): """Each probe calls a function XPath 1.0 does not define, which is what makes a positive answer a demonstrated capability rather than an inference.""" for probe in XQUERY_CAPABILITY_PROBES: self.assertTrue(any(fn in probe for fn in ("string-join", "upper-case", "matches")), probe) def test_file_read_uses_unparsed_text(self): self.assertIn("unparsed-text", XQUERY_FILE_READ) self.assertEqual(XQUERY_FILE_READ % "'/etc/passwd'", "unparsed-text('/etc/passwd')") def test_file_charset_covers_tab_and_newline(self): """The XML-tree charset excludes them; a file recovered with newlines replaced by '?' is useless.""" self.assertIn(0x09, _xpath._FILE_ORDS) self.assertIn(0x0a, _xpath._FILE_ORDS) self.assertIn(ord('&'), _xpath._FILE_ORDS) def test_file_charset_literal_carries_no_transport_hazard(self): """Built from codepoints because the charset contains ' " & and < - a bare '&' is an invalid entity in an XQuery string literal AND splits a query-string parameter in two.""" literal = _xpath._FILE_LITERAL self.assertTrue(literal.startswith("codepoints-to-string((")) for hazard in ("'", '"', "&", "<"): self.assertNotIn(hazard, literal) def test_file_charset_literal_matches_the_ordinals(self): codes = [int(_) for _ in re.search(r"\(\((.*)\)\)", _xpath._FILE_LITERAL).group(1).split(",")] self.assertEqual(codes, _xpath._FILE_ORDS) def test_builders_accept_a_charset_override(self): """The override is what keeps the file read from disturbing XML-tree extraction.""" builder = _xpath._XPathPayloadBuilder("x", _xpath.Boundary("' or ", " and '1'='1", True)) self.assertIn("ZZZ", builder.charPresent("t", 1, "ZZZ")) self.assertIn("ZZZ", builder.charIndexAtLeast("t", 1, 2, "ZZZ")) self.assertIn(_xpath._CS_LITERAL, builder.charPresent("t", 1)) class SwitchWiringTest(unittest.TestCase): def test_switch_is_registered_and_mutually_exclusive(self): from lib.core.optiondict import optDict self.assertEqual(optDict["Techniques"].get("xslt"), "boolean") self.assertIn('("--xslt", conf.xslt)', _source("lib", "core", "option.py")) def test_controller_dispatches_the_scan(self): self.assertIn("from lib.techniques.xslt.inject import xsltScan", _source("lib", "controller", "controller.py")) if __name__ == "__main__": unittest.main()