AVM Fritz!Box challenge-response login Verfahren ab Fritz!OS 5.50 in Perl

Update 9. April 2018:
Das hier beschriebene Login-Verfahren funktioniert nach Hardware- und Firmware-update immer noch (jetzt Fritzbox 7490 mit FRITZ!OS 06.93). Lediglich das Interface ändert sich leider, so dass die URLs und der Code zur Erkennung der Daten angepasst werden mussten. Der html code einer Seite ist zum Auslesen der Daten nicht mehr verwendbar. Statt dessen muss eine JSON Datendatei aufgerufen, z.B. die Anrufliste (lp=calls):

http://192.168.178.1/data.lua?sid=xxxxxxxxxxxxxxxx&lp=calls


FritzboxUpdate 27 August 2013:
Dies ist ein update zu meinem blog post vom 6. Dezember 2009 über das AVM Fritz!Box challenge-response Verfahren. Ab Fritz!OS 5.50 (Firmware Version xx.05.50) wurden von AVM einige Änderungen vorgenommen, die nachfolgend berücksichtigt werden:

Hier meine Perl Implementation des Login-Verfahrens ab Fritz!OS 5.50.
AVM hat das neue Verfahren endlich auch mit Beispiel-code dokumentiert.

Das Prinzip hat sich nicht geändert

Man erfragt in der Fritzbox einen „challenge“ string. Dieser wird mit dem Passwort gehasht und das Ergebnis als „response“ an die Box gesendet. Wenn die response valide war, erhält man eine login session ID mit 10 Minuten timeout. Diese wird ausgelesen und sie kann innerhalb des timeout-Intervals mit GET oder POST an jede weitere geschützte Seiten übergeben werden.

Perl code

Es werden folgende Perl modules benötigt:

use LWP 5.64;
use Digest::MD5 'md5_hex';

Den challenge string erfragt man durch Aufruf der folgenden URL:
http://fritz.box/login_sid.lua

my $user_agent = LWP::UserAgent->new;
my $http_response = $user_agent->get('http://fritz.box/login_sid.lua');

Die XML response enthält einen challenge string:

<SessionInfo>
<SID>0000000000000000</SID>
<Challenge>36c3b87f</Challenge>
<BlockTime>0</BlockTime>
<Rights/>
</SessionInfo>

Dieser string kann einfach extrahiert werden:

$http_response->content =~ /<Challenge>(\w+)/i and my $challengeStr = $1;

Im nächsten Schritt wird die challenge mit dem Fritzbox Passwort gehasht. AVM hat dies sehr kompliziert gemacht. Offenbar wollte man „script kiddies“ den Zugang zur Box erschweren. Es ist in Perl nicht einfach, den MD5 hash einer UTF-16LE kodierten Bytefolge zu erzeugen. Zum einen ist mir nicht gelungen, für ActiveState Perl ein CPAN Modul zu installieren, mit dem man UTF-16 kodieren kann. Zum anderen kann das Perl MD5 Modul keine anderen Eingaben als Text strings verarbeiten.
Athanasios Douitsis hat diese Problematik gelöst. Wer eine bessere UTF Implementation sucht, sollte sich sein Perl script anschauen.
Ich habe der Einfachheit halber aus der UTF 16 Bytefolge einen Text-String erzeugt. Dieser kann dann mit MD5 gehasht werden.

Beispiel:
ASCII = a
Dezimal = 97
Hexadezimal = 61
UTF-16 = 6100

$string = ‚a‘;
$decimal = ord($string);
$hexadecimal = sprintf(„%02lx“, $decimal);
$utf16 = $hexadecimal . ’00‘;

Das hier verwendete einfache UTF-16 „encoding“ ist nicht mehr als das Anhängen von 2 Nullen an den HEX Wert des Zeichens. Dieses Vorgehen ist nur möglich, wenn das zu kodierende Zeichen (insbesondere die Sonderzeichen) ein solches Format in UTF-16 aufweist.

Der challenge string enthält ohnehin nur die Zeichen [a-f0-9], die in UTF-16 Kodierung die 2 Nullen aufweisen. Das Fritzbox-Passwort muss ebenfalls aus Zeichen bestehen, die mit dem gezeigten einfachen Verfahren nach UTF-16 konvertiert werden können. Ich habe nicht alle Zeichen getestet, aber das script arbeitet mit a-z, A-Z, 0-9, Ausrufezeichen, Semikolon, und Punkt ohne Probleme. Damit können Passwörter wie J7hdH!k;4gZ! ohne weiteres verwendet werden.

Hier die verwendete einfache ASCII nach UTF-16 Umformung als Perl one-liner. Mit chr(hex()) werden gleichzeitig aus dem 16 bit UTF Zeichen 2 ASCII Zeichen als input für das Perl MD5 Modul erzeugt:

s/(.)/chr(hex(sprintf("%02lx", ord $1))) . chr(hex("00"))/eg;

Tja, und das ist nichts anderes als:

s/(.)/$1 . chr(0)/eg;

Damit können wir zur challenge eine response gemäß der AVM Dokumentation erstellen:

my $boxpasswort = 'geheim';
 my $ch_Pw = "$challengeStr-$boxpasswort";
 $ch_Pw =~ s/(.)/$1 . chr(0)/eg;
 my $md5 = lc(md5_hex($ch_Pw));
 my $challenge_response = "$challengeStr-$md5";

Das Ergebnis ist eine auf die challenge passende response, die im nächsten Schritt an die Box übertragen wird:

$http_response = $user_agent->get( "http://fritz.box/login_sid.lua?user=&response=$challenge_response");

Als Erbebnis einer validen challenge response wird von der Box die session ID mitgeteilt:

<?xml version=“1.0″ encoding=“utf-8″?>
<SessionInfo>
<SID>6386ded004736215</SID>
<Challenge>b3abe4bc</Challenge>
<BlockTime>0</BlockTime>
<Rights>
<Name>Dial</Name>
<Access>2</Access>
<Name>HomeAuto</Name>
<Access>2</Access>
<Name>BoxAdmin</Name>
<Access>2</Access>
<Name>Phone</Name>
<Access>2</Access>
<Name>NAS</Name>
<Access>2</Access>
</Rights>
</SessionInfo>

Diese session ID kann ausgelesen werden…

$http_response->content =~ /<SID>(\w+)/i and my $sid = $1;

…und von nun an in weiteren requests mittels GET oder POST übertragen werden (timeout=10 min), zum Beispiel für den Aufruf der Seite System-Ereignisse:
http//fritz.box/system/syslog.lua?sid=6386ded004736215
Ein komplettes Perl script zum Testen des Login:

use strict;
use LWP 5.64;
use Digest::MD5 "md5_hex";
my $boxpasswort = "geheim";
my $user_agent = LWP::UserAgent->new;
# challenge string holen
my $http_response = $user_agent->get("http://fritz.box/login_sid.lua");
$http_response->content =~ /<Challenge>(\w+)/i and my $challengeStr = $1;
# response zur challenge generieren
my $ch_Pw = "$challengeStr-$boxpasswort";
$ch_Pw =~ s/(.)/$1 . chr(0)/eg;
my $md5 = lc(md5_hex($ch_Pw));
my $challenge_response = "$challengeStr-$md5?;
# Session ID erfragen
$http_response = $user_agent->get( "http://fritz.box/login_sid.lua?user=&response=$challenge_response");
# Session ID aus XML Daten auslesen
$http_response->content =~ /<SID>(\w+)/i and my $sid = $1;
# Ereignisse-Seite mit SID als GET Parameter aufrufen und html ausgeben
$http_response = $user_agent-get("http://fritz.box/?sid=$sid&lp=xxxxx");
print $http_response->content;


Das  xxxxx steht in den neueren Fritzboxen für Seiten wie overview,  inet, tel, lan, wlan, dect, diag, sys (siehe lp Parameter der URL in der Statusleiste des browsers beim Selektieren eines Menüeintrags mit der Maus).

Ich hoffe, du findet dieses Perl script hilfreich. Mir hätte es jedenfalls einige Stunden Arbeit erspart. Falls du Fritzbox-Daten mit Perl auslesen willst oder Anmerkungen/Fragen hast, würde ich mich über eine Email freuen.

6 thoughts on “AVM Fritz!Box challenge-response login Verfahren ab Fritz!OS 5.50 in Perl

  1. Funktioniert die Methode noch mit der aktuellen Firmware (06.*)?

    Ich bekomme leider keine gültige SID:

    000000000000000014cd4fac128

  2. @Stefan
    Ich habe lange nichts mehr mit der Fritzbox gemacht, weiß daher leider nicht ob neuere Versionen diese Methode noch unterstützen.
    Gruß, Walter

  3. Boah, das Perl-Skript hat mich gerettet. Ich habe tagelang mit einer Arduino Anwendung gekämpft. Das Problem war, daß der Arduino Code aus der Response GROSSBUCHSTABEN macht und die Fritzbox braucht kleinBuchstaben. Dank dem Skript wars dann klar. Danke

  4. @Achim
    Freut mich dass ich helfen konnte. Benutzt du den Code oder hat er dich lediglich geholfen, das Arduino Problem zu erkennen?
    Gruß,
    Walter

  5. @Walter:
    Ich habe den Code von Dir nicht im Einsatz. Aber dann eine Lösung in Arduino-C daraus gebastelt.
    Nochmals Danke & Gruß
    Achim

Comments are closed.