Ich beschäftige mich zurzeit mit der Entwicklung einer schmalen, einfachen und vor allem kostenlosen Captive Portal Lösung. Die Hauptidee habe ich von Andy Beverly (http://www.andybev.com/index.php/Using_iptables_and_PHP_to_create_a_captive_portal), das ganze wurde jedoch noch um einen sog. „Walled Garden“ erweitert, damit die Clients gewisse Seite auch ohne Login bzw ohne Bestätigung der Terms & Conditions aufrufen können (Intranet etc.).
Das ganze ist eigentlich ziemlich schnell gebaut: Alles, was benötigt wird, ist eine Linux-Kiste mit zwei Netzwerkkarten, darauf dann noch einen DHCP Server (ISC-DHCPD) mit entsprechend großem Pool, einem DNS Server (bind9) und einen Webserver (Apache), um die Captive Portal Page und das PHP-Script zur Verarbeitung des Logins bereitzustellen.
Abschliessend fehlen dann nur noch die entsprechende iptables-Regeln, um
-Die Anfrage der Clients zu maskieren,
-dann die erlaubten Adresse zuzulassen und danach,
-jeglichen Traffic zu blocken
-alle Anfragen zu Port 80 werden im nächsten Schritt (Tabelle „mangle“) markiert
-In der Tabelle „nat“ lassen wir wiederum die erlaubten Seite trotz Markierung direkt durch,
-alle anderen, markierten Pakete werden zur Captive Portal Page, Port 80 umgeleitet
Dort wird dann, nachdem der User eine Webseite an seinem Endgerät aufruft, die Captive Portal Page präsentiert. Nach Betätigung einer Schaltfläche (Login) wird ein externes PHP-Script aufgerufen, welches die IP und die MAC des Clients ausliest und in Variablen verpackt. Im nächsten Schritt werden die Variablen in mehrere iptables-Statements verpackt, welche dann lokal auf dem Captive Portal Server ausgeführt werden. Faktisch wird der neu angemeldete Client an die jeweils oberste Stelle in den iptables-Regeln geschrieben und umgeht somit das restliche Regelwerk. Damit die Regeln nicht endlos lang werden und dem Client regelmäßig auch wieder die Login-Page gezeigt wird, werden die Regeln der einzelnen Clients alle 12 Stunden per Cronjob wieder entfernt.
Soweit, so gut. Aber auch leider noch nicht perfekt. Denn es bedarf der Interaktion des Users, dieser muss explizit eine, nicht verschlüsselte (!), Webseite aufrufen, andernfalls hat er zwar WLAN, aber kann damit genau gar nichts anfangen. Sieht dann auch irgendwie kaputt aus, bedarf eine Erkärung oder Anleitung und ist irgendwie auch ziemlich OldSchool.
Viele Software- und Betriebssystem-Hersteller sind aus diesem Grund seit einiger Zeit dazu übergegangen, den ersten Internet-Aufruf nach Herstellung einer neuen WLAN-Verbindung selbständig auszuführen, um ggf., falls vorhanden, das Laden der Login Page eines Captive Portals sozusagen zu „provozieren“ – falls ein Captive Portal vorhanden ist, öffnet sich dann meist ein abgespeckter Browser, welcher es dem User ermöglicht, sich anzumelden (Beispiel Vodaphone oder Telekom-Hotspot). Apples Geräte verwenden zu diesem Zweck die Adresse captive.apple.com/hotspot-detect.html, Microsoft verwendet zu ähnlichen Zwecken die Adresse www.msftncsi.com, Android-Geräte versuchen einfach www.google.com aufzurufen. Das klappt zwar am Telekom-Hotspot, nicht aber an meinem Projekt. Die Frage, die sich stellt ist: Wieso nicht?
(Nachträglicher Hinweis an dieser Stelle: Ich teste grundsätzlich mit Apple Geräten. Die Problemstellung hatte ein Kollege mit seinem Android-Handy nicht, somit werde ich auch folgenden nicht weiter auf Android eingehen)
Das sollte sich rausfinden lassen:
Nun sollte man annehmen, dass die Endgerät einfach eine der oben genannten Seiten aufrufen, um, wie in meinem Setup, den Redirect zum Portal zu forcieren. Doch weit gefehlt, das wäre wohl zu einfach gewesen und in meinem Projekt klappt ja eben genau das nicht. Nach einer längeren Recherche, bei welche ich schon die Access Points im Verdacht hatte, meinen eigenen Code mehrfach angezweifelt habe und auch ansonsten schon auf Seite 10 bei Google angekommen war, besann ich mich den Ursprüngen und ging das ganze mit einem technischeren Fokus an – denn nichts ist ehrlicher und aussagekräftiger als ein Paket-Mitschnitt..
Im folgenden der Capture einer Anmeldung am Telekom WLAN, also regulär über einen AP, SSID „Telekom“, geht ab Paket 185 los, und ab hier wird es richtig interessant:
Interessante Pakete in Telekom-WLAN-MacOS.pcapng:
185: captive.apple.com, selbständig angefragt von meinem Macbook (10.139.203.59)
187: Antwort vom DNS-Server, CNAME, Akamai Edge, A Record 104.125.43.150
188: Aufruf von 104.125.43.150
191-203: Kommunikation mit dem „Apple-Server“ bei Akamai
Und genau das ist auch der Trick! Die Telekom blockt nicht und leitet auch nicht vor dem Gateway bzw Portal schon alles um! Die lassen den Aufruf komplett durch bis zu Apple, hier bekommt der Client dann einen 302 zu sehen und zeigt aufgrund des von T-Mobile verwendeten, reinmanipulierten, User Agents „CaptiveNetworkSupport-325.10.1 tmobile_wispr1“ auf https://hotspot.t-mobile.net/wlan/redirect.do + SessionID. (Mehr zu dem ziemlich interessanten Themas „wispr“ siehe: http://blog.erratasec.com/2010/09/apples-secret-wispr-request.html?m=1). Bei dieser Gelegenheit können mit wispr auch gecachete Zugangsdaten für einen evtl. vorhandenen AutoLogin mit übergeben werden.
Ab Paket 213 tauscht sich nun mein Client mit T-Mobile (10.120.136.116) aus, sichert die Verbindung und ruft anschliessend, ab Paket 266, die Captive Portal Page von T-Mobile auf:
Da hätte ich ja bei mir auf dem Captive Portal Server lange suchen und experimentieren können.
Andere Hersteller wie beispielsweise Aruba Networks machen das übrigens ganz ähnlich. Aruba verwendet dabei aber den Standard-wispr Agent, die Identifikation bei Apple scheint dann über MAC bzw Vendor zu erfolgen:
Fragt sich jetzt nur: wie bekomme ich das mit meinem kleinen, unbedeutenden Captive Portal hin, dass die Captive Page ohne Interaktion aufgeht? Ich werde wohl kaum captive.apple.com mit wispr bedienen können.. Oder doch?
Es sieht ganz so aus, als ob ich mit diesem Problem nicht alleine bin. Das OpenSource Project CoovaChilli, ebenfalls eine Captive Portal Lösung, hat sich diesem Thema bereits seit längerem angenommen: https://coova.github.io/development/2010/08/09/coovachilli-wispr-2-0.html
Das ist zwar auch eine Lösung, aber ich möchte eigentlich nicht zu Drittanbieter-Lösungen wechseln, das würde auch den Charme meines Captive Portals schmälern, denn Coova benötigt wesentlich mehr als nur ein paar Bordmittel. Also auch, zumindest für mich, keine Lösung.
Durch meine ganzen Captures wusste ich aber immerhin schonmal, wie das ganze vor sich gehen muss, damit der Login-Dialog (CNA bei Apple genannt, NSCI bei Microsoft) aufgerufen werden kann.
Und so ergab sich dann folgende, äußerst simple „Quick-and-Dirty“-Lösung:
Apple will captive.apple.com/hotspot-detect.html?
Microsoft will msftncsi.com/nsci.txt?
Also gebe ich dem Client genau das, nur eben nicht auf den Servern der jeweiligen Hersteller, sondern direkt bei mir auf dem Captive Portal, ganz stumpf via redirects. Und siehe da: Schon klappt es auch mit dem CNA- bzw. NCSI-Login-Dialog!
Schön ist das sicherlich nicht gelöst, aber es funktioniert – zumindest solange, bis einer der Hersteller seine Adressen zur Hotspot-Erkennung wieder ändert (zuletzt durch Apple mit Einführung von iOS 7 passiert).
Fazit:
Ich hatte wispr bislang nicht auf dem Schirm. Die Recherche zu dieser Thematik war mehr als interessant, wenn auch an vielen Punkten sehr mühsam. Aus Sicht der Datensicherheit empfinde ich wispr als eher unsicher, denn mit aktivierten Flags „automatisch verbinden“ und „automatisch anmelden“ werden die zwischengespeicherten und „gehashten“ Zugangsdaten zu beispielsweise T-Mobile über den wispr-Agent übertragen.. Keine gute Idee in einem ungeschützten WLAN wie dem der Telekom.
Grundsätzlich stellt diese Methode einen Angriffsvector für Man-in-the-middle Angriffe dar, welcher es dem Angreifer theoretisch ermöglicht, sich mit den geklauten Hashes per wispr am Hotspot der Telekom anzumelden.
Abhilfe für das ganze gewispere und für Captive Portals im allgemeinen könnte hier ein von der IETF im Dezember 2015 definierter RFC darstellen: https://tools.ietf.org/html/rfc7710
Bis diese Methode unterstützt wird kann allerdings noch ziemlich viel Wasser den Rhein runterlaufen – der Ansatz ist aber mit Sicherheit „richtiger“ als die aktuelle Situation