Wenn es um Sicherheitsprobleme geht, werfen die entsprechenden Security-Advisories gerne mit Begriffen wie „cross-site scripting“, „cross-site request forgery“ oder „sql injection“ um sich. Aus aktuellem Anlass können wir mal ein praktisches Beispiel einer real existierenden Software nehmen (deren Name lieber ungenannt bleibt), mit der man mandantenfähig Rechnungen erstellen kann, die sich ein Kunde auf seinem Webspace installiert hat. Sie lief anfangs nicht, und die Basis seiner Anfrage war dann eher erstmal der Wunsch nach „Könnt ihr das zum Laufen bringen“-Support. Nachdem ich mir dazu kurz einige der fraglichen PHP-Dateien angeschaut hatte, wurde daraus dann aber eher ein kleines Lehrstück, das ich hier gerne (bis auf die Usernamen) unverändert weitergebe.
[...] Es sieht mir auf den ersten Blick so aus, als könne man da sehr viel verbessern, wenn ich das mal vorsichtig so sagen darf. ;-) Ich sehe schon auf den ersten Blick Angriffspunkte für SQL Injection sowie eine Abhängigkeit von register_globals = On. Das erste ist ein handfester Fehler; das zweite eröffnet eventuelle Angriffspunkte, die nicht sein müssten. Freundlich gesagt sieht man der Software an, das sie von 2006 ist. :) Willst du mal sehen, wie das mit SQL Injection funktioniert? Gib mal bei der Anmeldung als Benutzername deinen Zugang "max" ein und dann als Passwort dieses hier: '||ID='1 Schwupps, bist du drin. Das klappt mit "moritz" genauso. Und mit jedem anderen auch. Hintergrund ist, dass in der mod/authent.php ausgeführt wird: $result=$dbverbindung->run_sql("SELECT ID,User,Pwd FROM Admin WHERE User='" .$user."' AND Pwd='".$pwd."'"); Das heißt, wenn du "max" und "geheim" angibst, steht da: SELECT ID,User,Pwd FROM Admin WHERE User='max' AND Pwd='geheim' Genauso steht dann aber auch da, wenn du mein Trick-Passwort anwendest: SELECT ID,User,Pwd FROM Admin WHERE User='max' AND Pwd=''||ID='1' Also "selektiere alle User, deren Name 'max' ist und bei denen das Passwort leer ist (was nie vorkommt), oder alle User, deren ID = 1 ist". Das "Schöne" ist: Es wird so einfach der User 1 selektiert. Dass du da "max" angibst, tut überhaupt nichts zur Sache. Siehe hier: mysql> SELECT ID,User,Pwd FROM Admin WHERE User='moritz' AND Pwd=''||ID='1'; +----+--------+---------+ | ID | User | Pwd | +----+--------+---------+ | 1 | max | geheim | +----+--------+---------+ 1 row in set (0.00 sec) Aber: Da mod/authent.php immer den Namen aus dem _Formular_ in die Session als angemeldeten User übernimmt, musst du nicht mal die numerische ID kennen. Es reicht, dass das SQL-Statement _irgendeinen_ Datensatz zurückliefert; dann schreibt es den Namen aus dem Formular in die Session. Sprich, beim vorigen SQL-Statement wäre ich dann als "moritz" angemeldet, obwohl das SQL-Statement deinen Account geliefert hat. Ich brauche also nur einen beliebigen Benutzernamen sowie '||ID='1, dann komme ich rein. Richtig WÄRE im Programmcode übrigens: $result=$dbverbindung->run_sql("SELECT ID,User,Pwd FROM Admin WHERE User='" .mysql_real_escape_string($user)."' AND Pwd='".mysql_real_escape_string($pwd)."'"); Allerdings müsste man solche Fehler an ... $ grep -r ">run_sql" . | wc -l 92 ... Stellen in Ordnung bringen. Da kann man's auch gleich selber schreiben. (Dass das Problem nicht nur beim Login vorliegt, sondern bei jedem SQL-Statement, zeigt mir auch, dass das kein "Bug" ist, den der Autor dort versehentlich eingebaut hat, sondern dass ihm grundlegendes Verständnis vom Design von Webapplikationen fehlt. Ist ja nicht so, dass die PHP-Dokumentation das nicht alles haarklein erklären würde..! Da findet sich Code wie der von ihm fast 1:1 in der Doku - als Beispiel dafür, wie man's NICHT macht.) [...]
Folge: Der Kunde verpackt das installierte PHP-Script noch hinter einer .htaccess-Datei zum „übergeordneten“ Passwortschutz. Da es sich um eine Anwendung handelt, die er ausschließlich selbst einsetzt und die keinen Dritten zur Verfügung stehen wird, ist dies hier ein – vom Sicherheitsaspekt her – akzeptabler Kompromiss. Dass man üblicherweise lieber Software von Leuten einsetzen sollte, die ihr Handwerk verstehen, versteht sich hoffentlich von selbst.