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!

5 Kommentare:

  1. Um einen CSRF-Angriff zu machen ist es (allgemein und bei FEHM insbesondere) nicht notwendig dass man ein zweites Browserfenster / einen weiteren Tab mit FHEM offen hat. Es reicht also nicht, einfach nur alle FHEM-Browserfenster zu schließen um sich zu schützen.

    CRLF wird übrigens nicht durch cURL erzeugt, sondern von FHEM. Und das ist auch in RFC2616 so spezifiziert.

    Und du hast auch aus dem Thread die falsche Schreibweise mit den & als Einleitung für den Query String übernommen (ebenfalls in RFC2616 spezifiziert).

    AntwortenLöschen
    Antworten
    1. Da hast Du ja schneller mitgelesen als ich nach meinen letzten Änderungen auf veröffentlichen gedrückt habe.
      Danke für die Hinweise, ich habe die noch vorhandenen falschen &XHR korrigiert und meine Aussage mit curl berichtigt. Das mit dem Angriff habe ich verstanden, Du hast es gut ergänzt, ich lasse meine Einleitung mal so.

      Löschen
  2. Hi danke für den Post, jetzt weiß ich warum mein curl script aufgehört hat zu arbeiten. Nun tuts wieder ! :-)

    AntwortenLöschen
  3. Hallo Otto,
    ich möchte von einem Loxone Miniserver ein http Aufruf zu FHEM absetzen um einen Rollladen zu steuern.
    Loxone Miniserver kann kein curl, wget, etc. und somit bleibt mir, wenn ich das richtig verstanden habe, nur die Option WEBapi.
    Wenn das soweit richtig ist, kann ich dann auch auf eine Authentifizierung mit Benutzer+Passwort verzichten?
    Und wäre https auf dem FHEM Server notwendig?

    FHEM und Loxone Miniserver sind in einem separaten Netzwerk ohne Zugriff von außen (Firewall-Regeln auf dediziertem Router für dieses Netzwerk).

    AntwortenLöschen
    Antworten
    1. Hi,
      so wie Du beschreibst würde ich die Option WEBapi nutzen wie oben beschrieben. Dann ist der Zugriff ja eingeschränkt.
      https ist bezüglich des Zertifikates sicher wieder aufwendig und in dem Szenario nicht nötig.
      Gruß Otto

      Löschen