Ein Identitätsanbieter sagte uns, wer Sie sind, und wir glaubten es
Single Sign-on hat eine stille Prämisse: Wenn ein Nutzer von einem Identitätsanbieter kommt, hat dieser Anbieter bereits für ihn gebürgt, also kann man das Passwort überspringen und ihn einfach einlassen. Der ganze Sinn besteht darin, dem IdP zu vertrauen. Der Fehler, den wir gleich beschreiben, lebte in der Lücke zwischen „dem IdP vertrauen, dass er seine eigenen Nutzer authentifiziert" und „allem vertrauen, was der IdP einem darüber erzählt, wer seine Nutzer sind". Das sind nicht dieselben Sätze, und der Unterschied war eine Kontoübernahme.
Hier ist der Aufbau. Ein Mandant auf unserer Plattform kann föderierte Verbindungen konfigurieren: SAML zu seinem Unternehmens-IdP, OIDC zu einem anderen. Ein Nutzer meldet sich über eine dieser Verbindungen an, der IdP sendet eine Assertion zurück, und unser Server muss eine Frage beantworten: Welches lokale Konto ist das? Bringt man diese Zuordnung durcheinander, hat man entweder einen Fremden, der aus seinem eigenen Konto ausgesperrt ist, oder – weitaus schlimmer – einen Fremden, der in das Konto einer anderen Person spaziert.
Der Verknüpfungsschlüssel war eine E-Mail, und eine E-Mail ist nur eine Behauptung
Unser Code beantwortete „welches Konto ist das" auf die naheliegende Weise. Die Assertion trug eine E-Mail, also suchten wir den Nutzer danach: FindByEmailAsync(assertedEmail). Existierte ein passendes Konto, wurde der wiederkehrende föderierte Nutzer darauf aufgelöst und angemeldet. Sauber, einfach und genau so, wie viele SSO-Integrationen geschrieben werden.
Das Problem ist, was diese E-Mail tatsächlich ist. Sie ist keine Tatsache, die der IdP bewiesen hat. Sie ist eine Zeichenkette in einer Assertion, und die Assertion ist nur so vertrauenswürdig wie die Verbindung, über die sie kam. In einer mandantenfähigen Welt kontrolliert man nicht jede Verbindung. Jeder, der eine aufsetzen kann (sein eigener SAML-IdP, sein eigener OIDC-Anbieter), kann jede beliebige Zeichenkette in das E-Mail-Feld eintragen. Eine Verbindung, der man nicht vertraut, behauptet also email = [email protected], unsere Suche findet das echte Konto des CEO, und der Angreifer ist als diese Person angemeldet. Kein Passwort, kein Phishing, kein Fehler in der Kryptografie. Die Signatur auf der Assertion war vollkommen gültig. Sie bürgte nur für eine Behauptung, die wir niemals als Identität hätten behandeln dürfen.
Das Beunruhigende ist, dass jedes einzelne Teil funktioniert hat. Der IdP hat seinen eigenen Nutzer korrekt authentifiziert. Die Signatur wurde verifiziert. Das Konto existierte. Die Übernahme war kein Versagen einer Prüfung; sie nutzte das falsche Feld als Schlüssel.
Warum mehr Validierung nicht die Lösung ist
Die verlockende Reaktion ist, die E-Mail härter zu validieren. email_verified verlangen. Die Domain prüfen. Eine Regel hinzufügen. Aber beachten Sie die Form der Falle: Der Angreifer kontrolliert die Assertion, also ist jede Eigenschaft, die man aus der Assertion liest, eine Eigenschaft, die der Angreifer setzen kann. Von einer Verbindung, die dem Angreifer gehört, email_verified = true zu fordern, heißt, den Bock zum Gärtner zu machen. Man kann sich nicht aus dem Vertrauen in die falsche Quelle herausvalidieren.
Die E-Mail ist als Bequemlichkeit in Ordnung. Sie ist ein furchtbarer Primärschlüssel, weil sie global ist (dieselbe Adresse kann bei vielen Anbietern auftauchen) und fälschbar (sie ist, was auch immer die behauptende Verbindung sagt). Ein Verknüpfungsschlüssel darf weder das eine noch das andere sein.
Die Lösung war, den Schlüssel zu ändern, nicht Prüfungen hinzuzufügen
Die eigentliche Lösung war, überhaupt nicht mehr über die E-Mail zu verknüpfen, sondern über das eine, das eine Verbindung für einen Anbieter, der ihr nicht gehört, nicht fälschen kann: das Paar (provider, sub). Das sub ist die Subjektkennung, die der Anbieter für diesen Nutzer ausstellt, beschränkt auf diesen Anbieter. Ein wiederkehrender Nutzer wird aufgelöst, indem die lokale Identität nachgeschlagen wird, die zuvor mit dem (provider, sub) dieser Verbindung verknüpft wurde. Die Verbindung eines Angreifers hat ihre eigene Provider-ID; sie kann innerhalb ihres eigenen Namensraums jedes beliebige sub prägen, aber sie kann nicht das (provider, sub) der Verbindung einer anderen Person erzeugen. Der Namensraum ist der Burggraben.
Die E-Mail fällt dann auf die Rolle zurück, die sie immer hätte haben sollen: ein Hinweis zum Verknüpfen, niemals zum Auflösen, und nur, wenn für sie gebürgt wird. Wir ordnen eine behauptete E-Mail einem bestehenden Konto nur dann zu, wenn die E-Mail von einer Quelle verifiziert ist, der wir für diese Domain tatsächlich vertrauen: email_verified von einer Verbindung, deren Domain in den AllowedDomains des Mandanten steht. Außerhalb davon erhält eine neue föderierte Identität ihr eigenes Konto, nicht das einer anderen Person.
Die Lehre, herausdestilliert
Wenn man Identität föderiert, trennt man zwei Fragen, die man zu verschmelzen versucht ist. „Hat dieser Anbieter diesen Nutzer authentifiziert?" wird durch die Signatur beantwortet. „Wer ist dieser Nutzer in meinem System?" muss durch einen Schlüssel beantwortet werden, den die behauptende Partei über Grenzen hinweg nicht fälschen kann, was ein anbieterspezifisches Subject bedeutet, kein globales Attribut wie die E-Mail. Behandeln Sie jedes Feld in einer Assertion als angreiferkontrolliert, es sei denn, die konkrete Quelle dieses Feldes ist eine, der Sie für diese konkrete Behauptung vertrauen. Die E-Mail ist das Feld, nach dem die Leute zuerst greifen, weil sie menschlich und einzigartig aussieht. Sie ist genau das falsche.
Wir haben dies als Teil eines Sicherheitsdurchlaufs vor dem Start über unseren eigenen Auth-Server ausgeliefert, bevor irgendjemand uns seine Logins anvertraut hat. Es ist die Art von Fehler, die jeden Test besteht, jede Signatur verifiziert und die Schlüssel an jeden aushändigt, der im richtigen Format fragt. Die Lösung war kein besseres Schloss. Es war die Erkenntnis, dass wir die falsche Zeile des Ausweises gelesen hatten.
Identität sicher zu föderieren läuft darauf hinaus, über einen Schlüssel zu verknüpfen, den die behauptende Partei nicht fälschen kann, und wer das falsch macht, hat eine Kontoübernahme, die jeden Test besteht. Authagonal löst föderierte Nutzer über ihr anbieterspezifisches Subject auf, niemals über die E-Mail in der Assertion.