Authentication Bypass durch manipuliertes XML — mit einem gültigen Login als user1 einloggen und dabei als admin ankommen.
Nach dem Login schickt der IdP eine SAMLResponse an den SP — Base64-kodiertes XML, das die Identität des Users enthält und digital signiert ist. Das ist Schritt 5 im Flow, den wir in Burp sehen.
Dekodiert sieht es so aus — das ist eine echte Response aus der Demo-Umgebung (vereinfacht):
<samlp:Response ID="_375ef4cb992..." IssueInstant="2025-03-04T16:05:30Z" Destination="http://localhost:4300/login/callback"> <saml:Issuer>http://localhost:8080/simplesaml/saml2/idp/metadata.php</saml:Issuer> <ds:Signature> <ds:SignedInfo> <ds:CanonicalizationMethod Algorithm="...xml-exc-c14n#"/> <ds:SignatureMethod Algorithm="...rsa-sha1"/> <ds:Reference URI="#_375ef4cb992..."> <ds:DigestMethod Algorithm="...sha1"/> <ds:DigestValue>jMHaXYxasIl3oc5Q+VW0UYUCY6s=</ds:DigestValue> </ds:Reference> </ds:SignedInfo> <ds:SignatureValue>XKp7z2Q...longbase64...==</ds:SignatureValue> </ds:Signature> <saml:Assertion> <saml:Subject> <saml:NameID>user1@example.com</saml:NameID> </saml:Subject> <saml:AttributeStatement> <saml:Attribute Name="uid"> <saml:AttributeValue>1</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="email"> <saml:AttributeValue>user1@example.com</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="eduPersonAffiliation"> <saml:AttributeValue>group1</saml:AttributeValue> </saml:Attribute> </saml:AttributeStatement> </saml:Assertion> </samlp:Response>
Die zwei gelb markierten Elemente sind die Angriffsziele: DigestValue und NameID. Die Signatur schützt beide — theoretisch.
Der SP vertraut der Assertion nur weil eine kryptographische Kette sie absichert. So soll es aussehen:
Jedes Glied sichert das nächste. Der DigestValue ist der Hash der Assertion. Ändert man die Assertion, stimmt der Hash nicht mehr — und die Signatur schlägt fehl.
SAMLStorm bricht diese Kette — nicht durch Kryptographie, sondern weil xml-crypto und node-saml unterschiedliche Teile desselben Dokuments lesen.
xml-crypto liest den DigestValue als Text-Node. Wenn ein XML-Kommentar im Element steckt, liest es nur den Text nach dem Kommentar — den echten Hash. Aber die Assertion wurde bereits geändert.
Was xml-crypto sieht: jMHaXYxasIl3oc5Q+VW0UYUCY6s= — der echte Hash. Prüfung bestanden.
Was node-saml aus der Assertion liest: admin als NameID.
serialize user {
nameID: 'admin', ← eingeloggt als user1, angekommen als admin
uid: 'admin',
email: 'user1@example.com', ← IdP-Attribute bleiben von user1
eduPersonAffiliation: 'group1',
}xml-crypto und node-saml verarbeiten das XML unabhängig voneinander. Wenn zwei SignedInfo-Nodes existieren, schaut jeder auf den anderen.
<ds:Signature> <FakeWrapper> <!-- injiziert vom Angreifer --> <ds:SignedInfo> <!-- xml-crypto validiert DIESEN --> <ds:Reference URI="#fake"> <ds:DigestValue> 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= <!-- SHA256 von leerem String — vorhersagbar, immer gleich --> </ds:DigestValue> </ds:Reference> </ds:SignedInfo> </FakeWrapper> <ds:SignedInfo> <!-- node-saml liest DIESEN --> <ds:Reference URI="#realAssertion"> <ds:DigestValue>echter Hash...</ds:DigestValue> </ds:Reference> </ds:SignedInfo> </ds:Signature> <saml:Assertion> <saml:NameID>admin</saml:NameID> <!-- geändert --> </saml:Assertion>
SignedInfo (fake) und validiert ihn — DigestValue ist SHA256(""), was immer stimmt. node-saml liest den zweiten SignedInfo (echt) und extrahiert die Assertion daraus — die nun admin als NameID hat. Zwei Parser, zwei Perspektiven auf dasselbe Dokument.xml-crypto 6.0.1 wurde am gleichen Tag wie die CVE-Veröffentlichung released — März 2025.
| Check | xml-crypto 6.0.0 — verwundbar | xml-crypto 6.0.1 — gepatcht |
|---|---|---|
| Kommentar in DigestValue | ✗ Wird ignoriert, Hash passt | ✓ Sofort rejected |
| Mehrere SignedInfo | ✗ Beide akzeptiert | ✓ Mehr als einer → Fehler |
| Assertion-Quelle | ✗ Aus Original-XML gelesen | ✓ Aus verifiziertem Inhalt |
| HTTP Status bei Angriff | 302 → Login erfolgreich | 500 → Authentication failed |
npm audit hätte gereicht. CVE und Patch kamen gleichzeitig. Wer seinen Dependency-Stack nicht aktuell hält, verliert die Kontrolle über seine Auth-Infrastruktur — durch eine einzige veraltete npm-Version.PortSwigger Research hat die gleiche Angriffsklasse auf Ruby/PHP ausgedehnt. Die gemeinsame Wurzel ist dieselbe.
| SAMLStorm (unser Stack) | The Fragile Lock | |
|---|---|---|
| Betroffen | xml-crypto / node-saml | ruby-saml / php-saml |
| Technik | Comment Injection, doppelter SignedInfo | Void Canonicalization, Attribute Pollution |
| Voraussetzung | Gültiger Login nötig | Nur öffentliche IdP-Metadaten |
| Gepatcht | März 2025 | Dez. 2025 (CVE-2025-66568) |
| Real-World Impact | WorkOS, diverse SaaS-Plattformen | GitLab EE 17.8.4 (live Demo) |