30.
Juli
So kann man auch Aufträge akquirieren…
von Christian Metzeler Kommentieren
Eine nette Geschichte am Rande: wir haben ja zum ersten Juli Jens eingestellt. Und der hat vorher bei einem städtischen Unternehmen gearbeitet. Da dieses Unternehmen aus verschiedenen politischen und geschäftlichen Gründen über einen langen Zeitraum hinweg seine Belegschaft reduziert, die Arbeit aber dennoch nicht unbedingt weniger wird, müssen sie nun hergehen und Aufträge auslagern. Und was liegt näher, als einen Ex-Mitarbeiter, der sich mit allem wichtigen auskennt, dafür zu gewinnen. Und so kann man also auch an Aufträge kommen: so können wir Jens erstmal für drei Wochen wieder an seinen alten Arbeitgeber verleihen
– was uns natürlich freut.
Natürlich ist die grundlegende Strategie, Projekte auszulagern, die ausgelagert werden können, eine völlig richtige. Zum einen leben wir ja davon und zum anderen sollte man fachlich anspruchsvolle Arbeit eben diejenigen tun lassen, welche sich durch tägliche Beschäftigung mit dem Thema eben besonders gut auskennen. Das nennt man Spezialisierung und als Unternehmen sollte man nicht annehmen, alles inhouse tun zu können. Witzig ist die Sache trotzdem.
27.
Juli
Checkliste für Websicherheit – Teil 2
von Christoph Ein Kommentar
Und schon sind wir mittem in zweiten Teil unserer Checkliste für Websicherheit:
Dateiuploads
Sind Dateiuploads in Größe und Anzahl beschränkt?
Dateiuploads machen mir persönlich immer wieder Bauchschmerzen:
- Erlaube ich zu große Dateien, ist mein Webspace irgendwann voll.
- Erlaube ich nur kleine, sind meine Benutzer unzufrieden.
- Wie viele Dateien soll ich pro Benutzer in einer bestimmten Zeit zulassen?
- Wo speichere ich die Dateien?
- Worauf kann ich mich verlassen? Dateinamen? Dateitypen?
Irgendwie wirken Dateiuploads wie große, klaffende Wunden in der Anwendung. Jeder Besucher sieht sie, jeder kann etwas reintun und wenn’s schmutzig war, stirbt die Anwendung. Kein schöner Zustand und auch ein wenig paradox, wenn wir alles andere mit Firewalls abriegeln.
Wichtige Leitsätze sollten hierbei sein:
- Wähle die Dateigröße dem Zweck angemessen. Niemand muss für sein Profilfoto ein 10 MB JPEG hochladen dürfen.
- Vertraue nicht darauf, dass die Datei einen sinnvollen Namen hat(te). Generiere selbst einen, beispielweise aus der User-ID (534.jpg), dem Benutzernamen (wenn dieser z.B. per Regex abgesichert ist) oder erzeuge einen Hash. Du willst definitiv nicht den Namen übernehmen, den dir der Benutzer geschickt hat.
- Schließe nie von der Dateiendung auf den Dateityp. Wenn möglich, vermeide es gänzlich, auf den Typ zu vertrauen und behandle alle Dateien als potentiell böse Binärdateien oder potentiell böse Scripts.
- Sei extrem vorsichtig, wenn du Archive erlaubst und diese entpackst.
- Directory Traversal (oh, mein Zip enthält eine Datei namens “../../../../../etc/passwd”) ist gefährlich. Im schlimmsten Fall überschreibt jemand die komplette Anwendung mit seiner eigenen Version.
- Ein 100 KB großes Ziparchiv kann auch einfach 100 MB Daten (“aaaaaa…”) enthalten.
- Du willst niemals hochgeladene Dateien einbinden (include, require, eval(file_get_contents())). Egal für welchen Zweck.
- Beachte, wer welche Dateien sehen darf. Sollen sie nur für den Uploader zugänglich sein?
- Stelle sicher, dass jedem Benutzer ein Quota zukommt. Oder wenigstens ein systemweites Quota. Du willst nicht, dass dir ein Scriptkiddie deinen Webspace mit Pornobildchen zupflastert und/oder dich als seinen persönlichen Dateihoster missbraucht.
- Müssen die Dateien von außen zugänglich sein?
- wenn nein: per htaccess schützen oder außerhalb des Webroots ablegen
- wenn ja: umbenennen und ggf. in dem Verzeichnis sämtliche dynamischen Features des Webservers ausschalten (du willst nicht, dass dir jemand ein PHP-Script nach /uploads/ schiebt und es in seinem Browser aufrufen und ausführen kann!). Du kannst die Dateien immer über ein extra Script an den Client senden, dass den Dateiinhalt **einliest** (”readfile()” eignet sich hervorragend dazu).
Wenn viele (wirklich viele) Metadaten zu einer Datei benötigt werden und diese auch relativ klein sind, kann man auch darüber nachdenken, sie in der Datenbank (als BLOB) abzulegen. Bilder und größere Dateien würde ich davon jedoch immer ausnehmen, da man mit BLOBs und CLOBs sämtliche Stärken von SQL (Suchen, Selektieren, Verbinden, …) wegwirft (dann kann man auch CouchDB oder das Dateisystem nehmen).
Ein Beispiel soll noch verdeutlichen, wie gefährlich es sein kann, auch vermeintlich sichere Dateien einzubinden. Nehmen wir an, ein Benutzer lädt ein beliebiges Bild auf den Server. Mittels ImageMagick oder GD oder GIMP haben wir sichergestellt (indem wir versucht haben, es zu öffnen, was uns gelang), dass es sich wirklich um ein JPEG-Bild handelt. Theoretisch können wir es also einbinden.
// send.php
// Forbid direct access to a file and instead send it via this script so that
// we can add caching header and stuff.
include("$_GET[file].jpg");
Ganz davon abgesehen, dass dieser Code ohnehin mehrere Schwachstellen aufweist, nehmen wir ihn mal hin. Der Besucher sieht nun die folgende Ausgabe:
Warning: Unterminated comment starting line 1 in D:\test.jpg on line 1
Ï Ó ►JFIF ☺☺☺ ` ` ■ %Hello from JPEG!
Was ist passiert? Nun, ein Blick in das Bild offenbart das Problem:

Ich habe vor dem Upload als JPEG-Kommentar die Programmzeile
<?php print "Hello from JPEG!";/*
in mein Bild eingebettet. Und dabei war ich noch nett. Ich hätte auch den Webspace leeren oder dergleichen machen können. Und das auch ohne das Anzeigen von Fehlern. Wir lernen also, dass auch eigentlich geprüfte und für gut befundene Daten ihre Tücken haben können (und dass das Einbinden von hochgeladenen Dateien eine wirklich ganz schlechte Idee ist).
Komplexe Berechnugen
Kann mein Besucher über den Aufruf einer URL eine rechenintensive Aktion anstoßen?
Nehmen wir mal an, wir bieten einen Remote-Download-Service an. Besucher geben uns URLs, die sie aus irgendeinem Grund nicht aufrufen können, und wir laden sie für sie auf unseren Server (dabei gilt das eben schon für Dateiuploads Gesagte was identisch) und sie können sie später herunterladen. Wenn wir diesen Upload direkt auf Benutzerwunsch durchführen, ist nach 5 Besuchern (oder einem sehr bösen Besucher) unsere Bandbreite dicht. Das Gleiche gilt für komplexe Datenbank-Queries oder sonstige Berechnungen. Cachen oder Ablehnen. Wir wollen auch nicht, dass unsere Besucher mit jedem Seitenaufruf das Abrufen externer Quellen anstoßen können (z.B. soll ein Kunde in einem Shop nicht durch einen Klick auf einen Button eine Tracking-Anfrage an UPS starten dürfen).
Im Allgemeinen ist es hier sinnvoll, derartige Aufgaben vom Benutzer abzukoppeln. Zu holende Dateien (Remote-Download-Beispiel) können in einer Warteschlange landen. UPS-Trackings können per Cronjob unabhängig vom Geschehen auf der Webseite durchgeführt werden.
Im Idealfall kann ein Besucher durch seine Aktionen keine aufwändigen Operationen auf dem Server anstoßen.
Geheist werden
Läuft meine Anwendung auch noch dann [performant], wenn ich geheist / geslashdottet / populär werde?
Hier spielt die Sicherheit nur eine untergeordnete Rolle. Dennoch möchte ich das Thema gern ansprechen, da viele Seiten das Problem ignorieren. Beim Entwickeln der Anwendung sollte immer die Zielgruppe im Auge behalten werden: Facebook wird sicherlich keine Gesamtliste aller Mitglieder im Backend erzeugen während der kleine Tante Emma Laden um die Ecke seine Webseite sicher nicht redundant über 6 Server splitten wird. Augenmaß ist das Stichwort.
Einige Probleme, die auftreten können, wenn die Webseite und die in ihr enthaltenen Datenberge wachsen:
- Erzeugen eine SQL-Queries Full-Table-Scans? Das mag bei 10 Benutzern nicht wehtun, bei 10 Millionen wird MySQL schwer zu tun haben. <del>Indizes</del> Indexe helfen.
- Erzeuge ich irgendwo Listen von Datenbankeinträgen (Gästebuch, Benutzerliste, Forenbeiträge, …)? Was passiert, wenn mein Gästebuch zugespamt oder sehr populär wird? Die Datenbank schaufelt bergeweise Daten in mein Script, die erzeugten Seiten werden mehrere MB groß. Hier helfen Pager (Seite 1, 2, …) oder Selectboxen (auch wenn Selectboxen sich nicht so gut skalieren lassen).
- Was passiert, wenn mein Gästebuch **richtig** populär wird? Dann erzeuge ich Pager mit Links zu Zehntausenden Seiten. Hier hilft es, nur die erste, letzte sowie die 3 Seiten um die aktuelle herum auszugeben (Seite 1 … 3 4 5 6 7 … 10000000).
- Habe ich einen Cache in meiner Anwendung? Sind meine Assets (JS/CSS/Bilder) clientseitig cachebar?
- Verschicke ich basierend auf Benutzeraktionen (Formular abgeschickt ohne Captcha, …) eMails? Was passiert, wenn mein Gästebuch populär wird? Will ich Hunderte Mails erhalten, bis mein eMail-Anbieter die IP meines Hosters blockiert?
- Dummerweise ist das Beschränken von Mails so eine Sache. Man muss irgendwo Buch darüber führen, wie viele Mails wann rausgegangen sind.
- Wenn ich viel Traffic habe, was passiert mit meinen Datenbankzugriffen? Hier helfen Transaktionen oder Table Locks, Chaos zu verhindern (und verbessern teilweise auch noch die Performance).
- …
Nicht alle Probleme treffen auf alle Projekte zu, aber bei der Entwicklung sollte man derartige Probleme immer im Hinterkopf haben. Und sicherlich sind nicht alle Techniken immer bis ins Unendliche skalierbar und eine Neu-Implementierung wird notwendig.
Header Injection
Verwende ich Benutzerdaten in HTTP/eMail-Headern?
Das Problem mit HTTP-Headern ist nicht so dramatisch, da neuere Versionen von PHP in ”header()” von Haus aus keine Zeilenumbrüche zulassen. Theoretisch kann aber eine böse Benutzereingabe dazu führen, dass in ”header()” mehrere HTTP-Header erzeugt werden, was alle Arten von seltsamen Verhalten (Stichwort: Response Splitting) zur Folge haben kann. Hier gilt es, den Raum erlaubter Werte minimal zu halten ([a-z0-9.-]+ oder dergleichen).
Viel dramatischer sind hingegen Header in eMails. Ein typisches Script sieht wie folgt aus:
// Tell-a-friend-Script
$to = $_POST['to'];
$from = $_POST['from'];
$subject = "Hey, check this out!";
$body = "Hey, check out this wunderful website over there!";
mail($to, $subject, $body, "From: $from", "-f$from");
Ganz davon abgesehen, dass das Weiterreichen der Parameter an sendmail ohne weitere Prüfung schon fast einer Shell Injection gleicht, ist das Setzen des From-Headers sehr dramatisch. Nicht selten heißen die Empfänger solcher eMails plötlich
Timmy <timmy@foo.com>\nTo: spamopfer1@isp.com\nTo: foo@bar.com\nBCC: Foo\nSubject: Viagra!
Selbst wenn man aufgrund der Natur von eMail-Adressen diese nur schwer richtig validieren kann (kleine Regex können gefährliche Lücken haben oder viel zu restriktiv sein), kann und sollte man immer Newlines und derartige Sonderzeichen aus eMail-Adressen entfernen.
Session Fixation
Bekommt ein Benutzer beim Login eine neue Session-ID?
Session Fixation läuft in etwa wie folgt ab:
- Eve bringt Alice dazu, eine Webseite X aufzurufen. Dabei gibt Eve Alice bereits ein Cookie mit, in dem eine Eve bekannte Session-ID steht.
- Alice loggt sich auf der Webseite ein. Sie erhält keine neue Session-ID, sondern die beim ersten Aufruf gefundene wird nur mit einem Flag “eingeloggt” versehen (oder anderweitig als authorisiert gekennzeichnet).
- Eve kann sich selbst ebenfalls ein Cookie geben, dass die ihr bekannte Session-ID enthält.
- Eve ruft Webseite X auf. Wegen der identischen SID ist Eve ebenfalls eingeloggt.
Das Beispiel zeigt, dass es notwendig ist, Alice beim Login ein neues Cookie mit einer neuen SID zu geben. Nur dann ist die alte SID von Eve nicht mehr gültig. Das verhindert natürlich nicht, dass Eve Alice das Cookie nicht anderweitig (XSS, …) stiehlt.
Leitsatz: Wenn sich das Benutzerlevel ändert, generiere eine neue Session-ID.
Security through Obscurity
Gibt es Funktionen auf meiner Seite, die nur dadurch geschützt sind, dass ihr Ort (hoffentlich) einer nicht authorisierten Person nicht bekannt ist?
So verlockend es manchmal auch sein kann, man sollte sich niemals auf derartige “Sicherheit” verlassen. Da es sich dabei nicht um wirkliche Sicherheit handelt, ist eigentlich schon die Überschrift falsch. Es ähnelt mehr Russischem Roulett mit den Komponenten des Systems.
Vielleicht war der Entwickler so clever, das Godlike-Script “45z3i5u24h2g2h.php” zu nennen, in der Annahme, dass nur jemand, der diesen Namen kennt, es auch aufrufen kann (und überhaupt erst danach sucht). Dumm nur, dass der Betreiber der Seite aus Versehen seine index.html gelöscht hat und Apache ein Directory Listing erzeugt. Fail.
Okay, das war konstruiert. Warum sollte der Betreiber seine index.html löschen? Okay, die Datei ist da. Mist, in Super-Duper-CMS Version 1.0.9 ist ein Bug: Beim Uploadmanager für Besucher ist eine Directory Traversal-Lücke. Statt /media/ kann ein Angreifer jedes Verzeichnis auflisten lassen. Auch / — und da liegt unser Godlike-Script. Fail.
Wir sehen, die Möglichkeiten sind vielfältig. Der Fehler, der das Kartenhaus des Versteckens in sich zusammenfallen lässt muss nicht einmal bei uns liegen. Selbst wenn wir wissen, dass **wir** Fehler machen: wie unrealistisch ist es, weder Fehler in anderen Komponenten noch menschlisches Versagen anzunehmen?
Umgang mit Passwörtern
Speichere ich Passwörter im Klartext?
Der Umgang mit Passwörtern sollte zur Genüge bekannt sein:
- Speichere Passwörter niemals im Klartext.
- Verwende eine kryptografisch sichere Hashfunktion (SHA-Familie)
- Verwende immer einen (dynamischen oder statischen) Salt:
- statisch: $hash = sha1($userPassword.’thisismyrandomsaltthatnoonecaneverguess’)
- dynamisch: $hash = sha1($userPassword.$userID) — Dynamische Salts sind Werte, die pro Benutzer verschieden aber dennoch konstant sind (User-ID, Registrierzeitpunkt, ggf. Benutzername oder eMail).
- Vorteile:
- Wir sichern uns gegen Rainbow-Tables ab: Bei einem statischen Hash muss ein Angreifer sämtliche möglichen Passwörter 1x neu hashen, um eine Datenbank aller Passwörter zu haben. Beim dynamischen Salt muss ein Angreifer dies für jedes einzelne Passwort tun.
- Die Benutzer sind untereinander besser geschützt: Selbst wenn zwei Benutzer das gleiche Passwort haben, werden beim dynamischen Salt verschiedene Hashes erzeugt.
- Wenn gewünscht (paranoide Kunden gibt es überall), kann man auch das Hashen mit einem linearen Zeitfaktor versehen (1000x SHA-1 dauert trotzdem nur wenige Millisekunden, aber die Masse macht’s).
- Achtung: Nicht zuviel Rechenlast auf dem Server erzeugen!
- Verlange eine Mindestlänge vom Benutzer (mindestens 6 bis 8 Zeichen).
- Limitiere den Benutzer nicht zu sehr in der Wahl der Zeichen im Passwort (KeePass-Benutzer haben gern Sonderzeichen im Passwort).
- Vermeide domainspezifische Passwörter: Diese Technik kann nur unter Berücksichtigung der jeweiligen Zielgruppe angewandt werden. Auf einer Fanseite für Scrubs sollten Passwörter wie “JD”, “Doktor Cox”, “Bob Kelso”, “Hausmeister” usw. auf einer Blacklist stehen und nicht erlaubt sein. Ggf. kann man die Begriffe auch in das Wörterbuch integrieren, das man mit cracklib nutzt, um auch noch alle Varianten dieser Namen austesten zu lassen (“Bob Kelso”, “bobkelso”, “kelso”, …).
- Wenn eine Sicherheitsfrage eingegeben werden soll, um eine Passwort-vergessen-Funktion zu implementieren, lass den Benutzer seine Frage selber wählen. Die meisten typischen Fragen (Geburtsname der Mutter oder Name des Haustiers (siehe Paris-Hilton-Hack)) sind zu leicht über Social-Engineering zu knacken.
- Wenn du deinem Benutzer ein neues Passwort zuschickst, stelle sicher, dass sein altes so lange weiter gültig ist, bis er sein neues gewählt hat. Andernfalls kann Eve für Alice einfach ein neues Passwort anfordern und Alice muss sich entweder das neue merken oder andauernd ihr altes wieder eintragen. Das nervt gewaltig. Es gibt im Prinzip zwei Verfahren, das Passwort zu ändern:
- Generiere ein Alternativpasswort und prüfe beim Login sowohl das echte als auch das alternative. Problem: Ein Hacker muss nun nur noch eines von zwei Passwörtern erraten (doppelte Chance auf Treffer).
- Leite den Passwort-ändern-Prozess erst ein, wenn der Benutzer in der eMail auf den Zurücksetzen-Link klickt. Auf diese Weise existiert immer nur ein gültiges Passwort gleichzeitig.
Checkliste für Websicherheit – Teil 1
24.
Juli
Goodbye & Welcome
von Christian Metzeler Ein Kommentar
Im webvariants-Team gab es wieder einmal Veränderungen. Diese liegen sogar schon ein bisschen zurück, aber der Juli war ein hektischer Monat, was man an der niedrigen Blogfrequenz auch beispielhaft merkt.

Zunächst mal sagen wir also “Servus” zu Gregor, der sich im Zuge seiner persönlichen Lebensplanung und seines Studienabschlusses von der festen Mitarbeit bei uns verabschiedet hat. Dave hat sich so manches Wortgefecht und ausufernde Diskussion mit ihm geliefert – auf die muss er jetzt wohl verzichten. Gregor bleibt uns allerdings als freier Mitarbeiter erhalten – wenn er mal Zeit für uns finden sollte, versteht sich. Wir wünschen ihm und seiner Familie jedenfalls alles Gute und natürlich viel Glück für die Zukunft.

Da bei uns die Arbeit aber auch nicht weniger wird, haben wir Ersatz für Gregor eingestellt. Seit Anfang Juli arbeitet daher nun Jens bei uns, der direkt von den Städtischen Werken Magdeburg zu uns gewechselt ist. Deren Pech, unser Glück, denn dazu gibts nächste Woche gleich noch eine lustige Ankedote aus dem Alltag der Akquise. So konnten wir unser Team mit Jens um einen weiteren Webentwickler ergänzen. Als Fachinformatiker für Anwendungsentwicklung passt er prächtig ins Team und kann sich nun auf höchstem Niveau austoben.
23.
Juli
Checkliste für Websicherheit – Teil 1
von Christoph 7 Kommentare
Um unseren kleinen Beitrag zu einer sicheren Welt zu leisten, wollen wir nicht nur zum Welt-AIDS-Tag Kondome über Outdoor-Kunstwerke stülpen (ups… das gehört woanders hin), sondern diesmal auch etwas praktisch Orientiertes anbieten: im Folgenden sehen wir uns eine Liste möglicher Angriffe auf Webanwendungen an. Es geht dabei nicht zwangsweise um PHP, auch wenn die Beispiele aufgrund unserer Firmennatur PHP-Code sein werden. Außerdem geben wir uns nicht mit einer popeligen Gliederung ab, da die meisten Probleme eh vielfältig sind und Schubladen nicht mögen.
Ich werde wenig bis gar nicht auf praktische Angriffe eingehen, da ich davon ausgehe, dass jeder entweder Google bemühen kann oder bereits weiß, wie XSS & Co. funktionieren.
XSS (Cross Site Scripting)
Werden alle Ausgaben, die in irgendeiner Form vom Benutzer abhängen, entsprechend dem Ausgabemedium kodiert?
Das heißt, werden bei HTML alle Variablen durch ”htmlspecialchars()” verarbeitet? Dabei nicht vergessen: Encoding richtig setzen! Andernfalls knallt’s bei Multibyte-Ausgaben ganz schnell.
- Böse: print $_GET['page'];
- Besser: print htmlspecialchars($_GET['page']);
- Noch besser: print abs(intval($_GET['page']));
- Ideal (ab PHP 5.3): print abs(intval(isset($_GET['page']) ?: 1));
(Wir gehen davon aus, dass in ”page” vermutlich die aktuelle Seite einer Auflistung enthalten ist. Und, dass dieser Wert nicht negativ sein sollte.
Benutzerspezifische Werte können aus vielen Quellen kommen:
- GET und POST (die üblichen Verdächtigen)
- Cookies (auch die Session-ID, die dort oft liegt, kann manipuliert sein)
- in seltenen Fällern auch HTTP-Header (auch wenn diese eher selten PHP-seitig ausgewertet werden)
- hochgeladene Dateien (den Angaben in ”$_FILES” ist auch nicht zu trauen)
- fremde Webseiten (Wertet deine Seite den Inhalt von RSS-Feeds aus? Vertraust du ihnen? Jede externe Quelle ist potentiell genauso anfällig für Fehler wie deine eigene.)
Hier gilt: Immer so spät wie möglich escapen. HTML-kodierte Werte bringen in der normalen Applikation rein gar nichts. Auf keinen Fall sollte ”htmlspecialchars()” irgendwo im Model auftauchen, in der Regel auch nicht im Controller.
Spätes Escapen ergibt nicht nur mehr Sinn, sondern sorgt auch dafür, dass das Ausgabemedium beliebig gewechselt werden kann. Statt HTML könnte LaTeX die Ausgabe sein, bei der dann natürlich kein htmlspecialchars() mehr zum Einsatz kommen muss (dafür dann eine ”texspecialchars()”).
SQL-Injection
(Ich komme zu dem Thema, weil sich hier die logische Fortsetzung der Ausgabekodierung gut anschließen lässt.)
Werden alle Werte, die in irgendeiner Form in Queries verwendet werden, entsprechend dem DBMS und ihrem Datentyp kodiert?**
Das alte Problem von SQL-Injections dürfte so ziemlich jedem bekannt sein:
SELECT * FROM `mytable` WHERE `key` = $_GET[foo];
Hier müssten wir
- entweder davon ausgehen, dass ”key” numerisch sein soll. In diesem Fall fehlt ein intval() oder ähnliches.
- oder davon ausgehen, dass ”key” ein String ist. Dann fehlen sowohl die Quotes im SQL (”WHERE key = ‘$_GET[foo]”’) als auch das Escaping eben jener in der Variable.
Gerade das Escapen kann jedoch tricky sein: Wie schon beim Escapen der Ausgabe muss hier der Zeichensatz beachtet werden. ”addslashes()” kennt die verschiedenen SQL-Engines nicht und wird daher die spezifischen Steuerzeichen ebenso unbeachtet lassen wie Multibyte-Strings. Im Falle von MySQL ist also ”mysql_real_escape_string()” die Funktion der Wahl.
Doch auch hier gilt Vorsicht: Ändert man den Zeichensatz der Verbindung nur über MySQL (”mysql_query(“SET NAMES ‘utf8′””)), so kriegt ”mysql_real_escape_string()” von dieser Änderung nichts mit. Hier müsste man also stattdessen die native ”mysql_set_charset()”-Funktion bemühen, sonst kann es bei UTF-8-Verbindungen auch zu Problemen kommen.
Im Idealfall abstrahiert man den Datenbank-Zugriff bei größeren Projekten gänzlich über einen OR-Mapper (Doctrine, Propel, …) weg oder nutzt für kleinere Projekte zumindest PDO und die dort verügbaren Prepared Statements, womit das lästige Escapen gänzlich unter den Tisch fällt.
Ein abschließender Hinweis dazu noch: Ein eventuell von PHP durchgeführtes Magic Quoting sollte in jedem Fall von der Anwendung erkannt und entfernt werden. Einerseits machen escapte Werte beim normalen Verarbeiten (ohne SQL) nur Probleme. Andererseits sind die Werte nicht richtig escaped, um in SQL verwendet zu werden (siehe Multibyte-Encodings). Die Anwendung sollte das Escaping immer so spät wie möglich vornehmen, bestenfalls beim Zusammensetzen der Anfrage. Auf diese Weise vermeidet man, dass manche Variablen escaped vorliegen und manche nicht. Das Escapen ist damit meistens Aufgabe des Models (in MVC-Architekturen).
Benutzereingaben
All users are either stupid, evil or both. And so is their input.
Das alte Spiel noch einmal zusammengefasst:
- Egal, was der Benutzer eingibt, es ist vermutlich falsch.
- Er weiß es nicht besser.
- Er hat sich vertippt.
- Er hat vorsätzlich etwas Böses eingegeben.
- Validiere jeden Wert, egal, wie trivial er erscheint:
- Seitennummer in der URL? Das muss eine Zahl sein. Und die muss sogar meistens positiv sein: ”abs(intval())”
- Zeichenketten allgemein: Keine eMail ist 65k Zeichen lang, kein Forenbeiträge 3 MB: ”trim(substr($string, 0, $maxlen))”
- Achtung: nur auf nicht-escapte Strings anzuwenden, da sonst Entities (HTML) oder Slashes (SQL) abgeschnitten werden können!
- Benutzername: Einzeiliger String, i.d.R. recht eingeschränkt: ”preg_replace(whitelist, “”, $username)”
- Datumsangaben: Geburtstag des aktuellen Benutzers war erst vor einem Jahr? Oder vor 1900? Ablehnen (nicht: korrigieren). Parsing beispielsweise via ”strtotime()” (Vorteil: Benutzer kann Datum vermutlich in jeder ihm bekannten Weise eingeben.)
- Entsprechende Prüfroutinen sollten dynamisch sein (Maximaljahr nicht fest ”$X”, sondern ”date(‘Y’) – $X”)
- Das macht sich dann auch gut, wenn man eine Selectbox mit Jahresangaben befüllen will. Es sei denn, man will den Kunden dazu bringen, jedes Jahr auf’s neue das Hinzufügen eines Jahres zu einer Box zu beauftragen.
- Blacklists sind niemals vollständig. Das Prüfen, ob eine Eingabe auf einer Blacklist steht kann daher nur schiefgehen.
- Whitelists sind daher in jedem Fall vorzuziehen.
- Benutzereingaben haben nur sehr wenig und nur nach ausreichender Prüfung (Whitelist) in Funktionen wie ”include”, ”fopen”, ”file_get_contents”, … zu suchen.
- Nullbytes fungieren in PHP innerhalb von Strings wie ein Kommentarzeichen:
- ”include(“$foo.html”);”
- ”$foo = “../../../include/config.php\0″;”
- => ”include(“../../../include.config.php”);”
CSRF (Cross Site Request Forgery)
(Eigentlich sollte man das in Konsistenz zu XSS auch mit XSRF abkürzen.)
Werden alle schreibenden Aktionen (Löschen, Anlegen, Verschieben, Umbennnen) per POST-Anfrage ausgeführt?
In einer idealen Welt würden wir wohl neben POST auch noch PUT und DELETE als HTTP-Methoden nutzen. Aber die Welt ist nicht ideal. Und POST reicht meistens auch vollkommen aus.
Die Wichtigkeit, schreibende Aktionen nur über POST zugänglich zu machen, zeigt sich, wenn man CSRF-Attacken näher untersucht. Ein Großteil der Angriffe wären nicht möglich gewesen, hätte die Anwendung auf POST gesetzt.
Dazu reicht es nicht, nur das eigene Formular auf POST zu ändern. Auch die Anwendung muss in derartigen Fällen explizit über ”$_SERVER['REQUEST_METHOD']” prüfen, ob es sich um POST handelt. Schöner wäre es noch, wenn man ein explizites CSRF-Token einführt, was aber in den meisten Fällen die Benutzerfreundlichkeit gewaltig drücken kann. Beispielcode könnte wie folgt aussehen:
function ensure_method($method)
{
if (strtoupper($method) !== $_SERVER['REQUEST_METHOD']) {
die('Request must be made using '.strtoupper($method));
}
}
ensure_method('POST');
Das Verwenden von POST allein bringt einer Anwendung allerdings keinen vollständigen Schutz vor CSRF-Attacken. Hier hilft meist nur, ein Token zu implementieren, das sich bei jedem Seitenaufruf ändert und in Formularen als verstecktes Feld mitgeschickt wird. Fehlt es oder ist es veraltet, ist eine Verarbeitung des Requests abzulehnen. Problematisch werden diese Token, wenn Benutzer Fehleingaben tätigen oder alte Formulare via Back-Button ihres Browser erreichen und erneut abschicken möchten. Mit einem gesunden Augenmaß lässt sich hier jedoch auch die Belastung für den Benutzer auf ein erträgliches Maß reduzieren.
In ein paar Tagen gibts hierzu den zweiten Teil mit weiteren interessanten Sicherheitsproblemen wie Dateiuploads, Komplexen Berechnungen, Header Injection und Security by Obscurity. Freut euch drauf.
Weiterlesen: Checkliste für Websicherheit – Teil 2
9.
Juli
Der Texteditor im Internet Explorer 8
von Christian Metzeler Ein Kommentar
Der von uns am häufigsten verwendete Texteditor bei Redaxo-Projekten ist der TinyMCE. Er ist ein Drittanbieter-Produkt, dass in Redaxo integriert wird, um dem Redakteur eine einfachere Gestaltung des Inhaltes zu ermöglichen. Leider hat der TinyMCE ein Kompatibilitätsproblem mit dem Internet Explorer 8. Wenn man mit dem IE8 in den Texteditor geht und diesen zum Bearbeiten öffnet, bleibt das Textfeld ausgegraut und es kann nichts editiert werden.

Es gibt jedoch eine Möglichkeit, trotzdem mit dem IE8 zu arbeiten. Rechts oben im IE8 gibt es ein Menü mit mehreren Einträgen, darunter auch der Eintrag “Seite”. Hier findet sich in der Mitte der Punkt “Kompatibilitätsansicht”, mit welcher man den IE8 dazu bringt, sich wie ein IE7 zu verhalten. Aktiviert man diesen Punkt, so wird der Texteditor wieder normal dargestellt und man kann darin arbeiten.
Wir werden unsererseits weitere Projekte mit einem speziellen Tag versehen, welches vom IE8 interpretiert wird und diesen automatisch dazu bringt, sich bei der Verwendung des TinyMCEs wie ein IE7 zu verhalten, so das eine manuelle Umstellung nicht nötig ist. Im Lauf der nächsten Monate werden wir dann außerdem auf einen neuen, moderneren Texteditor für unsere Redaxo-Projekte umsteigen.