ES STELLT SICH VOR...

Johannes Truschnigg jun.

Bild von Johannes Truschnigg, Februar 2015

Zur Suche ▼Zum Archiv ▼

Eintrag von 2015-12-07

OpenWrt 15.05 "Chaos Calmer": A1 (AON / Telekom Austria) ADSL-Setup mit PPTP


In Österreich ist seit jeher alles sehr kompliziert - ist man hierzulande doch als Kunde des größten ADSL-Providers A1 (bzw. AON, vormals auch bekannt als Telekom Austria) an das archaische Point-to-Point Tunneling Protocol zur Herstellung der Internetverbindung gebunden. Andernorts verwendet man zumeist das modernere und für diesen Zweck viel tauglichere Point-to-Point Protocol over Ethernet (PPPoE). Sonst hat man es stellenweise noch in veralteten VPNs mit PPTP zu tun: Die damit verwendbaren Authentifizierungsmechanismen gelten heute aber als fundamental unsicher, und man sollte besser vorgestern als gestern auf eine modernere, sichere Technologie gewechselt haben.

Nicht zuletzt aus diesem Grund hat das OpenWrt-Projekt schon vor geraumer Zeit die Unterstützung für PPTP aus dem Standard-Installationsumfang entfernt - auf der Ziel-Hardware herrscht in vielen Fällen ohnehin ziemlicher Speicherplatzmangel, und den will man nicht durch das Mitschleppen vernünftigerweise nicht mehr zu verwendender Altlasten weiter verschlimmern. Für Kunden von "A1 Festnetz-Internet" (bzw. vormals jet2web, einstmals auch bekannt als AON Speed - wie eben auch immer das ADSL-Produkt der Telekom Austria A1 zur Stunde gerade nach intensivsten Strategiebesprechungen in der Marketingabteilung benamst ist) ist das freilich lästig: Sie müssen, aufgrund der technischen Unbeweglichkeit ihres ISPs, den gesamten PPTP-Support-Stack nachinstallieren, wenn sie hinter ihrem "Single User"-DSL-Modem ein vernünftiges Internetworking Device betreiben wollen.

Vor diese undankbare Aufgabe war ich kürzlich bei meinen Großeltern gestellt: Ein neuer Router bzw. Wireless Access-Point sollte dort eine schwer veraltete Buffalo Airstation G ersetzen. Viele Jahre hatte sie klag- und problemlos mit OpenWrt 0.9 "White Russian" (einer meiner erklärten Lieblingscocktails übrigens!) dort nach IEEE 802.11b/g in die nordsteirische Pampa gefunkt, um so u. a. einem Google Nexus 10, sowie den Wifi-tauglichen Geräten etwaiger Besucher, den Weg ins Internet zu weisen. Freilich ohne irgendwelchen modernen Firlefanz wie WPA2, oder Unterstützung des 5GHz-Bandes. Übernommen hat diese Aufgabe nun ein TP-Link TL-WDR4300 mit OpenWrt 15.05 "Chaos Calmer", der seinem Vorgängergerät freilich in allen technischen Belangen um Lichtjahre voraus ist.

Um nun OpenWrt 15.05 nach der Installation zur Zusammenarbeit mit ADSL von A1 zu überreden, muss man recht tief in der Trickkiste kramen. Da die notwendigen Schritte nicht wirklich dokumentiert zu sein scheinen, will ich sie hier (auch als Gedächtnisstütze für mich selbst, weil ich bestimmt noch öfters in die missliche Lage geraten werde, dieses Setup herstellen zu müssen) festhalten. Zuerst benötigt man mindestens die folgenden Pakete aus den OpenWrt-Repositories, inkl. aller Abhängigkeiten:

Freilich sollte man all diese Pakete besorgen, bevor man nur noch mit der OpenWrt-Basisinstallation auf dem Gerät dasteht - ohne sie bleibt die Internetverbindung über PPTP-ADSL nämlich düster. Im Zweifel, und um nichts vergessen zu können, mirrore ich mir deshalb vor der Installation eines Images stets den gesamten Paketbaum des zu installierenden OpenWrt-Releases (und zwar für die richtige Binärarchitektur! Mein WDR4300 benötigt jene für die Atheros 71xx-SoC-Familie.) auf ein System, das via Ethernet mit dem Router verbunden werden kann. Nach dem Flashen und initialen Setup des Routers kopiere ich dann alle so heruntergeladenen und benötigten Pakete via scp ins Dateisystem des OpenWrt-Geräts, logge mich via ssh dort ein, und installiere die Pakete via opkg nach.

Um danach einen A1-PPTP-WAN-Uplink in OpenWrt zu konfigurieren, ist noch etwas Konfigurations-Akrobatik notwendig: Zuerst braucht das von OpenWrt in der Default-Config mitgebrachte WAN-Interface eine statische IPv4-Konfiguration. Hier ist etwas Vorsicht geboten, denn je nach Router-Hardware (und damit OpenWrt-Installations-Image) kann das leicht unterschiedlich aussehen, vor allem im Hinblick auf den Interface-Namen. Beim TP-Link TL-WDR4300 liest sich die (bereits für den A1-Zugang angepasste) WAN-Konfiguration in /etc/config/network wie folgt:

config interface 'wan'
	option _orig_ifname 'eth1'
	option _orig_bridge 'false'
	option ifname 'eth1'
	option proto 'static'
	option ipaddr '10.0.0.140'
	option netmask '255.255.255.0'
	option gateway '10.0.0.138'

Ist das erledigt, braucht man noch ein zusätzliches, virtuelles Interface für das PPTP, ebenfalls in /etc/config/network. Nach dem Ersetzen der Dummy-Werte ("A1-ADSL-USERNAME", "A1-ADSL-PASSWORT") durch die tatsächlichen Benutzeridentifikationsdaten für die A1-ADSL-Verbindung kann dieser Abschnitt dann 1:1 wie hier übernommen werden:

config interface 'A1'
	option proto 'pptp'
	option server '10.0.0.138'
	option username 'A1-ADSL-USERNAME'
	option password 'A1-ADSL-PASSWORT'
	option peerdns '0'
	option dns '85.214.20.141 213.73.91.35 194.150.168.168'
	option keepalive '10 6'
	option mtu '1460'

Nach der Installation aller Laufzeitkomponenten und dem Einrichten der Interfaces muss man noch die Konfiguration des PPTP-Clients so zurechtbiegen, dass die von A1 benötigten Verbindungsparameter Anwendung finden. Dafür ersetzt man den gesamten Inhalt der Datei /etc/ppp/options.pptp mit dem folgenden:

noipdefault
noauth
nobsdcomp
nodeflate
idle 0
maxfail 0

Danach rebootet man das Gerät einfach. Ist es wieder hochgefahren, verbindet man sich wieder via ssh zu einer shell darauf, und wirft ein Auge auf den Output von logread - wenn sich der PPTP-Client hier nicht lautstark und wiederholt über Authentifizierungsprobleme beschwert, hat man alles richtig gemacht, und der WAN-Uplink wird nach wenigen Sekunden Initialisierungsarbeit den Internetzugang freigeben.

direkter Link ▲

Eintrag von 2015-10-26

2-Faktor-Authentifizierung via libpam_oath am Beispiel OpenVPN


In vielen Unternehmen stellt das lokale Netzwerk nach wie vor die zentrale Trust Boundary zwischen vertrauenswürdigem und grundsätzlich misstrautem Traffic dar. Virtuelle Private Netzwerke (VPN) sind deshalb vielerorts unverzichtbar, möchten heute doch Mitarbeiter auch von unterwegs und zu Hause aus auf schützenswerte Ressourcen und Dienste zugreifen können. OpenVPN ist eine sowohl freie als auch beliebte und erprobte Lösung, um ein solches VPN zu errichten. Die Authentifizierungsmethodik, die über die Berechtigung an der Teilnahme am VPN entscheidet, ist dabei normalerweise X.509/TLS: Ein RSA-Schlüsselpaar aus Private (bzw. Secret) Key und Public Key, sowie diverser anderer Metadaten, die von einer Certificate Authority als vertrauenswürdig eingestuft und deshalb kryptographisch signiert werden.

Das ist durchaus gut so, und soll sich aus heutiger Sicht auf die mathematischen Fundamente, die dieses Verfahren ermöglichen, auch nicht so bald ändern - aber es reicht doch oft in der Realität nicht aus, um dieses Paar aus Zertifikat und Schlüssel als einzigen Schranken zwischen potenziellen Tunichtguten und vertraulichen Datenschätzen zu positionieren: Findet sich das Endgerät mit den Schlüsseldaten (über deren etwaige lokale, symmetrische Verschlüsselung die CA ja nicht entscheiden kann) durch Diebstahl oder eine Unachtsamkeit in die falschen Händen wieder, ist die Kompromittierung der mithilfe des VPN abgeschotteten Systeme zu befürchten.

Solche Überlegungen sind der Grund, warum man für immer mehr kritische Systeme auf Authentifizierungsschemata setzt, die mehr als einen validierenden Faktor in Betracht ziehen. Solche Faktoren können verschiedenster Natur sein: Ein Tupel aus Username und Passwort als einen Faktor hat wohl jeder vernetzte Bürger schon einmal zur Anmeldung an irgendeinem Dienst wie bspw. einem E-Mail-Konto schon verwendet. Vom Online-Banking z. B. kennt man das SMS-TAN-Verfahren, wobei ein einmalig gültiges Passwort, das genau eine bestimmte Transaktion genehmigt, auf ein mit dem Benutzer in Verbindung gebrachtes Mobiltelefon übertragen wird. In wieder anderen Szenarien kommt z. B. die Kombination aus einer Zutrittskarte und einem Fingerabdruckleser zum Zug, um den Zutritt zu kritischer Infrastruktur wie Rechenzentren oder Steuerzentralen zu regeln. Nach oben gibt es hier, ähnlich wie bei der Passwortkomplexität, keine wirkliche Grenzen - es ist durchaus mach- und vorstellbar, fünf Faktoren bzw. Merkmale zu prüfen, und erst beim Überwinden all dieser Hürden den Zugriff bzw. Zutritt zu erlauben. Entscheidend ist es, ein vernünftiges Gleichgewicht aus Sicherheitsvorkehrungen und Benutzungskomfort zu finden, mit dem alle am System Beteiligten zurecht kommen, und das sich durch die organisatorisch Zuständigen ruhigen Gewissens verantworten lässt.

OATH und TOTP - Einwegpasswörter komfortabel und sicher

Auch im OpenVPN-Projekt hat man früh den Bedarf an einem zweiten authentifizierenden Faktor für VPN-Clients erkannt, und sich für eine Implementierung entschieden, die durch ihre Flexibilität und Verbreitung überzeugt: PAM (Pluggable Authentication Modules). PAM ist ein System aus Shared Libraries, die einzelne Rollen zur Authentifizierung und Authorisierung abwickeln können. Unter modernen GNU/Linux-Distributionen ist z. B. das pam_unix-Modul beim Login auf der Konsole dafür verantwortlich, Credentials mit jenen, die in /etc/shadow hinterlegt wurden, beim Anmeldeversuch abzugleichen. Andere Module erlauben es z. B., den Login an das erfolgreiche Entsperren eines zuvor hinterlegten, privaten SSH-Schlüssels zu binden - darüber habe ich vor einigen Jahren schon einmal geschrieben.

Ein anderes Modul, pam_oath, greift die Konzepte aus der Open Authentication-Initiative (OATH) auf und erlaubt das Einbinden von OTP (One-Time Passwords) in den Authenfizierungsstack PAM-kompatibler Services. Um OATH-OTP einsetzen zu können, benötigt man ein Gerät bzw. Software, die die dafür notwendigen Standards implementieren. Wer bereits ein Android- oder iOS-Gerät besitzt, kann auf quelloffene Software für diesen Zweck zurückgreifen. Der YubiKey ist ein beliebter und kostengünstiger Hardware-Token, der stattdessen ebenfalls eingesetzt werden könnte. OATH-OTPs basieren auf einem geteilten Geheimnis zwischen Client und Server - dem Secret. Dies ist eine base32-kodierte, möglichst zufällige Zeichenkette, die als Seed für pseudozufällige Berechnungen benutzt wird. Dadurch ergeben sich zeitlich begrenzt gültige, kurze und rein numerische Passwörter/PINs, die ohne weitere Kommunikation zwischen Client und Server übereinstimmen (müssen). Damit dies funktionieren kann, braucht es außer des initial ausgetauschten Secret auch noch eine weitere Komponente: Entweder den aktuellen Zeitstempel (dann verwendet man TOTP - Time-based OTP), oder die Anzahl der bisher erfolgten Authentifizierungen (dann verwendet man HOTP - HMAC-based OTP). Beide haben gewisse Vor- und Nachteile; TOTP ist auf Geräten, die über eine Echtzeituhr verfügen, sehr einfach umzusetzen. Auf Yubikey-ähnlichen Geräten und Tokens, die selbst ohne Stromversorgung auskommen, eignet sich HOTP aus naheliegenden Gründen besser. Ich werde mich hier auf TOTP beschränken, da die betrachteten Software-Lösungen für Mobiltelefone sowie auch das oathtool problemlos mit diesem Schema arbeiten.

Voraussetzungen am Server

Um nun eine existierende OpenVPN-Installation, die bereits auf die Gültigkeit von X.509-Zertifikaten zur Authorisierung setzt, um TOTP zu erweitern, braucht es auf dem Server drei Schritte:

  1. Installation der OATH-Software und des OATH-PAM-Moduls
  2. OpenVPN muss zur Nutzung von PAM konfiguriert werden
  3. PAM muss für OpenVPN TOTP akzeptieren

Für die ersten beiden Punkt genügt es, (unter Debian) die Pakete oathtool und libpam-oath zu installieren, und das von OpenVPN mitgebrachte auth-pam-Plugin zu aktivieren. Letzteres erledigt man mit einem Eintrag in der OpenVPN-Server-Konfigurationsdatei:

plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so "openvpn-otp"

Das erste Argument zur plugin-Direktive ist dabei der absolute Pfad zum Shared Object, das OpenVPN PAM-fähig macht - auf anderen Distributionen könnte dieser anders lauten; im Zweifel weiß es der Paketmanager. Das zweite Argument ist interessanter: Es ist der Name des PAM-Service, das für diese OpenVPN-Server-Instanz die authoritative Konfiguration ergeben wird. Dieses PAM-Service - bzw. seine Konfiguration - gibt es auf dem System so noch nicht, und sie muss erst erstellt werden. Unter Debian (und wohl auch auf allen anderen Distributionen, die Linux-PAM verwenden) legt man dazu eine neue Datei in /etc/pam.d/ an, die den Namen des zu konstituierenden Service trägt - hier also /etc/pam.d/openvpn-otp. Das folgende Listing konfiguriert einen PAM-Stack der sicherstellt dass der vom OpenVPN-Client an den Server übermittelte Username in einer Datei namens /etc/openvpn/users_whitelist vorkommt, und dass das für diesen User übermittelte One-Time Password gegen die in /etc/openvpn/users_oath hinterlegten OATH-Daten valide ist.

# Check if the given username is in the list of allowed names
auth requisite pam_listfile.so file=/etc/openvpn/users_whitelist item=user sense=allow onerr=fail
# Check for users' time-based One-Time Password from their OATH token device/app
auth requisite pam_oath.so usersfile=/etc/openvpn/users_oath window=10 digits=6

# Permit whitelisted usernames - if this is missing, getpwnam() will fail for non-system users
account sufficient pam_listfile.so file=/etc/openvpn/users_whitelist item=user sense=allow onerr=fail

Sobald und so lange diese beiden Bedingungen erfüllt sind, wird das OpenVPN-PAM-Plugin keine Einwände gegen die Anmeldung eines Clients haben, welcher auch ein valides X.509-Zertifikat präesentieren konnte. Um nun ein Secret zu errechnen, das man im OATH-OTP-Generator seiner Wahl, sowie im im OATH-Usersfile hinterlegen kann, benutzt man `oathtool`. Durch einen Aufruf wie den folgenden erhält man ein kyryptographisch ausreichend starkes Secret in base32-Notation (wie ihn die meisten Software-Token-Generatoren wie FreeOTP erwarten), sowie in hexadezimaler Darstellung (wie es pam_oath benötigt):

$ oathtool -v --totp $(openssl rand -hex 15)
Hex secret: b398741f71c6025eb9476be1d339ca
Base32 secret: WOMHIH3RYYBF5OKHNPQ5GOOK
[...]

OTP-Generator-Setup und -Überprüfung

Das base32-Secret füttert man nun in die OTP-App auf dem Smartphone, während man das Hex-Secret - mit dem gewünschten Usernamen verknüpft - in /etc/openvpn/users_oath hinterlegt:

#PROTO		USER	-	SECRET				COUNTER	LASTOTP	TS
HOTP/T30	vpnuser	-	b398741f71c6025eb9476be1d339ca

Der Inhalt dieser Datei muss als vertraulich eingestuft werden; es empfiehlt sich, die Ownership des Inodes auf root:root, und die Permissions auf 0600 zu setzen. Wichtig ist auch, dass die Datei nicht immutable gemacht wird, da pam_oath.so hierin Accounting betreiben wird. Weiters darf die Datei kein Symlink sein, da die Inode beim Accouting durch PAM unlinked, und wieder neu geschrieben wird. Das in hexadezimaler Notation anzuschreibende Secret darf eine gewisse (undokumentierte) Länge nicht überschreiten, da sonst ein von pam_oath allozierter Buffer nicht ausreicht, um es zur Validierung zu benutzen, was in stillschweigend fehlschlagenden (pam_oath schreibt Debug-Meldungen auf stdout, welche durch das OpenVPN-PAM-Plugin verschluckt werden - zum Debuggen von pam_oath.so bindet man es am besten in den PAM-Stack fuer `su` ein, das dieses lästige Problem nicht teilt) Logins mündet. Man muss hier also eine Menge beachten, um dieses filigrane Zusammenspiel nicht empfindlich zu stören!

Weiters ist es für die Beispielkonfiguration noch notwendig, den Usernamen in die Whitelist-Datei einzufügen - dazu öffnet man /etc/openvpn/users_whitelist mit einem Texteditor, und setzt die Zeichenfolge vpnuser in eine eigene, neue Zeile - fertig.

Hat man nun in Folge das Secret so im OTP-Generator (siehe unten für eine Liste von Apps, die ich persönlich als tauglich erachte) untergebracht, dass dieser sechsstellige Zifferncodes, die sich im 30-Sekunden-Takt erneuern/verändern, ausspuckt, kann man mithilfe von `oathtool` die Gegenprobe machen, ob der am Mobiltelefon errechnete Wert zu dem, den der OATH-Stack am Server errechnet, passt:

$ oathtool --totp b398741f71c6025eb9476be1d339ca
849562
$ oathtool --totp -b WOMHIH3RYYBF5OKHNPQ5GOOK
849562

Stimmt diese Ausgabe mit der Anzeige am Software-Token innerhalb der selben halben Minute überein, hat man alles richtig gemacht, und die App sollte künftig korrekte OTPs errechnen. Dabei ist (für TOTP) selbstverständlich strikt auf sychronisierte Uhrzeiten auf allen teilnehmenden Systemen zu achten - mithilfe des window-Parameters des pam_oath-Moduls kann man bei unzuverlässigen lokalen Zeitnehmern etwas gegensteuern - die Zahl, die man diesem Parameter als Argument übergibt, bestimmt, wie viele Zeitschritte (bei TOTP/30 sind das 30 Sekunden pro Schritt) das angegebene OTP realtiv zum am Server errechneten in der Zukunft bzw. Vergangenheit liegen darf.

Anpassung der OpenVPN-Client-Konfiguration

Jetzt gilt es noch, den OpenVPN-Client dazu zu bringen, eine Eingabemöglichkeit für Usernamen und OTP bereitzustellen. Dazu fügt man die beiden folgenden Zeilen an die existierende Konfigurationsdatei des Clients an:

auth-nocache
auth-user-pass

Die erste Zeile veranlasst den OpenVPN-Client dazu, die erfragte Passphrase nicht zu cachen (was im Falle von One-Time Passwords ohnehin absurd wäre, und immer eine sinnlose Warnung im Client-Log verursacht), während die zweite Zeile vor dem Aufbau der Verbindung zum Server nach Username und Passwort fragt, welche nach dem TLS-Handshake und der erfolgten Zertifikatsvalidierung verschlüsselt an den OpenVPN-Server übertragen, und dort von dessen PAM-Stack gegen die Usernamen-Whitelist und die OATH-TOTP-Daten validiert werden.

pam_oath in Aktion

Startet man nun den OpenVPN-Server neu, und versucht sich mit dem zusätzlich wie eben beschrieben konfigurierten Client anzumelden, kriegt man am Client zunachst die Frage nach Usernamen und Passwort - diese ergeben sich aus den in der user_whitelist und user_oath hinterlegten Daten, und dem aus dem OATH-Secret errechneten OTP, welches von der Smartphone-App angezeigt wird. Ist die Anmeldung mittels OTP einmal erfolgt, kann man in /etc/openvpn/users_oath das pam_oath-Modul beim Arbeiten beobachten. Hier könnte es nach einigen Logins in der Zeile für den Account vpnuser z. B. so aussehen:

#PROTO		USER	-	SECRET				COUNTER	LASTOTP	TS
HOTP/T30	vpnuser	-	b398741f71c6025eb9476be1d339ca	7	865748	2015-10-26T17:33:52L

Anhang: Empfehlenswerte OTP-Generator-Apps:

direkter Link ▲

Eintrag von 2015-07-17

GnuPG-Schlüsselrotation


Beinahe 10 Jahre Gültigkeit sind für einen 1024bit DSA-Key mehr als genug - seit 13. Juli 2015 habe ich deshalb ein neues GnuPG-RSA-Schlüsselpaar mit dem Fingerprint 1AEF 4886 423E EFF6 8A2D 4585 F795 B78C CB18 7CB5. Ich habe den neuen Key mit meinem alten signiert, womit der neue Schlüssel für all jene vertrauenswürdig sein sollte, die schon den alten mit mir persönlich in Verbindung gebracht hatten. Der Schlüssel ist seit gestern auf den übichen verdächtigen Keyservern verfügbar, und kann auch direkt über die Kontakt-Seite meiner Website bezogen werden.

direkter Link ▲

Eintrag von 2014-09-15

Archiv-Helferprogramm für PostgreSQL WAL-Segmente


Wer einen PostgreSQL-Cluster betreibt und Hochverfügbarkeit (oder zumindest kurze Recovery-Zeiten im Falle eines Crashes der Datenbank...) anstrebt, dem ist das gewissenhafte Archivieren der Segmente des WAL (Write Ahead Log) des Clusters ans Herz gelegt. Das WAL ist eine theoretisch endlose Folge aufsteigend nummerierter Datenblöcke, deren fixe Größe beim Kompilieren von PostgreSQL festgelegt wird - in der Standardkonfiguration beträgt diese 16 MByte. Diese Blöcke beinhalten eine Art Liste geplanter Änderungen in einer Form, die lückenlose Anwendbarkeit auf den Ist-Stand der Daten des Clusters garantiert. Sollte der Cluster vor dem erfolgreichen Abarbeiten eines ganzen WAL-Segments abstuerzen oder sonstwie ungeplant unterbrochen worden sein, so ist sichergestellt, dass beim nächsten Start des Clusters vom Beginn des letzten erfolgreich abgearbeiteten WAL-Segments aus fortgesetzt werden kann. In der PostgreSQL-Dokumentation findet man einen detaillierteren Abriss über das WAL.

Das WAL ermöglicht es aber darüber hinaus auch, die Änderungen, die ein Postgres-Cluster auf sich selbst anzuwenden plant, auf einem anderen Cluster (mit demselben Datenbestand bis zu diesem WAL-Segment) ebenfalls durchzuführen. Dazu bedient man sich einer Methode namens "Log Shipping", wobei komplette WAL-Segmente (in der Praxis 16 MByte große Binärdateien) vom Master-Cluster zu (mindestens) einem Slave-Cluster geschickt, d. h. irgendwie in dessen Dateisystem kopiert oder verschoben, werden. Dies ist auch dann noch relevant, wenn man sich der moderneren Replikationsmethode "Streaming Replication", wobei Änderungen am WAL-Strom in feinerer Abstufung als in ganzen WAL-Segmenten über eine persistente TCP-Verbindung zwischen Master und Slave(s) mehr oder weniger in Echtzeit repliziert werden, bedient. Hier benötigt man ein lückenloses WAL-Archiv, aus dem der Slave Segmente lesen kann, falls die replizierende TCP-Verbindung aus irgendwelchen Gründen über eine längere Zeit abreißt - konkret: wenn das erste benötigte WAL-Segment am Slave nicht mehr unter den durch den Master auch ohne explizites WAL-Archiv vorgehaltenen, durch wal_keep_segments festgelegten Segmenten ist.

Als Zielort für ein WAL-Archiv empfiehlt sich bspw. ein (Netzwerk-)Dateisystem, das sowohl von Master als auch Slave gelesen und geschrieben werden kann - der Master muss darauf WAL-Segmente archivieren können, und der Slave sollte applizierte und somit definitiv nicht mehr gebrauchte Segmente aus dem Dateisystem löschen können. Wenn dieses vollläuft, führt das zu sehr unschönen Konsequenzen für den Master-Cluster, sobald das Dateisystem seine Kapazitäten erschöpft, das das pg_xlog-Verzeichnis des Clusters beinhaltet. Ein NFS-Export auf dem Client oder dem Master ist für das Archiv also durchaus tauglich. Wie aber kommen nun die WAL-Segmente vom Master auf den Slave, bzw. in das geteilte Archiv-Verzeichnis? Hierzu dient das archive_command am Master - ein Aufruf via system(3), der das Kopieren des WAL-Segments an den Zielort übernehmen soll.

Die PostgreSQL-Dokumentation beschreibt ausführlich, welche Anforderungen dieses Kommando erfüllen muss:

  1. Wenn das Segment erfolgreich kopiert wurde, muss es mit Status 0 enden.
  2. In allen anderen Fällen darf es nicht mit Status 0 enden.
  3. Es darf keine schon existierenden Dateien (WAL-Segmente) überschreiben.

In der Dokumentation wird hierfür ein Shellscript vorgeschlagen, das um eine Limitation von GNU cp herumarbeitet - dieses ermöglicht es nämlich nicht, den Descriptor der Ziel-Datei mit den Flags O_CREAT und O_EXCL zu öffnen, was die garantierte Erfüllung der dritten Bedingung streng genommen unmöglich macht. Man behilft sich in der offiziellen Dokumentation mit einem zweiten Schritt vor dem tatsächlichen Kopieren: durch einen Aufruf des Programms (bzw. Builtins) `test` soll sichergestellt werden, dass die Zieldatei noch nicht existiert:

archive_command = 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'

Hierbei entsteht eine klassische Race Condition, die effektiv nur durch die zuvor erwähnten Flags für den open(2)-Syscall verhindert werden kann. Auch aus diesem Grund habe ich mich dazu entschlossen, ein möglichst minimales C-Programm zu schreiben, das diese Aufgabe (besser) erfüllen kann: pg_archive_wal_segment.

Hat man es kompiliert und an einem beliebigen Pfad im Dateisystem (das tunlichst nicht mit "noexec" gemountet sein sollte :-)) abgelegt - ich nehme /usr/local/bin/ an -, konfiguriert man den PostgreSQL-Master-Cluster einfach fortan so:

archive_command = '/usr/local/bin/pg_archive_wal_segment %p /mnt/server/archivedir/%f'

Das Programm beschränkt sich ganz absichtlich darauf, die Quelldatei (das erste Argument) an den angegebenen Pfadnamen (das zweite Argument) zu kopieren. Das gelingt durch read(2)- und write(2)-syscalls mit einem 128 KByte großen Buffer dazwischen. Im Fehlerfall bzw. wenn das Programm durch ein Signal unterbrochen wird, wird die Zieldatei wieder unlink(2)t und ein Exit-Status ungleich 0 zurückgegeben, worüber pg_archive_wal_segment auf stderr Bescheid gibt. In so einem Fall versucht der Master-Cluster nach kurzer Zeit einen erneuten Aufruf des archive_commands - so lange, bis der Kopiervorgang einmal erfolgreich verläuft. Hat das Kopieren geklappt, vermeldet das Programm den Erfolg der Operation auf stdout und retourniert 0.

direkter Link ▲

Eintrag von 2012-01-01

Wie der Grinch das Botnet gestohlen hat


Anmerkung: Aus Sicherheitsgründen sah ich mich dazu veranlasst, einige Namen und Bezeichner in diesem Text zu zensieren, da es mit Kenntnis dieser möglich ist, unerlaubt Kontrolle über einige fremde Computersysteme zu erlangen. Ich bitte um Verständnis, dass ich diese Daten deshalb nicht öffentlich machen kann.

Der Advent ist schon lange keine stille Zeit mehr. Abseits vom normalen Weihnachtsstress bietet sich rund um die Feiertage auch ein für Cracker attraktives Zeitfenster, um in diesen Tagen weniger genau als üblich bewachte Systeme zu entern und unter ihre Kontrolle zu bringen. Mit so einem Zwischenfall hatte ich kürzlich zu kämpfen, als das IDS eines Webservers verdächtige Aktivitäten in der Nacht vom 22. auf den 23. Dezember meldete.

Ein VHost des installierten Apache-Webservers wuchs des Nachts um einige unschuldig aussehende Dateien im Cache-Directory eines populären CMS - einem Ort im Dateisystem, auf das der Webserver zwingend Schreibzugriff benötigte. Ich musste gar nicht tief graben um zu wissen, womit ich zu tun hatte: die scheinbar unschuldige Textdatei mit dem Namen "os2.txt" demaskierte sich rasch als ein in Perl zusammengehackter IRC-Bot, der über einen im Script kodierten C&C (Command and Control) Channel eine Vielzahl Kommandos entgegennehmen konnte, um auf diese Geheiße hin allerhand unangenehme Aktionen zu starten. Die Palette reichte vom Auffinden von Schwachstellen in Webseiten (Google-Dorks) über Bulk-Mailing via MTA des infizierten Systems und einen etwas kruden TCP-Portscanner bis hin zum Up- und Download von Files via XDCC bzw. HTTP. Ein wahres Schweizer Offiziersmesser für übelwollende Scriptkiddies also.

Einfaches Löschen der unerwünschten Datei ist in so einem Fall niemals anzuraten - man muss analysieren, was man sich eingefangen hat, um dann entsprechende Gegenmaßnahmen planen und umsetzen zu können. Ich beschloss also, dem Möchtegern-Einbrecher ein bisschen nachzusteigen, und mir anzusehen, was er denn so alles treibt oder noch zu treiben plant. Da das Schadprogramm nicht durch primitive Behelfe wie base64-Encoding des Quellcodes obfuskiert war, konnte ich ohne lästige Umwege Einblick in seine Funktionsweise nehmen. Schon in den ersten paar Zeilen der Datei fanden sich einige vielversprechend aussehende Skalardefinitionen:

1 #!/usr/bin/perl [...] 18 my $fakeproc = "/usr/sbin/apache2 -k start"; 19 my $ircserver = "irc.plasa.com"; 20 my $ircport = "6660"; 21 my $nickname = "user-redacted[".int(rand(100))."]"; [...] 23 my $channel = "#channel-redacted"; 24 my $admin = "maliciousUser"; [...]

Ohne den folgenden Code zu lesen konnte ich daraus mit einiger Sicherheit die folgenden Schlüsse ziehen:

  1. Es handelt sich um einen IRC-Bot.
  2. Die Intention des Crackers ist es, mehrere solcher Bots parallel zu installieren und zu kontrollieren - deswegen wird der Nickname des Bots (leidlich) zufällig generiert.
  3. Der Bot scheint zur "Authentifizierung" seines Meisters lediglich dessen IRC-Nickname zu prüfen.

Einer so indirekten wie herzlichen Einladung in einen Botnet-CNC-Channel konnte ich natürlich nicht widerstehen, und leitete meinen ohnehin laufenden IRC-Client gleich in Richtung irc.plasa.com, Teil eines indonesischen IRC-Netzwerkes. Das ergab Sinn, denn der HTTP-Request, den ich laut httpd Access-Log für das Einpflanzen der os2.txt-Datei als verantwortlich identifizieren konnte, erging laut WHOIS ebenfalls von einer Quell-IP-Adresse aus dem Netz der "PT TELKOM INDONESIA", direkt aus dem schönen Jakarta.

irc.plasa.com erwies sich nach dem Verbinden als Teil der "AllNetwork"-Föderation. Dass ich dort normalerweise nichts zu schaffen habe, hinderte mich nicht daran, ein bisschen herumzustöbern. Zuerst wollte ich wissen, ob mein alter Bekannter "maliciousUser" - seines Zeichens passionierter Botnet-Züchter und Drohnen-Meister - für ein persönliches Pläuschchen zu haben wäre:

/WHOIS maliciousUser * maliciousUser: No such nick/channel

Schade, niemand zum Plaudern da. Wenn schon der Meister nicht anwesend ist, dann doch vielleicht eine seiner fleißigen Drohnen? Und um vor solchen einen guten und vertrauenswürdigen Eindruck zu machen, hilft es doch bestimmt, wenn man eine ihnen vertraute Identität annimmt...

/NICK maliciousUser * You are now known as maliciousUser -NickServ- This nickname is registered. Please choose a different nickname, or identify via /NickServ identify <password>.

maliciousUser legte also Wert darauf, dass nur er selbst seinen Nickname tragen würde - deshalb hatte er ihn sich via NickServ reservieren lassen. Das alleine hält niemanden im IRC davon ab, die Identität (bzw. den Nickname) eines beliebigen Users anzunehmen, der momentan nicht online ist. Erst wenn der rechtmäßige "Besitzer" eines Nicknames diesen annehmen möchte und dann feststellt, dass ihn jemand anders trägt, kann er ggf. via Nachricht an NickServ unter Angabe seines Passwortes einen Kick des Identitätsdiebes veranlassen, und daraufhin selbst den so freigewordenen Nickname übernehmen. Der gute maliciousUser war zum Zeitpunkt meiner Nachfroschungen nicht verbunden, und konnte somit nicht einmal hilflos zusehen, als ich die Kontrolle über seine Drohnen übernahm...

/JOIN #channel-redacted /NAMES #channel-redacted * Users on #channel-redacted: maliciousUser user-redacted[93]

Nun, das war zugegeben etwas weniger, als ich mir erwartet hätte: Ein einzelner, etwas armselig und einsam wirkender Bot, verlassen sogar von seinem (vermuteten) Meister maliciousUser - wobei es diese Beziehung noch zu prüfen galt. Laut mir vorliegendem Sourcecode des Bots sollte es das Kommando "!reset" geben, welches, sofern der Bot-Admin es in den Channel oder direkt in ein Query für den Bot schreibt, einen totalen Neuaufbau seiner IRC-Verbindung bewirken sollte. Und tatsächlich:

<maliciousUser> !reset * user-redacted[93] has quit (Quit: Restarting...) <user-redacted[93]> Hi maliciousUser im here !!!

Mein neuer Freund user-redacted[93] ließ sich ohne Umschweife dazu überreden, sich neu zu verbinden. Mehr noch: nach dem erfolgreichen Reconnect war er sogar so freundlich, mir in gebrochenem Englisch mitzuteilen, dass er jetzt anwesend (und bereit zur Entgegennahme neuer Kommandos) wäre.

Nach ein bisschen Umgraben im Quellcode meiner Bot-Kopie beschloss ich, dass ich genug gesehen hatte - die "erweiterten" Features wie z. B. eine vollwertige Shell auf dem infizierten Host musste ich gar nicht mehr mit eigenen Augen sehen, um dem Treiben des echten maliciousUser einen Riegel vorschieben zu wollen. Was aber tut man in so einem Fall? Ohne mehr über die Identität des wahren maliciousUser zu wissen, gibt es nicht viel, das man proaktiv unternehmen könnte. Eine kurze Anfrage bei NickServ ergab, dass es den registrierten Nickname "maliciousUser" noch nicht einmal ganz zwei Tage gab, und dass dieser seine Registrierung noch nicht (via E-Mail-Validation - die E-Mail-Adresse dafür wird von NickServ in der Regel, und so auch hier, nicht herausgegeben) bestätigt hätte. Aus dem Banner des IRC-Servers vermochten mir meine nicht gerade famosen Indonesisch-Kenntnisse auszudeutschen, dass man im Channel #help Administratoren des Netzwerks kontaktieren könnte:

/NICK c0l0 /JOIN #help * Now talking on #help <c0l0> hi everyone <SkiN> yes <SkiN> may i help u ? <c0l0> I need to find out the email address of a person who registered with the nickserv service on your network. I do know the nickname of that person. is that possible? <SkiN> who ? <SkiN> can you speak in bahasa ? <c0l0> sorry, only English or German <c0l0> (and a little bit of Italian) <SkiN> hehe * SkiN just can speak bahasa and litle bit of english <c0l0> it's about the user with the registered nickname "maliciousUser" <SkiN> who the person you're looking for <SkiN> wait pls <c0l0> I will. thanks for your support :) <SkiN> but before i give you the information <SkiN> may i know who are you ? <c0l0> I'm a network administrator. I have reasons to believe that "maliciousUser" is responsible for an intrusion attempt into one of our web servers. <SkiN> maliciousUser has NOT COMPLETED registration verification <c0l0> yes, I noticed that from querying nickserv <SkiN> [16:53] -NickServ- Email : cristercorp@gmail.com (hidden) [...]

So weit, so gut. Wenige Sekunden bevor ich die E-Mail-Adresse durch den freundlichen Admin SkiN mitgeteilt bekommen hatte, passierte in #channel-redacted allerdings etwas Bemerkenswertes: user-redacted[93] schaltete sich selbst spontant ab! Das kann natürlich wirklich Zufall sein - oder aber der Hinweis darauf, dass zumindest SkiN (oder ein mit ihm bekannter User des Netzwerks) irgendwie im Zusammenhang mit dem Aufbau oder Betrieb des Botnetzes stehen könnte. Da sich dieses aber nunmehr auf die ungefährliche Größe von 0 Hosts reduziert hatte, beschloss ich, den Netzwerk-Admin aus user-redacted[93]s IP-Block eine kurze Mitteilung zukommen zu lassen: Dass dieser Host Teil eines Botnetzes wäre, und dass man den Administrator der Maschine zu einem etwas vorgezogenen Neujahrsputz anhalten sollte. Zwei Tage später kam die Antwort (sogar die eines echten Menschen - ich war beinahe gerührt!), dass man sich den Host angesehen hätte, und keine TCP-Verbindungen mehr zum fraglichen IRC-Server bestünden. Ich hoffe für die Betreiber, dass dies immer noch zutrifft.

Die verwundbare Wordpress-Installation auf dem Webserver wurde inzwischen gesundgepatcht, und weitere Einbrüche sind - nach gegenwärtigem Kenntnisstand - vorerst nicht zu erwarten. Das Botnetz, das maliciousUser hätte aufbauen wollen, existiert aber weiterhin, zumindest in Bruchteilen. Alle paar Tage schaue ich kurz vorbei, schlüpfe in den Wolfspelz von maliciousUser und kümmere mich um meine Schäfchen - ich schalte sie also ab und benachrichtige jene, die nachhaltig etwas gegen ihren Befall tun können. Vielleicht laufe ich so ja auch maliciousUser einmal über den Weg. Ich wäre gespannt, was er in unserem Pläuschchen zu erzählen hätte!

direkter Link ▲

© 2007-2016 Johannes Truschnigg | Design by Andreas Viklund (modified) | valid xhmtl & css

Created with free software