Fundstücke und How-Tos rund um das Thema Sicherheit von Computersystemen.

PHP 5.3.7, eine Analyse

Wednesday, August 24, 2011 11:35:18 PM

Das PHP-Entwicklerteam veröffentlichte vor wenigen Tagen die Version 5.3.7, nur um schon wenige Tage später vor deren Verwendung zu warnen. Der Grund dafür war ein Fehler in der Funktion crypt(), welche bei bestimmten Hash-Verfahren lediglich das Salt zurückliefert. Das kann dazu führen, dass nach einem Update auf PHP 5.3.7 keine Benutzer sich mehr auf einem Webauftritt einloggen können oder sich bei einer Passwortänderung nach einem Update auf eine spätere PHP-Version nicht mehr einloggen können. In PHP 5.3.8 wurde der Fehler wieder behoben.

Dieser Artikel ist der Versuch einer Analyse, wie es zu dem Fehler kam und warum er erst nach der Release bemerkt wurde.

Die PHP-Funktion crypt() ist in der Datei php_crypt_r.c implementiert. Folgender Codeausschnitt baut dort den Passwort-Hash zusammen:

memcpy(passwd, MD5_MAGIC, MD5_MAGIC_LEN);
strlcpy(passwd + MD5_MAGIC_LEN, sp, sl + 1);
strcat(passwd, "$");

strcat() fügt eine Zeichenkette an das Ende eines Puffers. Die Funktion gilt als unsicher, da sie nicht prüft, ob der Zielpuffer genügend Speicherplatz zur Verfügung stellt. Wird die Zeichenkette zu lang, wird der nachfolgende Speicherbereich beschädigt – ein typisches Problem bei C-Sprachen.

Aus dem Grund wurde der Aufruf durch eine sicherere Funktion ersetzt. Leider ist aber gut gemeint das Gegenteil von gut gemacht.

memcpy(passwd, MD5_MAGIC, MD5_MAGIC_LEN);
strlcpy(passwd + MD5_MAGIC_LEN, sp, sl + 1);
strlcat(passwd, "$", 1);

strlcat() stellt sicher, dass der Puffer nicht über sein Ende hinaus beschrieben wird. Dazu wird dessen Größe übergeben. Und genau hier lag das Problem, denn statt der Größe von passwd wurde anscheinend die Größe des zu kopierenden Textes übergeben, nämlich 1. Da passwd zu dem Zeitpunkt bereits deutlich mehr als ein Zeichen enthält, tut strlcat() genau das, was es tun soll, nämlich gar nichts. Das “$”-Zeichen wird nicht angehängt, das tatsächliche Ergebnis weicht damit von dem gewünschten Ergebnis ab.

Solch ein Fehler ist eigentlich ein Lehrbuchbeispiel für Unit-Tests, und der Kommentar zum Bugfix (Revision 315218) deutet auch an, dass ein solcher existiert:

Unbreak crypt() (fix bug #55439)
# If you want to remove static analyser messages, be my guest,
# but please run unit tests after

Tatsächlich gibt es einen Test, der die crypt()-Funktion mit bestimmten Werten aufruft und das Ergebnis mit einem erwarteten Ergebnis vergleicht. Dieser Test schlägt Alarm, wenn er ausgeführt wird.

Dass PHP 5.3.7 dennoch veröffentlicht wurde, lässt eigentlich nur einen Schluss zu: Der Unit-Test wurde nicht ausgeführt oder der Alarm wurde schlichtweg ignoriert. Spätestens beim Bau der finalen Version unmitelbar vor der Veröffentlichung hätte dies aber stattfinden müssen. Alles andere wäre grob fahrlässig.

Zusammengefasst hatte der Fehler also folgende Ursachen:

  • die für C-Sprachen üblichen Probleme bei der sicheren Verarbeitung von Zeichenketten
  • eine missverständliche oder nicht verstandene Dokumentation der Funktion strlcat()
  • keine verbindlich vorgeschriebene fehlerfreie Ausführung der Unit-Tests vor der Freigabe einer Release

Insbesondere der letzte Punkt wiegt schwer und wirft ein schlechtes Licht auf die verantwortlichen PHP-Entwickler.

PHP-Angriff von EviLuTz

Friday, March 27, 2009 7:13:00 AM

Gestern hat ein netter Zeitgenosse offenbar versucht, meine Websites zu hacken. Er klapperte auf gut Glück eine Liste von über 700 URLs ab, die alle versuchen, die URL http://glendalehills.am/photo.gif? einzubinden. Hierbei handelt es sich nicht um ein Bild, sondern um ein simples PHP-Script:

<?php
echo ("EviLuTz hacked you");
?>

Der Sinn hinter dieser Aktion wird sein, die angegriffenen Server auf ein bei Hackern sehr beliebtes PHP-“Feature” abzuklopfen. PHP erlaubt es beim include()-Kommando, URLs anzugeben, die PHP dann artig vom externen Server nachlädt und einfach mal ausführt. Idiotischerweise ist diese Funktionalität per Default erlaubt, und kann auch erst seit PHP 5.2.0 gezielt abgeschaltet werden. Wenn man nun einen Parameter ungeprüft übernimmt und in das include() füttert, um normalerweise eine lokale PHP-Datei einzubinden, ist die Sicherheitslücke auch schon da. Leider tappen viele PHP-Entwickler in diese Falle. Vermutlich auch, weil dieses Feature erst irgendwann nachträglich dazukam. Andererseits dürfen Parameter, die vom Besucher der Site kommen, sowieso niemals ungeprüft verwendet werden.

Aber zurück zum Thema: Wenn der Angreifer nach dem Aufruf eine Seite zurückbekommt, in der der Text “EviLuTz hacked you” enthalten ist, weiß er, dass die Site angreifbar ist. Ob er dann sofort ein Hack-Script nachschiebt oder erst mal nur die Server sondiert, kann ich nicht sagen.

Der Angriff ging gegen mehrere Domains, aber immer von der IP 91.121.31.184 aus. Sie gehört offenbar einem dedizierten Server eines französischen ISPs. Auffällig ist auch der User-Agent, der mit der Anfrage mitgeschickt wird: “Toata dragostea mea pentru diavola” (laut Google-Übersetzer ist das Rumänisch für “Alle meine Liebe für den Teufel”).

Die Domain glendalehills.am gehört laut Whois seit 2005 der Firma “Glandale Hills” (sic!) in Armenien.

Als schnelle Gegenmaßnahme empfiehlt sich, die IP und/oder den User-Agent zu sperren. Auf jeden Fall sollte bei PHP außerdem allow_url_include abgeschaltet werden, um sich generell gegen diese Angriffe zu wehren. (Leider erfordert manche PHP-Software dieses Feature, zum Beispiel um externe Plugins nachzuladen.) Und der übliche Rat: Wikis, Foren, CMSe und weitere Software, die auf PHP aufsetzt, sollten immer auf einem möglichst aktuellen Stand gehalten werden.

PS: allow_url_include = Off stoppt nur die schlimmste Möglichkeit, nämlich dass fremde PHP-Scripte auf dem eigenen Server ausgeführt werden. Wenn die verwendete PHP-Software aber grundsätzlich anfällig ist (also HTTP-Parameter ungeprüft included), ist weiterhin eine gefährliche Sicherheitslücke offen, da darüber auch lokale Dateien gelesen oder zum Beispiel Administrations-Skripte aufgerufen werden können.

Written by Shred in Sicherheitno comments

Tags: Exploit, PHP

Spammer-Angriff

Thursday, July 28, 2005 2:57:29 PM

Ein verrückter Kerl hat heute morgen versucht, Spam über die Kommentarfunktion der shredzone zu verschicken.

Natürlich ist er daran gescheitert. Sein Versuch galt alten, fehlerhaften Perl-Mailscripten. Durch speziell manipulierte Texte ist es möglich, solchen fehlerhaften Scripten weitere Empfänger unterzujubeln, da sie den Text ungefiltert an sendmail weitergeben und jenes die Textteile als Mailheader interpretiert.

Die shredzone verwendet keine solchen Scripte. Trotzdem war sein Angriff in vielerlei Hinsicht interessant.

Er postete insgesamt sechs Kommentare in nur 35 Sekunden und mit einer Besonderheit: sein manipulierter Header wanderte bei jedem Kommentar, den er abschickte, in den jeweils nächsten Parameter des Formulars – natürlich auch in hidden-Felder und sogar in checkboxen.

Das kann nur bedeuten, dass er ein Tool oder ein Script verwendet, da solche Manipulationen mit einem normalen Browser nicht möglich sind. Eines seiner Kommentare sah dann so aus:

znundj@shredzone.de
Content-Type: multipart/mixed; boundary="===============1924061946=="
MIME-Version: 1.0
Subject: 173d2a6f
To: znundj@shredzone.de
bcc: bergkoch8@aol.com
From: znundj@shredzone.de

This is a multi-part message in MIME format.

--===============1924061946==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

urlcsiiou
--===============1924061946==--

Der Angreifer muss eine Möglichkeit haben, herauszufinden, ob mein Mailscript angreifbar ist. Dazu dient die E-Mailadresse bergkoch8@aol.com. Wenn der Angriff erfolgreich war, landet dort eine Blindkopie der E-Mail, und am Absender kann er dann leicht sehen, welche Domain angreifbar gewesen ist. Die E-Mailadresse steht also unter Kontrolle des Spammers.

Eine Suche bei Google nach der E-Mailadresse fand gleich 720 Treffer! Ein schneller Überblick der Treffer ergab, dass die E-Mailadresse in vielen anderen Gästebüchern und Kommentaren zu finden ist. Der Angriff war also nicht zufällig, sondern Teil einer momentan laufenden Spammerattacke. Auf einer der Seiten stand auch, dass der Angreifer ein entdecktes fehlerhaftes Mailscript umgehend zum Versand von Spam ausnutzt.

Eine Analyse meines Apache-Logfiles für diesen Zeitraum ergab, dass der Angreifer aus Belgien stammt und offenbar ein Kunde von Skynet ist. Ein entsprechender Abuse-Complaint ging bereits raus. Ich hoffe, dass dem Kerl damit das Handwerk gelegt werden kann.

Trotz allem: solche Angriffe auf fehlerhafte Mail-Scripte sind nichts Ungewöhnliches. Betroffen sind in der Regel Kontaktformulare, obwohl bei diesem Angriff alles ausprobiert wird, was auch nur entfernteste Ähnlichkeit mit einem Kontaktformular hat. Achtet also bitte darauf, dass eure Mail-Scripte gegen diese Angriffe geschützt sind. Im Spam steht sonst euer Server als Absender und wird in entsprechende Blacklists eingetragen.

Written by Shred in Sicherheit10 comments

Tags: Spam

Parameterprüfung

Monday, July 7, 2003 12:51:37 AM

Hier wird beschrieben, wie du durch Parameterprüfung deine Seite absichern kannst.

Session schützen

Wednesday, June 25, 2003 8:20:17 PM

Du findest hier, wie man Session davor schützen kann, gekapert zu werden.

SQL-Injections

Friday, May 23, 2003 1:26:57 AM

Dieser Artikel geht über SQL-Injections, und wie man sie verhindern kann.

Was sind SQL-Injections?

Bei SQL-Injections macht ein Angreifer sich zu Nutze, dass Parameter an das Script ungeprüft an das SQL-Statement weitergereicht werden. Durch geschickte Manipulation des Parameters erreicht er so, dass das SQL-Statement eine völlig andere Bedeutung erhält.

Ein Beispiel: Wir haben ein Login-Script, welches einen Usernamen und ein Passwort übergeben bekommt. Das Script sucht nun in der users-Tabelle nach einem passenden User mit dem gleichen Passwort, und liefert im Erfolgsfall die User-ID zurück. Der Einfachheit halber gehen wir davon aus, dass der übergebene Username in $user und das Passwort in $pwd abgelegt ist.

Folgendes SELECT würde sich für die Aufgabe anbieten:

mysql_query("SELECT id FROM users WHERE name=$name AND pwd=$pwd");

Der Angreifer könnte nun jedoch als Passwort $pwd = "foo OR 1=1" an das Script übergeben. Dadurch sähe der fertige SELECT-Statement so aus:

SELECT id FROM users
  WHERE name=meier
    AND pwd=foo OR 1=1

Der Angreifer wäre damit trotz falschem Passwort erfolgreich als meier eingeloggt worden.

Abwehrmaßname 1: Hochkommata

Um dies abzuwehren, sollten grundsätzlich alle Variablen im SQL-Statement in einfache Hochkommata gesetzt werden. 1

mysql_query("SELECT id FROM users WHERE name='$name' AND pwd='$pwd'");

Der ursprüngliche Angriff mit $pwd = "foo OR 1=1" würde dann folgendes SELECT-Statement ergeben:

SELECT id FROM users
  WHERE name='meier'
    AND pwd='foo OR 1=1'

Die Datenbank nimmt in dem Fall an, dass das Passwort "foo OR 1=1" lautet. Der Angriff wäre damit wirkungslos. Allerdings kann der Angreifer selbst Hochkommata einsetzen. Mit folgendem Passwort wäre er wieder erfolgreich: $pwd = "foo' OR ‘1’='1". Dies würde das folgende SELECT-Statement ergeben:

SELECT id FROM users
  WHERE name='meier'
    AND pwd='foo' OR '1'='1'

Und schon würde der User meier erneut trotz falschem Passwort eingeloggt werden.

Abwehrmaßnahme 2: addslashes()

Um auch diesen Angriff abzuwehren, muss man sicher stellen, dass Hochkommata im Parameter ordentlich mit einem Backslash escaped werden. Dafür sorgt PHP mit den Magic Quotes automatisch, sofern sie aktiviert sind. Man sollte sich aber nicht darauf verlassen, sondern den Magic Quotes-Modus abschalten und sich selbst mit der Funktion addslashes() um ordentliches
Escapen kümmern.

Der oben genannte Angriff lässt sich abwehren, indem man die Parameter $user und $pwd mit addslashes() sichert:

$user = addslashes($user);
$pwd  = addslashes($pwd);
mysql_query("SELECT id FROM users WHERE name='$name' AND pwd='$pwd'");

addslashes() würde im Angriffs-Passwort ein Backslash vor die Hochkommata stellen, was dann folgendes SELECT-Statement ergibt:

SELECT id FROM users
  WHERE name='meier'
    AND pwd='foo\' OR \'1\'=\'1'

Damit wäre die Abfrage wieder sicher. Der Angreifer hat nun keine Möglichkeit mehr, die Abfrage zu manipulieren. SQL-Injections sind nun nicht mehr möglich.

Weitere Sicherungsmaßnahmen

Zunächst sollten alle Parameter an das Script auf Plausibilität geprüft werden. Wenn man zum Beispiel eine Zahl erwartet, sollte man den Parameter mit intval() in eine Zahl wandeln und anschließend eine Bereichsprüfung durchführen.

Weiterhin gilt grundsätzlich, wirklich niemals ganze SQL-Kommandos per Parameter an das nächste Script weiter zu reichen. Dem Angreifer wird es damit ermöglicht, direkt SQL-Kommandos an die Datenbank zu schicken. Die Palette reicht von kleinen Spionage-SELECTs bis hin zu einem katastrophalen DROP DATABASE.

Um ein SQL-Kommando von einem Script zum nächsten zu transportieren, sollte man Sessions verwenden. In dem Fall bleibt das Kommando auf dem Server und kann vom Benutzer weder eingesehen noch verändert werden.

Zusammenfassung

Um SQL-Injections erfolgreich zu vermeiden, sollte man also:

  • alle übergebenen Parameter auf Plausibilität prüfen.
  • sämtliche Variablen in SQL-Statements mit Hochkommata einschließen.
  • Magic Quotes abschalten, und stattdessen
  • alle Parameter vor der Übergabe an das SQL-Statement mit addslashes() sauber escapen.
  • zum Weiterreichen eines SQL-Statements an das nächste Script niemals Parameter, sondern nur Sessions verwenden.

1 Dies erlaubt MySQL auch bei numerischen Spalten. Andere Datenbanken können darauf allerdings mit einer Fehlermeldung reagieren.