Skip to content

Commit 0b7a21a

Browse files
sandiyochristanclaudeAdriiiPRodri
authored
fix(api): [security] use defusedxml to prevent XML bomb DoS in SAML metadata parsing (#10165)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Adrián Peña <adrianjpr@gmail.com>
1 parent 872e6e2 commit 0b7a21a

File tree

3 files changed

+38
-1
lines changed

3 files changed

+38
-1
lines changed

api/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ All notable changes to the **Prowler API** are documented in this file.
3030
### 🔐 Security
3131

3232
- Use `psycopg2.sql` to safely compose DDL in `PostgresEnumMigration`, preventing SQL injection via f-string interpolation [(#10166)](https://github.com/prowler-cloud/prowler/pull/10166)
33+
- Replace stdlib XML parser with `defusedxml` in SAML metadata parsing to prevent XML bomb (billion laughs) DoS attacks [(#10165)](https://github.com/prowler-cloud/prowler/pull/10165)
3334

3435
---
3536

api/src/backend/api/models.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import json
22
import logging
33
import re
4-
import xml.etree.ElementTree as ET
54
from datetime import datetime, timedelta, timezone
65
from uuid import UUID, uuid4
76

87
from allauth.socialaccount.models import SocialApp
98
from config.custom_logging import BackendLogger
109
from config.settings.social_login import SOCIALACCOUNT_PROVIDERS
1110
from cryptography.fernet import Fernet, InvalidToken
11+
import defusedxml
12+
from defusedxml import ElementTree as ET
1213
from django.conf import settings
1314
from django.contrib.auth.models import AbstractBaseUser
1415
from django.contrib.postgres.fields import ArrayField
@@ -2067,6 +2068,8 @@ def _parse_metadata(self):
20672068
root = ET.fromstring(self.metadata_xml)
20682069
except ET.ParseError as e:
20692070
raise ValidationError({"metadata_xml": f"Invalid XML: {e}"})
2071+
except defusedxml.DefusedXmlException as e:
2072+
raise ValidationError({"metadata_xml": f"Unsafe XML content rejected: {e}"})
20702073

20712074
# Entity ID
20722075
entity_id = root.attrib.get("entityID")

api/src/backend/api/tests/test_models.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,39 @@ def test_invalid_metadata_xml_fails(self, tenants_fixture):
243243
assert "Invalid XML" in errors["metadata_xml"][0]
244244
assert "not well-formed" in errors["metadata_xml"][0]
245245

246+
def test_xml_bomb_rejected(self, tenants_fixture):
247+
"""
248+
Regression test: a 'billion laughs' XML bomb in the SAML metadata field
249+
must be rejected and not allowed to exhaust server memory / CPU.
250+
251+
Before the fix, xml.etree.ElementTree was used directly, which does not
252+
protect against entity-expansion attacks. The fix switches to defusedxml
253+
which raises an exception for any XML containing entity definitions.
254+
"""
255+
tenant = tenants_fixture[0]
256+
xml_bomb = (
257+
"<?xml version='1.0'?>"
258+
"<!DOCTYPE bomb ["
259+
" <!ENTITY a 'aaaaaaaaaa'>"
260+
" <!ENTITY b '&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;'>"
261+
" <!ENTITY c '&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;'>"
262+
" <!ENTITY d '&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;'>"
263+
"]>"
264+
"<md:EntityDescriptor entityID='&d;' "
265+
"xmlns:md='urn:oasis:names:tc:SAML:2.0:metadata'/>"
266+
)
267+
config = SAMLConfiguration(
268+
email_domain="xmlbomb.com",
269+
metadata_xml=xml_bomb,
270+
tenant=tenant,
271+
)
272+
273+
with pytest.raises(ValidationError) as exc_info:
274+
config._parse_metadata()
275+
276+
errors = exc_info.value.message_dict
277+
assert "metadata_xml" in errors
278+
246279
def test_metadata_missing_sso_fails(self, tenants_fixture):
247280
tenant = tenants_fixture[0]
248281
xml = """<md:EntityDescriptor entityID="x" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">

0 commit comments

Comments
 (0)