Xml External Entities in Flask with Basic Auth
Xml External Entities in Flask with Basic Auth — how this specific combination creates or exposes the vulnerability
An XML External Entity (XXE) injection occurs when an application processes XML input and allows an attacker to define external entities that can read local files, trigger SSRF, or cause denial of service. In Flask, this typically arises when you use XML-parsing libraries such as lxml or xml.etree on user-supplied data without disabling external entity resolution. When Basic Auth is used, the client sends an Authorization header (e.g., Authorization: Basic base64(username:password)) with the request. The combination of accepting XML input and relying on Basic Auth for access control can create a misleading sense of protection: the presence of credentials does not change how the XML parser behaves, and an authenticated or unauthenticated attacker can still supply malicious XML if the endpoint is reachable.
Consider a Flask endpoint that accepts XML to configure or enrich a request. If the endpoint parses XML without disabling external entities, an attacker can provide an XML payload that references a file:// URL to read /etc/passwd, or an http:// URL to an internal SSRF target. Basic Auth might limit who can reach the endpoint in your design, but if the endpoint is exposed (for example, due to misconfigured routing or a proxy), the XXE vector remains. Moreover, in an unauthenticated scan, middleBrick tests the unauthenticated attack surface; if Basic Auth is expected but not enforced at the parser boundary, credentials may be omitted, and the parser will still process external entities. The risk is compounded when XML is used to construct requests to internal services, enabling SSRF via external entities, while the expectation that Basic Auth protects the endpoint may delay detection.
Real-world examples include parsing uploaded configuration files or SOAP messages. An attacker might send:
<?xml version="1.0"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" >]>> <root>&xxe;</root>
If the parser resolves &xxe;, the file contents may be returned or forwarded. MiddleBrick’s checks for Data Exposure and Input Validation highlight whether XML parsers are resolving external entities, and findings may map to OWASP API Top 10 A05 (2023) and related compliance frameworks.
Basic Auth-Specific Remediation in Flask — concrete code fixes
To mitigate XXE in Flask when using Basic Auth, focus on disabling external entity resolution in your XML parser and ensuring credentials are validated before processing sensitive operations. Below are concrete, safe patterns.
1. Secure XML parsing with defusedxml
Use defusedxml to prevent entity expansion and external file access. This is the recommended approach because it removes dangerous features at the parser level.
from defusedxml.ElementTree import fromstring
@app.route('/process', methods=['POST'])
def process_xml():
xml_data = request.get_data()
try:
tree = fromstring(xml_data) # safe: no external entities
# process tree safely
return {'status': 'ok'}, 200
except Exception as e:
return {'error': str(e)}, 400
2. Disable entities explicitly with lxml (if you must use lxml)
If you rely on lxml, configure the parser to disable DTDs and external entities.
from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_load=False, load_dtd=False)
@app.route('/process-lxml', methods=['POST'])
def process_lxml():
xml_data = request.get_data()
try:
tree = etree.fromstring(xml_data, parser=parser)
# process tree safely
return {'status': 'ok'}, 200
except Exception as e:
return {'error': str(e)}, 400
3. Enforce Basic Auth before parsing, using Flask-HTTPAuth
Ensure credentials are verified and tied to the request before any XML processing. This does not prevent XXE on its own, but ensures that only authorized users can invoke the parser.
from flask import Flask, request, jsonify
from flask_httpauth import HTTPBasicAuth
app = Flask(__name__)
auth = HTTPBasicAuth()
USERS = {
"admin": "secret",
}
@auth.verify_password
def verify_password(username, password):
if username in USERS and USERS[username] == password:
return username
@app.route('/secure-process', methods=['POST'])
@auth.login_required
def secure_process():
xml_data = request.get_data()
# Use a safe parser here, e.g., defusedxml
try:
# Example with defusedxml
from defusedxml.ElementTree import fromstring
tree = fromstring(xml_data)
# process tree
return jsonify({'user': auth.current_user(), 'status': 'ok'}), 200
except Exception as e:
return jsonify({'error': str(e)}), 400
4. Validate and restrict input size
Limit XML size and reject DOCTYPE declarations to reduce attack surface.
@app.route('/validate-process', methods=['POST'])
@auth.login_required
def validate_process():
# Limit payload to 128 KiB
if len(request.data) > 128 * 1024:
return jsonify({'error': 'payload too large'}), 413
# Reject inline DOCTYPE
if b'
These steps ensure that authentication does not create a false sense of security and that XML parsing is hardened against XXE. MiddleBrick scans can verify that external entities are not resolved and that Basic Auth is properly enforced before sensitive operations.