mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-22 03:08:23 +00:00
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>
This commit is contained in:
@@ -30,6 +30,7 @@ All notable changes to the **Prowler API** are documented in this file.
|
||||
### 🔐 Security
|
||||
|
||||
- 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)
|
||||
- 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)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
@@ -9,6 +8,8 @@ from allauth.socialaccount.models import SocialApp
|
||||
from config.custom_logging import BackendLogger
|
||||
from config.settings.social_login import SOCIALACCOUNT_PROVIDERS
|
||||
from cryptography.fernet import Fernet, InvalidToken
|
||||
import defusedxml
|
||||
from defusedxml import ElementTree as ET
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractBaseUser
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
@@ -2067,6 +2068,8 @@ class SAMLConfiguration(RowLevelSecurityProtectedModel):
|
||||
root = ET.fromstring(self.metadata_xml)
|
||||
except ET.ParseError as e:
|
||||
raise ValidationError({"metadata_xml": f"Invalid XML: {e}"})
|
||||
except defusedxml.DefusedXmlException as e:
|
||||
raise ValidationError({"metadata_xml": f"Unsafe XML content rejected: {e}"})
|
||||
|
||||
# Entity ID
|
||||
entity_id = root.attrib.get("entityID")
|
||||
|
||||
@@ -243,6 +243,39 @@ class TestSAMLConfigurationModel:
|
||||
assert "Invalid XML" in errors["metadata_xml"][0]
|
||||
assert "not well-formed" in errors["metadata_xml"][0]
|
||||
|
||||
def test_xml_bomb_rejected(self, tenants_fixture):
|
||||
"""
|
||||
Regression test: a 'billion laughs' XML bomb in the SAML metadata field
|
||||
must be rejected and not allowed to exhaust server memory / CPU.
|
||||
|
||||
Before the fix, xml.etree.ElementTree was used directly, which does not
|
||||
protect against entity-expansion attacks. The fix switches to defusedxml
|
||||
which raises an exception for any XML containing entity definitions.
|
||||
"""
|
||||
tenant = tenants_fixture[0]
|
||||
xml_bomb = (
|
||||
"<?xml version='1.0'?>"
|
||||
"<!DOCTYPE bomb ["
|
||||
" <!ENTITY a 'aaaaaaaaaa'>"
|
||||
" <!ENTITY b '&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;'>"
|
||||
" <!ENTITY c '&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;'>"
|
||||
" <!ENTITY d '&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;'>"
|
||||
"]>"
|
||||
"<md:EntityDescriptor entityID='&d;' "
|
||||
"xmlns:md='urn:oasis:names:tc:SAML:2.0:metadata'/>"
|
||||
)
|
||||
config = SAMLConfiguration(
|
||||
email_domain="xmlbomb.com",
|
||||
metadata_xml=xml_bomb,
|
||||
tenant=tenant,
|
||||
)
|
||||
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
config._parse_metadata()
|
||||
|
||||
errors = exc_info.value.message_dict
|
||||
assert "metadata_xml" in errors
|
||||
|
||||
def test_metadata_missing_sso_fails(self, tenants_fixture):
|
||||
tenant = tenants_fixture[0]
|
||||
xml = """<md:EntityDescriptor entityID="x" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
|
||||
|
||||
Reference in New Issue
Block a user