PyroPeter's homepage (PyroPeters Heimseite)

IPv6 ready since 2010

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.