Mein Prof. rümpfte immer die Nase wenn PHP zur Sprache kam. Er vertrat die Meinung, dass dies keine ernstzunehmende Sprache sei zumindest nicht für professionelle Anwendungen ... einfach weil sich zu viele chaotische Stile gebildet haben. Der eine codet alles prozedural, der andere nimmt Funktionen und ein Dritter erschlägt einen mit Referenzen oder globalen Variablen etc.
Dein Prof. hat damit nicht ganz unrecht. Eben aus den genannten Gruenden. PHP ist im Grunde dafuer ausgelegt eine einfach zu lernende und zu nutzende Sprache zu sein, und einem eben nicht irgendwelche Stolpersteine in den Weg zu legen. Die Auswirkungen dessen sind zum Teil halt recht katastrophale Scripts die dann z.B. nur mit bestimmten Einstellungen laufen oder riesige Sicherheitsluecken in's System reissen.
Das Problem liegt dabei aber nicht unbedingt bei PHP selbst, auch wenn PHP natuerlich durch seine Struktur ordentlich Hilfestellung leistet, sondern bei Usern die unerfahren sind und aus schlechten oder veralteten Quellen lernen. Oder aber auch User die meinen, dass jeder der seine kleine Website besucht auch wirklich nur auf die Links klickt und nicht mal probiert was passiert wenn man eine Anfrage etwas abaendert, also Usern die sich mit Sicherheit kein Stueck auseinandersetzen.
Das Hauptproblem findet man also, wie so oft zwischen Tastatur und Stuhl aufzufinden. Mit jeder Programmiersprache kann man Schrott schreiben, aber PHP verleitet zum Teil halt dazu, weil eben Einstellungen wie z.B. register_globals oder allow_url_fopen einiges vereinfachen koennen. Und wenn ein User es nicht von Anfang an richtig lernt, dann wird er wohl frueher oder spaeter Probleme mit seinen Scripts haben und dann umlernen muessen. Und wir wissen ja alle dass der Mensch ein Gewohnheitstier ist, nicht?
Ich kenn das ja selbst. Meine ersten Erfahrungen mit PHP hab ich mit PHP3 gemacht, und da war noch vieles anders. Und auch ich hab mich damals auf register_globals verlassen und Code geschrieben wovor ich heute wild kreischend weglaufen wuerde.
Und im Grunde hauptsaechlich dadurch, dass ich mich in den letzten Jahren viel mit Security befasst habe hab ich meinen kompletten Programmierstil in PHP geaendert. Und es fallen mir immer wieder neue Kleinigkeiten ein die man noch besser machen koennte.
Es ist einfach ein lauffaehiges Script zu schreiben, aber es ist was anderes ein Script gegen alle Eventualitaeten abzudichten.
Mal ein einfaches Beispiel in ein paar Evulotionsstufen. Ich will dabei nicht alle Einzelschritte aufzeigen, dass wuerde etwas den Rahmen sprengen, aber mal ein paar Schritte wie so ein Script ein gutes Stueck sicherer wird.
PHP:
$galleries=mysql_query("select * from galleries where id='".$gallery."'");
$gallery=mysql_fetch_assoc($galleries);
echo 'Galerie: '.$gallery['gallery'];
$images=mysql_query("select * from images where id='".$gallery."'");
while ($image=mysql_fetch_assoc($image))
{
//Ausgabe
}
Dies ist wohl die schreckliste aller moeglichen Varianten. $gallery kommt per GET, wird in keinster Weise geprueft ob es ueberhaupt uebergeben wird oder numerisch und es wird auch davon ausgegangen, dass die gewuenschte Galerie existiert. Weiterhin werden hier 2 Moeglichkeiten zur SQL-Injection geboten.
PHP:
if ($_GET['gallery'])
{
$galleries=mysql_query("select * from galleries where id='".$_GET['gallery']."'");
$gallery=mysql_fetch_assoc($galleries);
echo 'Galerie: '.$gallery['gallery'];
$images=mysql_query("select * from images where id='".$_GET['gallery']."'");
while ($image=mysql_fetch_assoc($image))
{
//Ausgabe
}
}
Hier wird zumindest mit $_GET gearbeitet und auch geprueft ob der Wert gallery auch uebergeben wurde. Ein Schritt in die richtige Richtung, aber nur ein kleiner, denn alle anderen Probleme bestehen weiterhin.
PHP:
if ((!empty($_GET['gallery'])) && (is_numeric($_GET['gallery'])))
{
$galleries=mysql_query("select * from galleries where id='".$_GET['gallery']."'");
$gallery=mysql_fetch_assoc($galleries);
echo 'Galerie: '.$gallery['gallery'];
$images=mysql_query("select * from images where id='".$gallery['id']."'");
while ($image=mysql_fetch_assoc($image))
{
//Ausgabe
}
}
Hier wird nun geprueft ob $_GET['gallery'] nun auch wirklich einen Wert beinhaltet, und dieser muss zudem numerisch sein. Dadurch faellt auch das Problem der SQL-Injections flach, denn diese sind ueber eine einfache Zahl nicht moeglich. Weiterhin wird in der 2. Query nun die GalleryID aus der ersten Query genutzt. Dies macht zwar hier keinen wirklichen Unterschied, jedoch kann dies bei anderen Werten als bei Zahlen zu ein klein wenig mehr Sicherheit fuehren da ein Angriffspunkt wegfaellt.
PHP:
if ((!empty($_GET['gallery'])) && (is_numeric($_GET['gallery'])))
{
$galleries=mysql_query("select * from galleries where id='".$_GET['gallery']."'");
if (mysql_num_rows($galleries)==1)
{
$gallery=mysql_fetch_assoc($galleries);
echo 'Galerie: '.$gallery['gallery'];
$images=mysql_query("select * from images where id='".$gallery['id']."'");
while ($image=mysql_fetch_assoc($image))
{
//Ausgabe
}
}
}
Hier wird dann zusaetzlich auch noch geprueft ob die gewuenschte Galerie ueberhaupt existiert.
Zusaetzlich koennte man nun noch die Feld- und Tabellennamen in der Query entsprechend kennzeichnen, das saehe dann so aus:
PHP:
if ((!empty($_GET['gallery'])) && (is_numeric($_GET['gallery'])))
{
$galleries=mysql_query("select * from `galleries` where `id`='".$_GET['gallery']."'");
if (mysql_num_rows($galleries)==1)
{
$gallery=mysql_fetch_assoc($galleries);
echo 'Galerie: '.$gallery['gallery'];
$images=mysql_query("select * from `images` where `id`='".$gallery['id']."'");
while ($image=mysql_fetch_assoc($image))
{
//Ausgabe
}
}
}
Einen wirklichen Unterschied macht dies aber nicht. Damit verhindert man nur, dass evtl. ein Tabellen-/Feldname mit einem Schluesselwort kollidiert. Man kann ja durchaus ein Feld mit dem Namen select haben.
Die Query
SQL:
select * from my_table where select='lala'
wird aber fehlschlagen da select als Schluesselwort angesehen wird.
SQL:
select * from `my_table` where `select`='lala'
hingegen funktioniert wunderbar.
Bei Werten die nicht erwiesenermassen numerisch sind sollte man zusaetzlich auch mit Escaping arbeiten, bei MySQL kommt da in der Regel mysql_real_escape_string() zum Einsatz. Hier ist jedoch Vorsicht geboten, denn PHP bringt eine Einstellung mit die von Haus aus escapen kann, die Magic-Quotes. Falls diese Einstellung aktiv ist muessen diese entweder erst entfernt werden oder es wird dann nicht mehr mit mysql_real_escape_string() gearbeitet. Ersteres ist da meiner Meinung nach der bessere Weg.
Ich nutze dafuer diese Funktionen:
PHP:
function remove_magic_quotes($string)
{
if (get_magic_quotes_gpc())
{
$string=stripslashes($string);
}
return $string;
}
function quote_string($string)
{
global $sqldb;
return $sqldb->escape_string(remove_magic_quotes($string));
}
$sqldb->escape_string() entspricht im Grunde mysql_real_escape_string(), ist nur halt eine Methode einer meiner Klassen.
Jeder Wert (sogar Zahlen, da hab ich auf totale Paranoia gesetzt
) der bei mir an die Datenbank geht wird durch quote_string() gejagt, welches ja wiederum remove_magic_quotes() nutzt um evtl. vorhandene Magic Quotes zu entfernen.