mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
try to decode some malicious xml on startup; if this succeeds, then force-disable all xml-based features (primarily WebDAV) this is paranoid future-proofing against unanticipated changes in future versions of python, specifically if the importlib or xml.etree.ET behavior changes in a way that somehow reenables entity expansion, which (still hypothetically) would probably be caused by failing to unload the `_elementtree` c-module no past or present python versions are affected by this change
118 lines
2.7 KiB
Python
118 lines
2.7 KiB
Python
# coding: utf-8
|
|
from __future__ import print_function, unicode_literals
|
|
|
|
import importlib
|
|
import sys
|
|
import xml.etree.ElementTree as ET
|
|
|
|
from .__init__ import PY2
|
|
|
|
if True: # pylint: disable=using-constant-test
|
|
from typing import Any, Optional
|
|
|
|
|
|
class BadXML(Exception):
|
|
pass
|
|
|
|
|
|
def get_ET() -> ET.XMLParser:
|
|
pn = "xml.etree.ElementTree"
|
|
cn = "_elementtree"
|
|
|
|
cmod = sys.modules.pop(cn, None)
|
|
if not cmod:
|
|
return ET.XMLParser # type: ignore
|
|
|
|
pmod = sys.modules.pop(pn)
|
|
sys.modules[cn] = None # type: ignore
|
|
|
|
ret = importlib.import_module(pn)
|
|
for name, mod in ((pn, pmod), (cn, cmod)):
|
|
if mod:
|
|
sys.modules[name] = mod
|
|
else:
|
|
sys.modules.pop(name, None)
|
|
|
|
sys.modules["xml.etree"].ElementTree = pmod # type: ignore
|
|
ret.ParseError = ET.ParseError # type: ignore
|
|
return ret.XMLParser # type: ignore
|
|
|
|
|
|
XMLParser: ET.XMLParser = get_ET()
|
|
|
|
|
|
class _DXMLParser(XMLParser): # type: ignore
|
|
def __init__(self) -> None:
|
|
tb = ET.TreeBuilder()
|
|
super(DXMLParser, self).__init__(target=tb)
|
|
|
|
p = self._parser if PY2 else self.parser
|
|
p.StartDoctypeDeclHandler = self.nope
|
|
p.EntityDeclHandler = self.nope
|
|
p.UnparsedEntityDeclHandler = self.nope
|
|
p.ExternalEntityRefHandler = self.nope
|
|
|
|
def nope(self, *a: Any, **ka: Any) -> None:
|
|
raise BadXML("{}, {}".format(a, ka))
|
|
|
|
|
|
class _NG(XMLParser): # type: ignore
|
|
def __int__(self) -> None:
|
|
raise BadXML("dxml selftest failed")
|
|
|
|
|
|
DXMLParser = _DXMLParser
|
|
|
|
|
|
def parse_xml(txt: str) -> ET.Element:
|
|
parser = DXMLParser()
|
|
parser.feed(txt)
|
|
return parser.close() # type: ignore
|
|
|
|
|
|
def selftest() -> bool:
|
|
qbe = r"""<!DOCTYPE d [
|
|
<!ENTITY a "nice_bakuretsu">
|
|
]>
|
|
<root>&a;&a;&a;</root>"""
|
|
|
|
emb = r"""<!DOCTYPE d [
|
|
<!ENTITY a SYSTEM "file:///etc/hostname">
|
|
]>
|
|
<root>&a;</root>"""
|
|
|
|
# future-proofing; there's never been any known vulns
|
|
# regarding DTDs and ET.XMLParser, but might as well
|
|
# block them since webdav-clients don't use them
|
|
dtd = r"""<!DOCTYPE d SYSTEM "a.dtd">
|
|
<root>a</root>"""
|
|
|
|
for txt in (qbe, emb, dtd):
|
|
try:
|
|
parse_xml(txt)
|
|
t = "WARNING: dxml selftest failed:\n%s\n"
|
|
print(t % (txt,), file=sys.stderr)
|
|
return False
|
|
except BadXML:
|
|
pass
|
|
|
|
return True
|
|
|
|
|
|
DXML_OK = selftest()
|
|
if not DXML_OK:
|
|
DXMLParser = _NG
|
|
|
|
|
|
def mktnod(name: str, text: str) -> ET.Element:
|
|
el = ET.Element(name)
|
|
el.text = text
|
|
return el
|
|
|
|
|
|
def mkenod(name: str, sub_el: Optional[ET.Element] = None) -> ET.Element:
|
|
el = ET.Element(name)
|
|
if sub_el is not None:
|
|
el.append(sub_el)
|
|
return el
|