
Proof-of-Concept-Exploit für CVE-2022-0778, der eine Endlosschleife in OpenSSLs BN_mod_sqrt über ein manipuliertes X.509-Zertifikat mit Nicht-Prim-Kurvenparametern demonstriert und Denial-of-Service-Angriffe ermöglicht.
Die entdeckte Sicherheitslücke führt zu einer Endlosschleife in der Funktion BN_mod_sqrt() von OpenSSL beim Parsen eines elliptischen Kurvenschlüssels. Das bedeutet, dass ein bösartig erstelltes X.509-Zertifikat einen nicht gepatchten Server mit einem DoS-Angriff lahmlegen kann.
Der Kern der Sicherheitslücke liegt im Parsen von EC-Schlüsseln mit Punkten in komprimiertem Format: Beim Parsen dieser Schlüsselart versucht OpenSSL, den komprimierten Punkt zu expandieren, indem es versucht, eine Quadratwurzel modulo der Primzahl p zu berechnen, über der die Kurve definiert ist. Die Primalität von p wird jedoch nirgends überprüft, nicht einmal in BN_mod_sqrt(), für die sie eine Voraussetzung ist; daher führt ein Fehler in der Implementierung aufgrund von p, das nicht wie erwartet prim ist, zu einer Endlosschleife.
中文版说明可以见我的博客OpenSSL CVE-2022-0778漏洞问题复现与非法证书构造
BN_mod_sqrt()Voraussetzung ist die Installation von gcc und einer verwundbaren Version von OpenSSL.
Für den Fehler in BN_mod_sqrt(): Kompilieren Sie mit gcc -o my_bad_sqrt my_bad_sqrt.c -lcrypto, führen Sie ./my_bad_sqrt aus und beobachten Sie, wie es für immer hängt! :D
Mit einem Zertifikat: Führen Sie openssl x509 -in certs/cert.der.new -inform DER -text -noout in der Befehlszeile aus; auch dies führt zum Hängen.
Die Funktion BN_mod_sqrt() implementiert den Tonelli-Shanks-Algorithmus zum Finden einer modularen Quadratwurzel, d.h. gegeben eine ganze Zahl a und eine Primzahl p, gibt sie einen Wert r zurück, so dass r^2 == a (mod p).
Bei der Analyse des Commits, der die Sicherheitslücke patcht, sehen wir, dass der Übeltäter die Schleife ist, die den kleinsten Index i findet, für den b^(2^i)==1 (mod p) gilt, wobei b zuvor im Algorithmus definiert wird.
Die Schleife wird geändert von
i = 1;
if (!BN_mod_sqr(t, b, p, ctx))
goto end;
while (!BN_is_one(t)) {
i++;
if (i == e) {
ERR_raise(ERR_LIB_BN, BN_R_NOT_A_SQUARE);
goto end;
}
if (!BN_mod_mul(t, t, t, p, ctx))
goto end;
}
zu
for (i = 1; i < e; i++) {
if (i == 1) {
if (!BN_mod_sqr(t, b, p, ctx))
goto end;
} else {
if (!BN_mod_mul(t, t, t, p, ctx))
goto end;
}
if (BN_is_one(t))
break;
}
Im zweiten Fall gibt es eine for-Schleife, die durch die Variable e begrenzt ist; im ursprünglichen Code gibt es jedoch nur eine Prüfung auf den Fall i==e innerhalb einer while-Schleife.
Da diese Schleifen sich innerhalb einer größeren Schleife befinden, die bei jeder Iteration den neuen Wert von e auf den aktuellen Wert von i setzt, versuchen wir die folgende Angriffsstrategie:
i=1 ist; dafür benötigen wir, dass b^2=1 (mod p).e=1, und wenn wir in die innere Schleife gelangen, wird die Prüfung i==e immer fehlschlagen.t != 1 (mod p) zu haben, bleiben wir für immer in der Schleife.Beachten Sie, dass die ersten beiden Schritte tatsächlich in einer „normalen“ Ausführung stattfinden können, d.h. mit einer Primzahl p. Wenn p jedoch zusammengesetzt ist, können wir auch den dritten Schritt erfüllen!
Der Tonelli-Shanks-Algorithmus funktioniert, indem er p - 1 = 2^e * q schreibt, mit einem ungeraden q. Dies ist auch die Ordnung der multiplikativen Gruppe der ganzen Zahlen modulo p, und die Werte e und q werden während der Ausführung mehrfach verwendet; wenn p jedoch nicht prim ist, wird die Ordnung der multiplikativen Gruppe nicht p-1 sein, und dies wird uns helfen, in die Endlosschleife zu geraten.
Insbesondere wird b initialisiert als b = a^q (mod p), was bedeutet, dass, wenn p prim wäre, b die Ordnung einer Potenz von 2 hätte, die wir dann mit der Schleife finden würden.
Aber wenn wir p = r * s setzen, ist die Ordnung der multiplikativen Gruppe (r-1)*(s-1) = r*s - r - s + 1 anstelle von r*s-1. Der Algorithmus verwendet den q-Wert, um ein Element y mit der exakten Ordnung 2^e zu erhalten; wenn p jedoch nicht prim ist, hat der q-Wert keine besondere Bedeutung für die Ordnung, sodass das Element y keine Ordnung einer Potenz von zwei modulo p=r*s haben wird.
Da am Ende der ersten äußeren Schleife b gesetzt wird auf b = b*y^(e-i) (mod p), wird die innere Schleife bei ihrer zweiten Iteration versuchen, einen Wert i zu finden, für den b^(2^i) == 1 (mod p) gilt, aber fehlschlagen, da nicht mehr garantiert ist, dass y eine Ordnung hat, die eine Potenz von zwei ist.
Die Zahlen im Exploit sind sehr einfach: Wir nehmen r=17,s=41, was p=r*s=697 ergibt. Dies bedeutet, dass die berechneten Werte von e und q p-1 = 2^3 * 87 sein werden.
Wir wählen dann a=696, was bedeutet, dass a == -1 (mod p) und auch b == -1 (mod p) bei der Initialisierung. Dies erfüllt Schritt 1, indem e=1 für die folgende äußere Schleife gesetzt wird.
Dann wird b auf ein Element gesetzt, dessen Ordnung keine Potenz von 2 ist, und die innere Schleife bleibt stecken, während sie versucht, ein i zu finden, für das b^(2^i)==1 (mod p) gilt.
OK, jetzt lassen Sie uns ein gefährliches Zertifikat erstellen, das ungültige explizite Kurvenparameter mit einem Basispunkt in komprimierter Form enthält.
Die Dateien dieses Abschnitts befinden sich im Ordner certs.
Zuerst müssen wir einen EC-Private-Key erstellen. Da wir ein Zielzertifikat mit expliziten Kurvenparametern möchten, sollte der Schlüssel ebenfalls explizite Kurvenparameter enthalten.
$ openssl ecparam -out ec.key -name prime256v1 -genkey -noout -param_enc explicit -conv_form compressed
Dann haben wir der Einfachheit halber ein selbstsigniertes Zertifikat erstellt und im DER-Format ausgegeben.
$ openssl req -new -x509 -key ec.key -out cert.der -outform DER -days 360 -subj "/CN=TEST/"
Lassen Sie uns die Zertifikatsinformationen überprüfen. Es enthält wie erwartet explizite Kurvenparameter.
$ openssl x509 -in cert.der -text -noout -inform DER
...
Field Type: prime-field
Prime:
00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:ff:ff
A:
00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:ff:fc
B:
5a:c6:35:d8:aa:3a:93:e7:b3:eb:bd:55:76:98:86:
bc:65:1d:06:b0:cc:53:b0:f6:3b:ce:3c:3e:27:d2:
60:4b
Generator (compressed):
03:6b:17:d1:f2:e1:2c:42:47:f8:bc:e6:e5:63:a4:
40:f2:77:03:7d:81:2d:eb:33:a0:f4:a1:39:45:d8:
98:c2:96
Order:
00:ff:ff:ff:ff:00:00:00:00:ff:ff:ff:ff:ff:ff:
ff:ff:bc:e6:fa:ad:a7:17:9e:84:f3:b9:ca:c2:fc:
63:25:51
...