Xml External Entities in Django with Mutual Tls
Xml External Entities in Django with Mutual Tls — how this specific combination creates or exposes the vulnerability
XML External Entity (XXE) injection occurs when an XML parser processes external entity references in untrusted XML data. In Django, this typically arises when the application parses XML payloads using libraries such as defusedxml alternatives or lower-level XML parsers without proper safeguards. When Mutual TLS (mTLS) is enforced, the server validates client certificates before reaching application code, which can create a false sense of transport-layer security. Because mTLS ensures the identity of the client, developers may assume the incoming XML is from a trusted source and skip input validation, deserialization hardening, or schema restrictions. This combination exposes a critical gap: transport authentication does not protect against malicious XML content. An authenticated client with a valid certificate can still supply an XML payload that defines external entities, leading to local file reads, SSRF, or denial of service. In Django views that use lxml or xml.etree with entity resolution enabled, the parser resolves DTDs and external references, potentially reading files like /etc/passwd or triggering requests to internal endpoints behind the mTLS perimeter. The risk is amplified when XXE detection is absent because the scanner’s unauthenticated checks do not present a client certificate, so the endpoint might appear to reject unauthenticated probes while remaining vulnerable to authenticated mTLS clients submitting malicious XML.
Mutual Tls-Specific Remediation in Django — concrete code fixes
Remediation focuses on disabling external entity processing and enforcing strict XML schema validation regardless of mTLS status. Even when mTLS is in place, treat XML input as untrusted. Use defusedxml or configure your XML parser to prohibit external entities and DTDs entirely. Below are concrete Django examples with mTLS-aware patterns.
1. Secure XML parsing with defusedxml
Install and use defusedxml, which provides safe replacements that block external entities:
pip install defusedxml
In your Django view:
import defusedxml.lxml as safe_lxml
from django.http import JsonResponse
def parse_xml_view(request):
xml_data = request.body
try:
root = safe_lxml.fromstring(xml_data)
# Process elements safely; external entities are blocked
name = root.findtext('name')
return JsonResponse({'name': name})
except Exception as e:
return JsonResponse({'error': str(e)}, status=400)
2. Disable DTD and entity resolution with lxml
If you use lxml directly, disable DTD loading and entity resolution:
from lxml import etree
import ssl
# mTLS context with client cert validation (server-side)
ssl_context = ssl.create_default_context(cafile='/path/to/ca.pem')
ssl_context.load_cert_chain(certfile='/path/to/server-cert.pem', keyfile='/path/to/server-key.pem')
def parse_with_lxml_secure(xml_bytes):
parser = etree.XMLParser(
no_network=True,
resolve_entities=False,
load_dtd=False,
attribute_defaults=False,
strip_cdata=False,
ns_clean=False,
recover=False,
schema=None
)
try:
tree = etree.fromstring(xml_bytes, parser=parser)
# Safe processing
return tree.xpath('//title/text()')
except etree.XMLSyntaxError as err:
raise ValueError(f'Invalid XML: {err}')
3. Use Django REST Framework with strict parsers
For API endpoints, configure DRF to avoid XML parsing when possible. If XML is required, add a custom parser that uses secure settings:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from defusedxml.minidom import parseString as safe_parse_string
class SecureXMLView(APIView):
def post(self, request):
try:
doc = safe_parse_string(request.body)
# Limit traversal and avoid external entities
title = doc.getElementsByTagName('title')
text = title[0].firstChild.nodeValue if title else ''
return Response({'title': text})
except Exception:
return Response({'error': 'Invalid or unsafe XML'}, status=status.HTTP_400_BAD_REQUEST)
4. mTLS enforcement in Django using middleware
Ensure mTLS is enforced before XML processing. Use a middleware that validates client certificates and sets request attributes safely:
import ssl
from django.http import HttpResponseForbidden
class MutualTlsMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.ssl_context = ssl.create_default_context(cafile='/path/to/ca.pem')
def __call__(self, request):
cert = request.META.get('SSL_CLIENT_CERT')
if not cert:
return HttpResponseForbidden('Client certificate required')
# Perform additional validation as needed (e.g., verify against allowed CNs)
request.client_cert = cert
response = self.get_response(request)
return response
5. Schema-based validation with XML Schema (XSD)
Validate against a strict XSD to ensure only expected elements and attributes are present:
from lxml import etree
schema_doc = etree.parse('/path/to/schema.xsd')
schema = etree.XMLSchema(schema_doc)
parser = etree.XMLParser(schema=schema, no_network=True, resolve_entities=False)
def validate_with_xsd(xml_bytes):
try:
etree.fromstring(xml_bytes, parser=parser)
return True
except etree.DocumentInvalid as err:
raise ValueError(f'Schema validation failed: {err}')
Combine these practices: enforce mTLS at the infrastructure or middleware layer, then apply secure parsing and schema validation in Django. This ensures that even authenticated clients cannot exploit XXE, and findings from middleBrick scans correctly highlight remaining issues like improper entity handling or missing input constraints.