diff --git a/scrapy/selector/unified.py b/scrapy/selector/unified.py
index babb65070..60598a24d 100644
--- a/scrapy/selector/unified.py
+++ b/scrapy/selector/unified.py
@@ -15,11 +15,16 @@ from .csstranslator import ScrapyHTMLTranslator, ScrapyGenericTranslator
__all__ = ['Selector', 'SelectorList']
+
+class SafeXMLParser(etree.XMLParser):
+ def __init__(self, *args, **kwargs):
+ super(SafeXMLParser, self).__init__(*args, resolve_entities=False, **kwargs)
+
_ctgroup = {
'html': {'_parser': etree.HTMLParser,
'_csstranslator': ScrapyHTMLTranslator(),
'_tostring_method': 'html'},
- 'xml': {'_parser': etree.XMLParser,
+ 'xml': {'_parser': SafeXMLParser,
'_csstranslator': ScrapyGenericTranslator(),
'_tostring_method': 'xml'},
}
diff --git a/scrapy/tests/test_selector.py b/scrapy/tests/test_selector.py
index 489a163a0..95a44be27 100644
--- a/scrapy/tests/test_selector.py
+++ b/scrapy/tests/test_selector.py
@@ -332,6 +332,16 @@ class SelectorTestCase(unittest.TestCase):
div_class = x.xpath('//div/@class')
self.assertTrue(all(map(lambda e: hasattr(e._root, 'getparent'), div_class)))
+ def test_xml_entity_expansion(self):
+ malicious_xml = ''\
+ ' ]>&xxe;'
+
+ response = XmlResponse('http://example.com', body=malicious_xml)
+ sel = self.sscls(response=response)
+
+ self.assertEqual(sel.extract(), '&xxe;')
+
class DeprecatedXpathSelectorTest(unittest.TestCase):