Der Code sagte GOTO FAIL, und Apples SSL-Prüfung gehorchte
Apple hat ein dringendes Update für iOS veröffentlicht, die damit behobenen Schwachstelle in der Prüfung von SSL-Zertifikaten betrifft auch Mac OS X. Der zu Grunde liegende Fehler ist... sagen wir mal "interessant", und das aus mehreren Gründen.
Mit einem iOS-Update ging es los...
Mit dem Update auf iOS 6.1.6 und 7.0.6 sowie Apple TV 6.0.2 wurde die Schwachstelle mit der CVE-ID CVE-2014-1266 behoben. Apples Beschreibung dazu lautet
"Impact: An attacker with a privileged network position may capture or modify data in sessions protected by SSL/TLS
Description: Secure Transport failed to validate the authenticity of the connection. This issue was addressed by restoring missing validation steps."
Das klingt schon mal gar nicht gut. Die Frage ist nur, was ein Angreifer mit einer "privileged network position" sein soll. Wie sich herausgestellt hat, zum Beispiel ein Man-in-the-Middle. Das ist die NSA ja gerne mal, in offenen WLANs kann sich ein MitM leicht in die Verbindung einschleichen, und dann gibt es ja des öfteren Probleme mit SOHO-Routern, die schnell dazu führen können, dass ein MitM es sich im Router gemütlich macht. Mit anderen Worten: Diese Schwachstelle gehört in der Tat zu den schlimmsten, die es im Fall von SSL geben kann.
Katze im offenen Sack
Da Apple den betroffenen Socurcecode veröffentlicht, dauerte es nicht lange, bis jemand auf den Fehler hin wies. Nachdem die Katze damit aus dem Sack war, hat Adam Langley sich die Mühe gemacht, das Problem zu erklären. Was eigentlich gar nicht nötig ist, denn wenn man weiß, nach was man suchen muss, erklärt sich das Problem eigentlich von selbst. Hier ist der relevante Teil des Sourcecodes, den Fehler habe ich rot hervor gehoben:
static OSStatus
SSLVerifySignedServerKeyExchange(SSLContext *ctx, bool isRsa, SSLBuffer signedParams,
uint8_t *signature, UInt16 signatureLen)
{
OSStatus err;
...
if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
goto fail;
goto fail;
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
goto fail;
err = sslRawVerify(ctx,
ctx->peerPubKey,
dataToSign, /* plaintext */
dataToSignLen, /* plaintext length */
signature,
signatureLen);
...
fail:
SSLFreeBuffer(&signedHashes);
SSLFreeBuffer(&hashCtx);
return err;
}
Ursache des Problems sind die zwei "goto fail;
"-Zeilen. Die
erste ist an die if-Abfrage gebunden und wird nur ausgeführt, wenn die
if-Bedingung erfüllt ist. Die zweite, von mir rot markierte, aber
führt dazu, dass der Code immer nach fail:
springt, wenn
er die Anweisung erreicht. Die eigentliche Prüfung der Signatur durch
die Funktion sslRawVerify()
wird also niemals
durchgeführt. Und da SSLHashSHA1.update()
beim Erreichen
der "goto fail;
"-Zeile bereits erfolgreich war (sonst
wäre schon aus einer der if-Anweisungen davor zu fail:
gesprungen worden), enthält err
einen erfolgreichen Wert
und die Signaturprüfung durch
SSLVerifySignedServerKeyExchange()
schlägt niemals fehl.
Die Funktion SSLVerifySignedServerKeyExchange()
prüft die
Signatur einer
ServerKeyExchange-Nachricht,
die von DHE- und ECDHE-Verschlüsselungsverfahren verwendet wird, um
dem Client den Sitzungsschlüssel mitzuteilen. Die Nachricht
enthält den Schlüssel und die zugehörige Signatur des
Servers, mit der der Server beweist, dass der Schlüssel auch wirklich
von ihm stammt und nicht von einem Man-in-the-Middle eingeschleust wurde.
Da die Signaturprüfung im Grunde nicht statt findet, kann ein Man-in-the-Middle seinen eigenen Sitzungsschlüssel einschleusen. Er muss nur ein Zertifikat mit einer korrekten Zertifikatskette bis zu einem vertrauenswürdigen Herausgeber an den Client senden, den selbst erzeugten Schlüssel in der ServerKeyExchange-Nachricht kann er mit irgend einem vom Zertifikat unabhängigen privaten Schlüssel oder auch gar nicht signieren - die Signatur wird ja gar nicht geprüft. Der Client verwendet dann diesen Schlüssel, und der MitM kann die Kommunikation nach belauschen und nach Belieben manipulieren.
Besonders unschön ist, dass viele Schutzmaßnahmen in diesem Fall nicht greifen:
- Beim Certificate Pinning besteht der Webbrowser auf einem bestimmten Zertifikat. Das Zertifikat interessiert den MitM aber gar nicht, er kann einfach das Originalzertifikat durch reichen. Er muss in der ServerKeyExchange-Nachricht nur den vom Server erzeugten und signierten Session-Schlüssel durch seinen eigenen Session-Schlüssel mit seiner Signatur oder ohne Signatur ersetzen.
- Versuchen Client oder Server TLS 1.2 zu verwenden, dass eine andere Funktion zum Prüfen der ServerKeyExchange-Nachricht verwendet und nicht von der Schwachstelle betroffen ist, kann der MitM versuchen, SSL aus zu handeln. Sofern Client und Server sich darauf einlassen, ist der Angriff möglich. Der Schutz ist also nur erfolgreich, wenn der Client ausschließlich TLS 1.2 verwendet und ansonsten auf den Verbindungsaufbau verzichtet. Was unpraktisch ist, wenn auf der Gegenseite ein Server steht, der TLS 1.2 einfach nicht unterstützt.
- Das gleiche gilt, wenn nicht betroffene Cipher-Suiten verwendet werden sollen - solange der MitM eine der betroffenen Suiten aushandeln kann, ist der Angriff möglich.
- Nur wenn der Client ausschließlich die reinen RSA-Cipher-Suiten verwendet ist kein Angriff möglich, da es dann keinen ServerKeyExchange gibt. Dafür ist aber keine Verbindung mit Servern möglich, die diese Chiffren nicht unterstützen.
Adam Langley hat eine Testsite eingerichtet, mit der Sie prüfen können, ob Ihr Rechner von der Schwachstelle betroffen ist: https://www.imperialviolet.org:1266/. An diesem Port sendet der Server sein korrektes SSL-Zertifikat, signiert die ServerKeyExchange-Nachricht aber mit einem komplett anderen Schlüssel. Wenn alles in Ordnung ist, muss es eine Fehlermeldung wie zum Beispiel
"Safari kann die Seite "https://www.imperialviolet.org:1266/" nicht
öffnen, da Safari keine sichere Verbindung zum Server
"https://www.imperialviolet.org:1266/" aufbauen kann."
geben, da die Signaturprüfung fehl schlägt. Wenn Sie von diesem Port eine HTTPS-Website laden können, wurde die falsche Signatur akzeptiert, Ihr System enthält also den Fehler.
Update 25.2.:
Aldo Cortesi hat sein Tool
mitmproxy
so
angepasst,
dass die Schwachstelle damit ausgenutzt werden kann.
Ende des Updates
Mac OS X Mavericks ist auch betroffen
Das aktuelle Mac OS X Mavericks enthält die Schwachstelle ebenfalls, einen Patch hat Apple bereits angekündigt. Besser wäre es allerdings gewesen, alle Updates auf einmal auszuliefern.
Update 25.2.:
Apple hat OS X Mavericks 10.9.2 und das Security Update 2014-001
veröffentlicht,
dass unter anderem die "GOTO FAIL"-Schwachstelle behebt.
Ende des Updates
GOTO ist nicht harmlos!?
Spätestens seit Edsger W. Dijkstra berühmten Text "Letters to
the editor: Go To Statement Considered Harmful" in den "Communications
of the ACM" 11, Nr. 3, März 1968 (als
PDF
und
Text)
sollte man bei Entwicklern voraus setzen, dass Sie beim Einsatz des
GOTO-Befehls vorsichtig sind. Diese Fehler sieht mir auf den ersten Blick
sehr nach einem Lösch- oder Copy&Paste-Fehler aus - entweder wurde
eine if-Abfrage raus geschnitten und die zugehörige "goto
fail;
"-Zeile vergessen, oder beim rein kopieren der "goto
fail;
"-Zeile wurde zwei mal eingefügt. Hätte man sich den
entstandenen Code angesehen, hätte dieser Fehler auffallen
müssen. Oder zumindest sollen. Und bei sicherheitsrelevanten Code
wie diesem sollte man eigentlich davon ausgehen, dass jede
Code-Änderung von einem zweiten Entwickler geprüft wird.
Klammert gefälligst!
Andererseits ist das eigentliche Problem nicht das GOTO, der gleiche Fehler
wäre auch entstanden, wenn die Freigabe der Puffer und die
Rückgabe des Fehlercodes in einer Funktion erledigt würde und da
statt goto fail;
zum Beispiel return
cleanup(err);
gestanden hätte. Das oder zumindest ein Problem
ist m.E. die Verwendung von if-Abfragen ohne Code-Block. Um es brutal zu
formulieren: Wäre anständig geklammert worden, wäre es sehr
wahrscheinlich nie zu diesem Fehler gekommen. Aber wenn man es sich
einfach macht, macht man es eben auch dem Entstehen von Fehlern und damit
Schwachstellen einfach.
Nehmen wir mal an, die "goto fail;
"-Zeile ist versehentlich in
die if-Abfrage eingefügt worden. Dann stünde da
...
if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0) {
goto fail;
}
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) {
goto fail;
goto fail;
}
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0) {
goto fail;
}
...
}
und die zusätzliche GOTO-Anweisung hätte keinerlei Auswirkung auf dem Programmablauf. Jedenfalls wenn der, der sie eingefügt hat, sie in die if-Abfrage einfügen wollte.
Wobei es natürlich auch sicher unendlich viele Möglichkeiten gibt, den gleichen Fehler mit Klammerung zu erzeugen. Womit wir bei der entscheidenden Frage angekommen sind: Ist das ein zufällig entstandener Fehler - oder eine absichtlich eingefügte und als "Fehler" getarnte Backdoor? Denn die würde man genau als solche Art von Fehler tarnen.
Wo kommt der Fehler her?
Alex Yakoubian hat ein
Diff
der Dateien aus Mac OS X 10.8.5 (Security-55179.13) und 10.9 (Security-55471)
erzeugt.
Die betroffene Zeile 631 wurde anscheinend ziemlich unmotiviert
hinzugefügt, es gibt zumindest keinen ersichtlichen Grund dafür,
warum sie eingefügt wurde. Die anderen Code-Änderungen in der
Funktion SSLVerifySignedServerKeyExchange()
scheinen mit
dieser zusätzlichen Zeile nichts zu tun zu haben. Vielleicht hat man
bei Apple ja den Grund für diese Code-Änderung dokumentiert und
verrät ihn uns irgendwann.
Fehler oder Hintertür?
Die Zeile wurde gezielt eingefügt. Die Frage ist eben nur, wieso. Und so lange Apple sich nicht dazu äußert, kann man nur spekulieren. Und da man nichts mit Boshaftigkeit erklären soll, was sich auch durch Dämlichkeit erklären lässt, und außerdem "Im Zweifel für den Beschuldigten" gelten sollte, sollten wir erst mal von einem ziemlich dummen Fehler ausgehen. Der, der das eingefügt hat, dürfte zur Zeit einige unangenehmen Fragen beantworten müssen und noch unangenehmere Kommentare zu hören bekommen.
Ich hoffe, man vergisst dabei nicht, auch das "Umfeld" zu berücksichtigen - wieso wurde diese Änderung nicht sofort als Fehler erkannt? Wurde die Konsequenz der Änderung nicht erkannt, oder wurde die Änderung gar nicht geprüft? Haben Prüfungen und/oder Tests versagt, oder liegt das Problem schon in der Organisation der Abläufe und es gibt keine Prüfungen und Tests?
Fragen über Fragen
Wie die Schwachstelle entstanden ist und ob es sich um einen Fehler oder eine absichtliche Backdoor handelt, kann nur Apple klären. Ich hoffe, es gibt irgendwann eine Erklärung und Apple kehrt die Ursache für dieses Desaster nicht unter den Teppich.
Interessant wäre es auch, zu wissen,
- welche weiteren Änderungen es zwischen den beiden veröffentlichten Sourcecode-Versionen gibt. Vielleicht ist diese einzelne Zeile ja Bestandteil einer größeren Änderung und wurde beim Löschen einer if-Abfrage übersehen,
- oder ob die Zeile vielleicht im Rahmen des Zusammenführens verschiedener Versionen entstanden ist. Wobei ein derartiger Merge-Fehler doch ziemliche Zweifel an der Tauglichkeit der verwendeten Merge-Tools aufwerfen würde.
- seit wann Apple von der Schwachstelle weiß - die CVE-ID wurde am 8. Januar reserviert, aber das hat nichts zu bedeuten, die großen Softwarehersteller reservieren gerne mal größere Blöcke an IDs und verwenden die dann nach und nach.
- wer die Schwachstelle entdeckt hat - Apple oder ein externer Forscher? Oder wurde sie womöglich bereits für Angriffe ausgenutzt?
Ich hoffe, Apple verrät uns möglichst bald, was da los war. Denn wo ein solcher Fehler entdeckt wurde, gibt es ja vielleicht noch weitere.
Trackbacks
Dipl.-Inform. Carsten Eilers am : Neues zur iOS-Sicherheit, bösartigen E-Mails und Überwachungsmöglichkeiten
Vorschau anzeigen
Dipl.-Inform. Carsten Eilers am : 2014 - Das Jahr, in dem die Schwachstellen Namen bekamen
Vorschau anzeigen
Dipl.-Inform. Carsten Eilers am : Drucksache: Windows Developer 10.15 - Wie sicher ist C# 6.0?
Vorschau anzeigen
entwickler.de am : PingBack
Die Anzeige des Inhaltes dieses Trackbacks ist leider nicht möglich.