Sonntag, 5. März 2017

csrf Token und FHEM

Wenn ich es richtig verstanden habe, gibt es eine Bedrohung wenn man im Browser mehrere Seiten offen hat und eine davon ist "böse". Diese Seite könnte die aktive Anmeldung an einem anderen Webserver ausnutzen und diesen angreifen. Heise hat das hier ganz gut beschrieben.

In FHEM wurde Anfang des Jahres mit der Version 5.8 der csrfToken als eine Abwehr Maßnahme scharf geschaltet. Was leider dazu führt, dass man nicht mehr mit einem einfachen http Link auf das FHEMWEB zugreifen kann.
Was bisher einfach so ging:
curl http://localhost:8083/fhem?cmd=set%20Office%20on
muss jetzt um den Token ergänzt werden. Man beachte auch: die URL muss dafür in Anführungszeichen stehen!
curl "http://localhost:8083/fhem?cmd=set%20Office%20on&fwcsrf="
Mit dem Einzeiler
curl -s -D - 'http://localhost:8083/fhem?XHR=1' | awk '/X-FHEM-csrfToken/{print $2}'
kann man den aktuellle csrfToken aus dem Header extrahieren und muss ihn nur noch an den Aufruf anhängen. Das Ganze mit einmaliger Angabe des Hostnamen(IP) und dem FHEM Befehl mit normalen Leerzeichen, sieht so aus:
h='host:Port'; c='FHEM Befehl'; curl --data "fwcsrf=$(curl -s -D - http://$h/fhem?XHR=1 | awk '/X-FHEM-csrfToken/{print $2}')" http://$h/fhem?cmd=$(echo $c|sed 's/ /%20/g')

Da bei Headerfeldern immer ein cr+lf angehängt wird (danke für den Hinweis unten im Kommentar), kann man den ermittelten Token nicht so wie hier kurz gezeigt direkt anhängen.  Diese Zeile
curl "http://localhost:8083/fhem?cmd=set%20Office%20on&XHR=1&fwcsrf="`curl -s -D - 'http://localhost:8083/fhem?XHR=1' | awk '/X-FHEM-csrfToken/{print $2}'`
oder auch diese Schreibweise:
curl "http://localhost:8083/fhem?cmd=set%20Office%20on&XHR=1&fwcsrf="$(curl -s -D - 'http://localhost:8083/fhem?XHR=1' | awk '/X-FHEM-csrfToken/{print $2}')
führt zu dem Fehler: curl: (3) Illegal characters found in URL

So wird cr+lf verhindert ( Diskussion im Forum ):
curl "http://fhem.example.org:8083/fhem?cmd=set%20Office%20on&XHR=1&fwcsrf="`curl -s -D - 'http://fhem.example.org:8083/fhem?XHR=1' | awk '/X-FHEM-csrfToken/{print $2}' | tr -d "\r\n"`
Mir persönlich gefällt die Variante mit -d (--data) besser.

Der csrfToken wird bei einem Neustart von FHEM neu generiert. Bis dahin bleibt er gültig. Hat man mehrere Anweisungen in Folge, genügt es den Token einmal auszulesen und zu speichern. Hier zwei Varianten.
Bash Script:
token=$(curl -s -D - 'http://<host>:8083/fhem?XHR=1' | awk '/X-FHEM-csrfToken/{print $2}')
curl --data "fwcsrf=$token" http://<host>:8083/fhem?cmd=set%20Aktor1%20off
Powershell:
# Wenn man mehr aus dem Request herausziehen will, erstmal in ein Objekt
# UseBasicParsing verhindert die Verwendung des IE und dessen Securityabfragen
$wp=Invoke-WebRequest -UseBasicParsing -Uri "http://<host>:8083/fhem?XHR=1"
$token = $wp.Headers["X-FHEM-csrfToken"]
# Alternativ nur der Token
$token = Invoke-WebRequest -UseBasicParsing -Uri "http://<host>:8083/fhem?XHR=1" | %{$_.Headers["X-FHEM-csrfToken"]}
$URL="http://<host>:8083/fhem?cmd=set%20Aktor1%20on&fwcsrf=$token"
Invoke-WebRequest -UseBasicParsing -Uri $URL | out-null
Um den Zugriff im internen Netzwerk zu vereinfachen, kann man auch ein separates "API Web" ohne Token einrichten:
define WEBapi FHEMWEB <Port> global
attr WEBapi csrfToken none
attr WEBapi allowFrom 192.168.x.x|127.0.0.1
Im Attribute allowfrom wird wird mit einem regEx der IP Adressbereich angegeben. Einzelne Adressen kann man einfach mit | (oder) trennen, Bereiche werden etwas aufwendiger.
Ich würde es aber lesbar und übersichtlich halten, eine Hand voll Adressen geht einfach so:
192.168.178.(1|20|203|124)
Wichtig: Dieses Web für den Zugriff von Maschinen einzurichten, an denen man vom Browser aus arbeitet ist am Thema vorbei!