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

FritzboxDies ist ein update zu meinem blog post ü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/system/syslog.lua?sid=$sid");
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.

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

Leave a Reply

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