Prevent XXE in epub/fb2/goodreads API
The lxml.etree.fromstring() function use the default XML parser, which resolves external entities because XML handling defaults in Python sucks. There is no need for such dangerous misfeatures in calibre-web, so let's disable it. A user able to upload epub/fb2 could add something like this to the file: ```xml <?xml version="1.0"?> <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> <container><rootfiles><rootfile full-path="&xxe;"/></rootfiles></container> ``` and obtain the content of the `/etc/passwd` file, which is bad™.
This commit is contained in:
@@ -175,7 +175,7 @@ def parse_epub_cover(ns, tree, epub_zip, cover_path, tmp_file_path):
|
||||
for cs in cover_section:
|
||||
if cs.endswith('.xhtml') or cs.endswith('.html'):
|
||||
markup = epub_zip.read(os.path.join(cover_path, cs))
|
||||
markup_tree = etree.fromstring(markup)
|
||||
markup_tree = etree.fromstring(markup, parser=etree.XMLParser(resolve_entities=False, no_network=True))
|
||||
# no matter xhtml or html with no namespace
|
||||
img_src = markup_tree.xpath("//*[local-name() = 'img']/@src")
|
||||
# Alternative image source
|
||||
|
||||
@@ -53,16 +53,20 @@ def updateEpub(src, dest, filename, data, ):
|
||||
zf.writestr(filename, data)
|
||||
|
||||
|
||||
# Safe parser: disable entity resolution and network access to prevent XXE attacks
|
||||
_safe_parser = etree.XMLParser(resolve_entities=False, no_network=True)
|
||||
|
||||
|
||||
def get_content_opf(file_path, ns=None):
|
||||
if ns is None:
|
||||
ns = default_ns
|
||||
epubZip = zipfile.ZipFile(file_path)
|
||||
txt = epubZip.read('META-INF/container.xml')
|
||||
tree = etree.fromstring(txt)
|
||||
tree = etree.fromstring(txt, parser=_safe_parser)
|
||||
cf_name = tree.xpath('n:rootfiles/n:rootfile/@full-path', namespaces=ns)[0]
|
||||
cf = epubZip.read(cf_name)
|
||||
|
||||
return etree.fromstring(cf), cf_name
|
||||
return etree.fromstring(cf, parser=_safe_parser), cf_name
|
||||
|
||||
|
||||
def create_new_metadata_backup(book, custom_columns, export_language, translated_cover_name, lang_type=3):
|
||||
|
||||
@@ -20,6 +20,9 @@ from lxml import etree
|
||||
|
||||
from .constants import BookMeta
|
||||
|
||||
# Safe parser: disable entity resolution and network access to prevent XXE attacks
|
||||
_safe_parser = etree.XMLParser(resolve_entities=False, no_network=True)
|
||||
|
||||
|
||||
def get_fb2_info(tmp_file_path, original_file_extension):
|
||||
|
||||
@@ -29,7 +32,7 @@ def get_fb2_info(tmp_file_path, original_file_extension):
|
||||
}
|
||||
|
||||
fb2_file = open(tmp_file_path, encoding="utf-8")
|
||||
tree = etree.fromstring(fb2_file.read().encode())
|
||||
tree = etree.fromstring(fb2_file.read().encode(), parser=_safe_parser)
|
||||
|
||||
authors = tree.xpath('/fb:FictionBook/fb:description/fb:title-info/fb:author', namespaces=ns)
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ class my_GoodreadsRequest(GoodreadsRequest):
|
||||
if resp.status_code != 200:
|
||||
raise GoodreadsRequestException(resp.reason, self.path)
|
||||
if self.req_format == 'xml':
|
||||
root = etree.fromstring(resp.content)
|
||||
root = etree.fromstring(resp.content, parser=etree.XMLParser(resolve_entities=False, no_network=True))
|
||||
data_dict = etree_to_dict(root)
|
||||
|
||||
return data_dict['GoodreadsResponse']
|
||||
|
||||
Reference in New Issue
Block a user