Fritz!BoxAb Firmware-Version xx.04.74 hat AVM die Login-Prozedur für die Fritzbox von einem normalen Passwort-Login zu einem recht komplizierten challenge-response Verfahren geändert. Ich lese mit einem Perl script die Telefonanrufe alle 6 Stunden aus und logge sie in eine Datei. Nach dem letzten Firmware-Update funktionierte das script plötzlich nicht mehr. Ich meide in Zukunft solche updates. Bereits zum zweiten mal musste ich nämlich nach einem update mehrere Stunden investieren, um die Änderungen zu verstehen und mein script entsprechend anzupassen. Hier also meine Perl Implementation des neuen Login-Verfahrens.

AVM hat das neue Verfahren ziemlich schlecht dokumentiert.  Insbesondere fehlen auch Code-Beispiele.

Das Prinzip

Man erfragt in der Fritzbox einen “challenge” string. Dieser wird mit dem Passwort gehasht und das Ergebnis als “response” mittels POST an die geschützte Seite übergeben. Wenn dieser Login erfolgreich war, enthält der source code der geschützten Seite eine session ID mit 10 Minuten timeout. Diese kann man auslesen und innerhalb des timeout-Intervals mit GET oder POST an weitere geschützte Seiten übergeben.

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/cgi-bin/webcm?getpage=../html/login_sid.xml


my $user_agent = LWP::UserAgent->new;
my $http_response = $user_agent->get('http://fritz.box/cgi-bin/webcm?getpage=../html/login_sid.xml');

Die XML response enthält einen challenge string:

<SessionInfo>
<iswriteaccess>0</iswriteaccess>
<SID>0000000000000000</SID>
<Challenge>fe494b9c</Challenge>
</SessionInfo>

Dieser kann einfach extrahiert werden:

$http_response->content =~ /<Challenge>(\w+)<\/Challenge>/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 es 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. Deshalb muss aus der UTF 16 Bytefolge ein Text-String erzeugt werden. 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->post('http://fritz.box/cgi-bin/webcm',
[
"login:command/response" =>
$challenge_response,
getpage => "../html/de/home/foncallsdaten.xml"
],
);

In diesem Beispiel wird die geschützte Datei foncallsdaten.xml angefragt, in der alle gespeicherten Verbindungsdaten enthalten sind. Wenn statt dessen eine geschützte HTML Datei angefragt wird (z.B. ../html/de/menus/menu2.html), enthält deren html source code bei erfolgreichem Login die session ID, z.B.:

<input type=“hidden” name=“sid” value=“cfa24ddccee06073″>

Wird die session ID ausgelesen, kann sie von nun an in weiteren requests mittels GET oder POST übertragen werden (timeout=10 min).

Ein komplettes Perl script zum Testen des Login:


use strict;
use LWP 5.64;
use Digest::MD5 “md5_hex”;
my $boxpasswort = “geheim”;
# challenge string holen
my $user_agent = LWP::UserAgent->new;
my $http_response = $user_agent->get(”http://fritz.box/cgi-bin/webcm?getpage=../html/login_sid.xml”);
$http_response->content =~ /<Challenge>(\w+)<\/Challenge>/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″;

# XML Telefongesprächs-Daten holen
$http_response = $user_agent->post(”http://fritz.box/cgi-bin/webcm”,
[
“login:command/response” => $challenge_response,
getpage => “../html/de/home/foncallsdaten.xml”
],
);

# XML Daten anzeigen
print $http_response->content;

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.

Google translation DE > EN

Leave a Reply