#!/usr/bin/env python
"""
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
See the file 'LICENSE' for copying permission
Offline, deterministic tests for the XXE injection engine. Pure helpers are exercised
directly; detection tiers run against a mocked _send() so reflected/error/echo oracles
can be simulated without a live target; and crafted payloads are parsed with real lxml
to prove they are well-formed and actually expand the injected entity.
"""
import os
import re
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _testutils import bootstrap
bootstrap()
import lib.techniques.xxe.inject as xxe
from lib.core.data import conf
from lib.core.data import kb
class TestLooksXmlAndClean(unittest.TestCase):
def test_looks_xml(self):
self.assertTrue(xxe._looksXml("x"))
self.assertTrue(xxe._looksXml(" "))
self.assertFalse(xxe._looksXml("id=1&name=x"))
self.assertFalse(xxe._looksXml("{\"a\": 1}"))
self.assertFalse(xxe._looksXml(""))
def test_clean_body_strips_marks_and_bom(self):
conf.data = u"\ufeffluther%s" % (kb.customInjectionMark or "*")
cleaned = xxe._cleanBody()
self.assertFalse(cleaned.startswith(u"\ufeff"))
self.assertNotIn(kb.customInjectionMark or "*", cleaned)
self.assertTrue(cleaned.startswith(""))
class TestRootName(unittest.TestCase):
def test_plain(self):
self.assertEqual(xxe._rootName("x"), "user")
def test_with_prolog_and_comment(self):
self.assertEqual(xxe._rootName("x"), "order")
def test_namespaced(self):
self.assertEqual(xxe._rootName(''), "soap:Envelope")
def test_existing_doctype_skipped(self):
self.assertEqual(xxe._rootName(''), "user")
class TestBuildDoctype(unittest.TestCase):
SUBSET = ''
def test_no_doctype_prepended(self):
out = xxe._buildDoctype("x", "r", self.SUBSET)
self.assertIn("x", "r", self.SUBSET)
self.assertLess(out.index("]>x", "r", self.SUBSET)
self.assertEqual(out.count("x', "r", self.SUBSET)
self.assertEqual(out.count("onetwo
", "&e;")
self.assertEqual(out.count("&e;"), 2)
self.assertNotIn("one", out)
self.assertNotIn("two", out)
def test_attributes_only_when_requested(self):
text = 'luther'
self.assertNotIn('id="&e;"', xxe._placeRef(text, "&e;")) # attrs off by default
self.assertIn('id="&e;"', xxe._placeRef(text, "&e;", attrs=True)) # attrs on
def test_xmlns_preserved(self):
out = xxe._placeRef('x', "&e;", attrs=True)
self.assertIn('xmlns:soap="ns"', out) # namespace decl untouched
def test_self_closing_fallback(self):
out = xxe._placeRef("", "&e;")
self.assertIn("&e;", out)
self.assertIn("", out)
def test_empty_element_fallback(self):
out = xxe._placeRef("", "&e;")
self.assertIn("&e;", out)
class TestGuards(unittest.TestCase):
def test_echoed(self):
self.assertTrue(xxe._echoed("... luther", "u", baseline="Hello, luther!")
self.assertIsNotNone(payload)
def test_internal_echo_rejected(self):
# endpoint mirrors the raw body back (never parses) -> must NOT be a hit
xxe._send = lambda body: "You sent: %s" % body
payload, _ = xxe._tryInternal("luther", "u", baseline="You sent: luther")
self.assertIsNone(payload)
def test_internal_baseline_contains_sentinel_rejected(self):
xxe._send = lambda body: "Hello, %s!" % xxe.SENTINEL
payload, _ = xxe._tryInternal("luther", "u", baseline="already %s here" % xxe.SENTINEL)
self.assertIsNone(payload)
def test_error_based_positive(self):
xxe._send = lambda body: 'XML error: failed to load external entity "file:///%s/nonexistent"' % xxe.SENTINEL
payload, page = xxe._tryError("x", "u")
self.assertIsNotNone(payload)
self.assertIsNotNone(xxe._fingerprint(page))
def test_error_based_echo_rejected(self):
xxe._send = lambda body: "You sent: %s" % body # echoes DOCTYPE/ENTITY -> _echoed guard
payload, _ = xxe._tryError("x", "u")
self.assertIsNone(payload)
def test_error_exfil_extraction_base64(self):
import base64
from lib.core.convert import getText
secret = getText(base64.b64encode(b"root:x:0:0:root:/root:/bin/sh"))
def mock(body):
m = re.search(r'file:///(\w+)/%file;', body) or re.search(r'file:///(\w+)/%file;', body)
marker = m.group(1) if m else "zzz"
return 'failed to load "file:///%s/%s"' % (marker, secret)
xxe._send = mock
conf.fileRead = "/etc/passwd"
try:
content, name = xxe._tryErrorExfil("x", "u")
finally:
conf.fileRead = None
self.assertEqual(name, "/etc/passwd")
self.assertIn("root:x:0:0", content or "")
class TestRealXmlPayloads(unittest.TestCase):
"""Prove crafted payloads are well-formed and actually expand the entity."""
@staticmethod
def _expand(payload):
try:
from lxml import etree
except ImportError:
raise unittest.SkipTest("lxml not available")
parser = etree.XMLParser(resolve_entities=True, load_dtd=True, no_network=True, huge_tree=False)
doc = etree.fromstring(payload.encode("utf-8"), parser)
return "".join(doc.itertext())
def test_internal_entity_expands(self):
xxe.SENTINEL = "realxmlsentinel"
ent = "abcd"
subset = '' % (ent, xxe.SENTINEL)
payload = xxe._placeRef(xxe._buildDoctype("luther", "u", subset), "&%s;" % ent)
self.assertIn(xxe.SENTINEL, self._expand(payload))
def test_internal_entity_expands_with_existing_doctype(self):
xxe.SENTINEL = "realxmlsentinel2"
ent = "efgh"
subset = '' % (ent, xxe.SENTINEL)
base = ']>luther'
payload = xxe._placeRef(xxe._buildDoctype(base, "u", subset), "&%s;" % ent)
self.assertIn(xxe.SENTINEL, self._expand(payload))
def test_attribute_entity_expands(self):
xxe.SENTINEL = "attrsentinel"
ent = "ijkl"
subset = '' % (ent, xxe.SENTINEL)
payload = xxe._placeRef(xxe._buildDoctype('x', "u", subset), "&%s;" % ent, attrs=True)
self.assertIn(xxe.SENTINEL, self._expand(payload))
if __name__ == "__main__":
unittest.main()