IPv4 1.2.3.4

Als ich kürzlich die Zeilen

Waiting for 1.2.3.4

und dann noch

Transferring from 1.2.3.4

in der Status-Zeile des Firefox Browser las wurde ich etwas unrund. Die aufgerufene Seite (http://snowhow.info) hatte ich selbst entwickelt und ganz sicher keinen Link auf http://1.2.3.4 eingebaut. Irgendwie schaute die Seite auch anders aus, aber nicht wesentlich. Also den Quellcode der Seite studiert und da steht es: Vor der head-Anweisung hat jemand Javascript-Code aus einer anderen Domain eingeschleust:

<script src="http://1.2.3.4/bmi-int-js/bmi.js" language="javascript"></script>

Gehackt

Die Seite wurde gehackt, ganz klar. Aber wie konnte ich gehackt werden und wer hat eigentlich die extrem coole IP-Adresse 1.2.3.4 auf die da verlinkt wird? Dr. Google hilft in solchen Fällen ja immer sehr zuverlässig: In meinem Fall wird das Javascript von meinem aktuellen Internet Service Provider (eine Prepaid SIM von Vodafone Greece) eingefügt um Datenvolumen zu sparen. Also nicht gehackt, zuerst einmal durchschnaufen.

Doch nicht gehackt

Wie viele Seiten im Netz zeigen ist es gar nicht einfach (oder mir zumindest nicht gelungen) die wohl gut-gemeinte Optimierung abzuschalten: Vodafone proxyt alle Bilder, verkleinert sie massiv und schickt nur schlecht aufgelöste Versionen an den Client. Für Entwickler unangenehm dabei ist, dass der Javascript-Code nicht gerade state-of-the-art ist: Es werden einige globale Variable erzeugt (oder überschrieben) und es wird versucht eine Art Tooltip mit inkorrekten Informationen über alle Bilder zu legen.

Und wer ist jetzt 1.2.3.4?

Wie Google ebenfalls ans Licht brachte wurde der Adressblock im Jahr 2010 zwar vergeben, aber er kann nicht seriös verwendet werden, weil Skripts wie dieses den Bereich vollständig überschwemmen. Schade eigentlich.

Fazit

Mit HTTPS wäre das nicht passiert! Die Übertragung der Webseite wird dabei vom Browser zum Webserver verschlüsselt, was es für den ISP unmöglich macht eine Javascript Datei unterzuschieben.

Nodejs und WordPress mit gemeinsamer Authentifizierung

Das Problem

Bei snowhow verwenden wir WordPress als Content-Management-System. Die Benutzer-Accounts für die snowhow User werden auch in WordPress verwaltet. Warum? Das System ist sehr weit verbreitet, die User-Verwaltung sollte also ausgereift sein (ge-hashte Passwörter, Passwort per E-Mail zurücksetzen).

Außerhalb von WordPress als CMS setzen wir auf Nodejs und MongoDB. Die aufgezeichneten Freeride-Tracks und Skitouren kommen als GeoJSON direkt in die MongoDB und können dort via räumlicher Suche gefunden werden, herrlich unkompliziert. Aber wie kann der Nodejs-Server (express) auf die Authentifizierung von WordPress zugreifen?

  • Das node-Modul node-wordpress-auth funktionierte mit einer älteren Version von WordPress, war aber immer schon ein dirty hack.
  • Die Passport Authentifizierung via OAuth2 für WordPress wäre prinzipiell möglich (ein OAuth2-Provider-Plugin für WordPress vorausgesetzt), die Nuss wäre damit wohl mit dem Vorschlaghammer geknackt.
  • Als Lösung wählten wir den gemeinsamen Zugriff auf Session-Daten von PHP und Nodejs (hier kommt uns zugute, dass der Nodejs-Server durch den apache, der WordPress betreibt, ge-proxy-t wird). Wie das geht?

Die Lösung: Gemeinsamer Zugriff auf Session Daten

Klingt einfacher, als es dann war. Hier die Kurzversion:

  1. Die PHP Sessions in den memcached Speicher verlegen.
  2. Ein WordPress Plugin schreiben, das die User-ID in die Session speichert.
  3. Nodejs Module installieren: memcached cookies php-unserialize.
  4. Den Javascript Code anpassen, damit die Session-Daten ausgelesen und dekodiert werden können.

Im Detail sind dazu folgende Schritte notwendig:
ad 1.) Zuerst wird der memcached-Server installiert. Unter Ubuntu Linux ein leichtes mit

apt-get install memcached php5-memcached

Der Server startet (in der Standardeinstellung unter Ubuntu) automatisch auf Port 11211 und der Zugriff ist nur vom localhost möglich. Als nächstes gilt es die PHP Konfiguration anzupassen. Im Fall von Ubuntu legt man zum Beispiel die Datei /etc/php5/apache2/conf.d/30-memcached-sessions.ini mit folgendem Inhalt an:

session.save_handler = memcached
session.save_path='127.0.0.1:11211'

Jetzt nicht vergessen den apache neu zu starten, dann sollten die Sessions im memcached Server liegen.

ad 2.) WordPress verwaltet die Session in einem eigenen Cache, nicht in der PHP Session. Ein kleines Plugin bringt die Login Informationen von WordPress in die PHP Session (die Idee dazu stammt von dieser Seite):

add_action('init', 'startSession', 1);
add_action('wp_logout', 'endSession');
add_action('wp_login', 'setSessionData', 10, 2);

function startSession() {
  session_name("snowhowSession");
  if(!session_id()) {
    session_start();
    $_SESSION['snowhowinit'] = true;
  }
}

function setSessionData($user_login, $user) {
  $_SESSION['wp_user_id'] = $user->ID;
  $_SESSION['wp_user_email'] = $user->user_email;
}

function endSession() {
  session_destroy();
}

ad 3.) Das sollte ein Leichtes sein:

npm install memcached cookies php-unserialize

Wird der Javascript Code in Form eines Node-Pakets verwaltet (was vermutlich eine gute Idee ist), trägt man die Abhängigkeiten am besten gleich in die package.json Datei ein und kann sie am Produktionssystem mit npm install installieren.

ad 4.) Zum Schluss wird noch eine Javascript-Funktion definiert, die die Login Informationen, sofern vorhanden, zurück gibt:

var Cookies = require('cookies');
var PHPUnserialize = require('php-unserialize');
var Memcached = require('memcached');
var mem = new Memcached('127.0.0.1:11211');

function getWPUser(req, res, cb) {
  var cookies = new Cookies(req, res);
  var sessionCookie = "memc.sess.key."+cookies.get("snowhowSession");
  mem.get(sessionCookie, function(err,data) {
    var user;
    if (err) cb({ error: err });
    if (data) {
      try {
        user = PHPUnserialize.unserializeSession(data);
      } catch(e) {
        user = {};
      }
    }
    cb(user);
  });
}

Hier ist noch anzumerken, dass das php-unserialize Modul nicht korrekt mit PHP Objekten (new stdClass) umgehen kann (zumindest nicht bei meiner Installation).

Fazit

Einige Klimmzüge sind notwendig, um die zwei Serverdienste auf der Backendseite zu verbinden. Das Session-Cookie (hier mit dem Namen snowhowSession) ist der gemeinsame Nenner der beiden Dienste. Das System funktioniert natürlich nur, wenn der Nodejs-Server durch den apache ge-proxy-t wird.

Reverse ssh tunnel mit autossh

Eigentlich ist das Port-Forwarding im Router so eingestellt, dass der Remote Zugriff auf den Heimserver problemlos funktioniert. Leider hat der Provider aber die Möglichkeit den Router neu zu initialisieren (und damit meine Forward-Regeln zu löschen) und er macht auch massiv Gebrauch davon. Zwar kann der Server dann weiterhin mit dem Internet kommunizieren (und so auch als nagios-Server andere Server überwachen), der Zugriff von außen funktioniert aber nicht mehr.

Mit einem Server im Internet lässt sich dem Problem ein Schnippchen schlagen, in dem man den Heimserver anweist einen reverse ssh tunnel aufzubauen. Über diesen kann man später auf das Heimnetz zugreifen und die Port-Forwarding Regeln neu einspielen. Damit der Tunnel nach der Unterbrechung durch den Provider wieder hergestellt wird hilft autossh. Mit folgendem Eintrag in /etc/rc.local (Ubuntu Server):

sudo -u meinuser /usr/bin/autossh \
  -R 17777:localhost:22 -N meinserver.tld &

bleibt die Verbindung immer offen. Hier noch kurz die Erklärung der Einzelheiten:

  • sudo -u meinuser: Die Datei /etc/rc.local wird als root ausgeführt, der SSH-Tunnel soll aber unter meinem User aufgebaut werden
  • autossh -R 17777:localhost:22: autossh übergibt alle Parameter an ssh. Hier wird der reverse Tunnel auf Port 17777 zum Port 22 der lokalen Maschine hergestellt.
  • -N: ssh soll keine Shell starten, nur den Tunnel bereit stellen

Natürlich müssen die ssh-Schlüssel für den eigenen Account mit dem Server meinserver.tld zuerst ausgetausch werden, damit ein login ohne Passwort möglich ist. Anschließend kann man mit den Kommandos

ssh meinuser@meinserver.tld
ssh -p 17777 localhost

vom Internet aus auf den Heimserver zugreifen. Der Port 17777 ist dabei nur für Benutzer am Server meinserver.tld zugängig.

vsftp mit chroot für sehr eingeschränktes FTP

Für verschiedene Dienste bekommen wir stündlich, beziehungsweise täglich Daten von unseren Partnern. Diese Systeme verwenden FTP für den Transfer, weshalb wir einen eigenen FTP-Server (vsftp) betreiben müssen. Der Zugriff wird mehrstufig abgesichert:

  1. Firewall-Regeln, die den Dienst auf das Subnetz des Partners einschränken
  2. FTP-Zugriff auf die entsprechenden User einschränken. Dazu wird die vsftp.conf um folgende Einträge ergänzt:
    userlist_deny=NO
    userlist_enable=YES
    userlist_file=/etc/vsftpd.user_list
    

    Das entsprechende /etc/vsftpd.user_list File enthält nur die Benutzernamen der erlaubten FTP-Accounts (einer pro Zeile)

  3. chroot Umgebung für diese User aktivieren. In vsftp.conf wird folgende Zeile hinzugefügt.
    chroot_local_user=YES
    

    Das alleine reicht aber noch nicht aus, zudem muss in /etc/passwd das Heimatverzeichnis der Benutzer angepasst werden:

    ftpupload:x:1001:1001::/mnt/ftpbase/./ftpupload:/usr/sbin/nologin
    

    Das Umschreiben von /mnt/ftpbase/ftpupload auf /mnt/ftpbase/./ftpupload hat zur Folge, dass der User nicht aus dem Verzeichnis /mnt/ftpbase ausbrechen kann.

  4. Andere Zugänge für diese User deaktivieren. Im passwd Ausschnitt oben sieht man außerdem, dass als Shell /usr/sbin/nologin angegeben wird. Dadurch wird der Zugang via SSH für den account gesperrt. Hier gibt es nur einen Stolperstein: Ubuntu führt nologin nicht in der Datei /etc/shells auf. Dadurch funktioniert der Zugriff auch via FTP nicht und vsftp meldet, dass das Passwort falsch ist 🙁
    root@sidi:~# cat /etc/shells 
    # /etc/shells: valid login shells
    /bin/sh
    /bin/dash
    /bin/bash
    /bin/rbash
    /usr/bin/screen
    /usr/sbin/nologin
    
  5. und schließlich läuft auch noch fail2ban mit folgendem Eintrag:
    [vsftpd]
    enabled  = true
    port     = ftp,ftp-data,ftps,ftps-data
    filter   = vsftpd
    logpath  = /var/log/vsftpd.log
    maxretry = 6
    

Cordova/Phonegap Plugin als Android Service

Das Problem

App-Entwicklung mit Cordova funktioniert großartig. Beim Aufzeichnen von GPS Tracks stößt man aber an die Grenzen der Technologie, da es keine Möglichkeit gibt, einen Prozess zu starten, der nicht automatisch vom System beendet werden kann. Dazu benötigt man ein Plugin…

wpid-screenshot_2014-07-30-10-13-30.png

Ein Android Service

Mit ein wenig Java Kenntnissen ist es einfach einen Hintergrund-Dienst (Service) als Plugin für Cordova zu realisieren. Android kann den Service zwar auch beenden, startet ihn aber sofort wieder, wodurch das GPS tracking fortgesetzt wird. Einen Haken gibt es bei der Sache allerdings: Die Kommunikation zwischen Plugin und der Web-app ist schwierig. Die vorhandene Schnittstelle über den Callback-Context funkioniert (nach meinem Wissen) nicht, da der Service abgekoppelt läuft.

GPS Websocket Server

Moderne Browser unterstützen das effiziente Websocket Protokoll nativ. Der GPS-Service startet einen einfachen Websocket-Server auf einem hohen Port und die Web-app verbindet und wartet auf Messages:

    var ws = new WebSocket('ws://localhost:8887/snowhow');
    ws.onmessage = function (evt) {
      var data;
      try {
        data = JSON.parse(evt.data);
      } catch (e) {
        console.log("illegal json data via websocket", evt.data);
      }
      handleWSResopnse(data);
    }

Doch damit nicht genug: Über den Websocket Server kann das HTML-UI auch die Nachricht zum beenden des Service schicken. Natürlich steigt durch den Server und den Websocket Client der Stromverbrauch etwas, erste Tests haben aber keine dramatischen Änderungen gezeigt.