|
8 | 8 | from django.utils.xmlutils import SimplerXMLGenerator
|
9 | 9 | from django.utils.encoding import smart_unicode
|
10 | 10 | from xml.dom import pulldom
|
| 11 | +from xml.sax import handler |
| 12 | +from xml.sax.expatreader import ExpatParser as _ExpatParser |
11 | 13 |
|
12 | 14 | class Serializer(base.Serializer):
|
13 | 15 | """
|
@@ -149,9 +151,13 @@ class Deserializer(base.Deserializer):
|
149 | 151 |
|
150 | 152 | def __init__(self, stream_or_string, **options):
|
151 | 153 | super(Deserializer, self).__init__(stream_or_string, **options)
|
152 |
| - self.event_stream = pulldom.parse(self.stream) |
| 154 | + self.event_stream = pulldom.parse(self.stream, self._make_parser()) |
153 | 155 | self.db = options.pop('using', DEFAULT_DB_ALIAS)
|
154 | 156 |
|
| 157 | + def _make_parser(self): |
| 158 | + """Create a hardened XML parser (no custom/external entities).""" |
| 159 | + return DefusedExpatParser() |
| 160 | + |
155 | 161 | def next(self):
|
156 | 162 | for event, node in self.event_stream:
|
157 | 163 | if event == "START_ELEMENT" and node.nodeName == "object":
|
@@ -290,3 +296,90 @@ def getInnerText(node):
|
290 | 296 | else:
|
291 | 297 | pass
|
292 | 298 | return u"".join(inner_text)
|
| 299 | + |
| 300 | + |
| 301 | +# Below code based on Christian Heimes' defusedxml |
| 302 | + |
| 303 | + |
| 304 | +class DefusedExpatParser(_ExpatParser): |
| 305 | + """ |
| 306 | + An expat parser hardened against XML bomb attacks. |
| 307 | +
|
| 308 | + Forbids DTDs, external entity references |
| 309 | +
|
| 310 | + """ |
| 311 | + def __init__(self, *args, **kwargs): |
| 312 | + _ExpatParser.__init__(self, *args, **kwargs) |
| 313 | + self.setFeature(handler.feature_external_ges, False) |
| 314 | + self.setFeature(handler.feature_external_pes, False) |
| 315 | + |
| 316 | + def start_doctype_decl(self, name, sysid, pubid, has_internal_subset): |
| 317 | + raise DTDForbidden(name, sysid, pubid) |
| 318 | + |
| 319 | + def entity_decl(self, name, is_parameter_entity, value, base, |
| 320 | + sysid, pubid, notation_name): |
| 321 | + raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name) |
| 322 | + |
| 323 | + def unparsed_entity_decl(self, name, base, sysid, pubid, notation_name): |
| 324 | + # expat 1.2 |
| 325 | + raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) |
| 326 | + |
| 327 | + def external_entity_ref_handler(self, context, base, sysid, pubid): |
| 328 | + raise ExternalReferenceForbidden(context, base, sysid, pubid) |
| 329 | + |
| 330 | + def reset(self): |
| 331 | + _ExpatParser.reset(self) |
| 332 | + parser = self._parser |
| 333 | + parser.StartDoctypeDeclHandler = self.start_doctype_decl |
| 334 | + parser.EntityDeclHandler = self.entity_decl |
| 335 | + parser.UnparsedEntityDeclHandler = self.unparsed_entity_decl |
| 336 | + parser.ExternalEntityRefHandler = self.external_entity_ref_handler |
| 337 | + |
| 338 | + |
| 339 | +class DefusedXmlException(ValueError): |
| 340 | + """Base exception.""" |
| 341 | + def __repr__(self): |
| 342 | + return str(self) |
| 343 | + |
| 344 | + |
| 345 | +class DTDForbidden(DefusedXmlException): |
| 346 | + """Document type definition is forbidden.""" |
| 347 | + def __init__(self, name, sysid, pubid): |
| 348 | + super(DTDForbidden, self).__init__() |
| 349 | + self.name = name |
| 350 | + self.sysid = sysid |
| 351 | + self.pubid = pubid |
| 352 | + |
| 353 | + def __str__(self): |
| 354 | + tpl = "DTDForbidden(name='{}', system_id={!r}, public_id={!r})" |
| 355 | + return tpl.format(self.name, self.sysid, self.pubid) |
| 356 | + |
| 357 | + |
| 358 | +class EntitiesForbidden(DefusedXmlException): |
| 359 | + """Entity definition is forbidden.""" |
| 360 | + def __init__(self, name, value, base, sysid, pubid, notation_name): |
| 361 | + super(EntitiesForbidden, self).__init__() |
| 362 | + self.name = name |
| 363 | + self.value = value |
| 364 | + self.base = base |
| 365 | + self.sysid = sysid |
| 366 | + self.pubid = pubid |
| 367 | + self.notation_name = notation_name |
| 368 | + |
| 369 | + def __str__(self): |
| 370 | + tpl = "EntitiesForbidden(name='{}', system_id={!r}, public_id={!r})" |
| 371 | + return tpl.format(self.name, self.sysid, self.pubid) |
| 372 | + |
| 373 | + |
| 374 | +class ExternalReferenceForbidden(DefusedXmlException): |
| 375 | + """Resolving an external reference is forbidden.""" |
| 376 | + def __init__(self, context, base, sysid, pubid): |
| 377 | + super(ExternalReferenceForbidden, self).__init__() |
| 378 | + self.context = context |
| 379 | + self.base = base |
| 380 | + self.sysid = sysid |
| 381 | + self.pubid = pubid |
| 382 | + |
| 383 | + def __str__(self): |
| 384 | + tpl = "ExternalReferenceForbidden(system_id='{}', public_id={})" |
| 385 | + return tpl.format(self.sysid, self.pubid) |
0 commit comments