blog/Timing_Attacks.txt
ooooooooooooo o8o o8o
8' 888 `8 `"' `"'
888 oooo ooo. .oo. .oo. oooo ooo. .oo. .oooooooo
888 `888 `888P"Y88bP"Y88b `888 `888P"Y88b 888' `88b
888 888 888 888 888 888 888 888 888 888
888 888 888 888 888 888 888 888 `88bod8P'
o888o o888o o888o o888o o888o o888o o888o o888o `8oooooo.
d" YD
"Y88888P'
.o. . . oooo
.888. .o8 .o8 `888
.8"888. .o888oo .o888oo .oooo. .ooooo. 888 oooo .oooo.o
.8' `888. 888 888 `P )88b d88' `"Y8 888 .8P' d88( "8
.88ooo8888. 888 888 .oP"888 888 888888. `"Y88b.
.8' `888. 888 . 888 . d8( 888 888 .o8 888 `88b. o. )88b
o88o o8888o "888" "888" `Y888""8o `Y8bod8P' o888o o888o 8""888P'
________________________________________________________________________________
Vor geschätzten drei Monaten las ich während eines kurzen Kontaktes mit Ruby on
Rails in einem Bugreport zum ersten Mal von "timing attacks". Es handelt sich
dabei um einen Exploit, der die bei manchen Stringvergleichsfunktionen je nach
Eingangsdaten unterschiedliche Laufzeit ausnutzt.
---[ code type:python ]---------------------------------------------------------
def vulnequal(first,second):
if len(first) != len(second):
return False
for i in range(len(first)):
if first[i] != second[i]:
return False
return True
---[ /code ]--------------------------------------------------------------------
Wenn man Beispielsweise diese Funktion die Strings "foo" und "bar" vergleichen
lässt, so arbeitet sie kürzer, als bei einem Vergleich von "bar" und "baz":
+----------------------------------------------------------------------------+
| Laufzeit der oben dokumentierten Funktion vulnequal bei 1.000.000 Aufrufen |
+----------------------------------------------------------------------------+
| Argumente | Laufzeit [s] | Normiert |
+----------------------------------------------------------------------------+
| 'xy', 'bar' | 0.2954 | 0.3552 |
| 'foo','bar' | 0.8317 | 1 |
| 'boo','baz' | 0.9824 | 1.1812 |
| 'bar','baz' | 1.1165 | 1.3424 |
+----------------------------------------------------------------------------+
Mir stellte sich daraufhin die Frage, wie aufwendig es ist, eine Lücke dieser
Art in einem realen Service auszunutzen, bei dem ein Großteil der Latenz dem
Netzwerk und eventuellen Festplatten/Datenbankzugriffen geschuldet ist.
Deshalb bastelte ich mir ein kleines Script, dass keinen Anspruch auf
Realitätsnähe erhebt, es aber schon erlaubt, den Einfluss des Netzwerks zu
erforschen.
---[ code type:python ]---------------------------------------------------------
mport SocketServer as sserv
import sys
class vulnserver(sserv.BaseRequestHandler):
key = sys.argv[1]
def handle(self):
while True:
try:
login = self.request.recv(1024)
if vulnequal(self.key,login):
self.request.send("success\n")
else:
self.request.send("fail\n")
except: break
server = sserv.TCPServer(("",50542), vulnserver)
server.serve_forever()
---[ /code ]--------------------------------------------------------------------
100.000 Anfragen benötigten nun ~380s. Dabei läuft die Vergleichsfunktion nur
0.02954s.
Nun kam ich auf die Idee, nur die Zeitdifferenz zwischen Eintreffen des ACK's
und der Antwort zu messen. Bei einer mit Wireshark mitgesnifften Anfrage betrug
diese 0.659ms, die Zeit zwischen Anfrage und Antwort betrug hingegen 2.165ms.
Leider wird nur bei der ersten Anfrage ACK und Antwort getrennt gesendet.
Um sinnvoll weiterbasteln zu können, bräuchte ich nun ein solides Wissen über
gebräuchliche TCP-Stacks. Cool, schon der zweite Blogeintrag endet erbärmlich.