Ein paar grundlegende Sachen zur Sicherheit in PHP / MySQLi

filament

Erfahrenes Mitglied
Hallo Leute,

ich habe mich vor ein paar Tagen noch einmal in das Thema Sicherheit eingelesen. Jetzt sind einige grundsätzliche Fragen entstanden für ein Projekt mit welchem ich auf PHP / MySQLi setze. Ich hatte hier mal im Forum das Sicherheitsthema durchgeschaut, doch dort sollte man keine Fragen posten, zumal es aus dem Jahr 2006 - 2009 ist. Weiß nicht ob das noch aktuell ist.

Verzeichnis Sicherheit

Wie genau sollte ich meine Verzeichnisse sichern? Ich möchte selbstverständlich gerade mein zu includierenden PHP Dateien absichern. Reicht hier eine htaccess Datei mit dem Inhalt deny from all? Oder sollte ich hier noch auf andere Art und Weise absichern?

Wie schütze ich denn sensible Daten von den Usern? Meine User sollen Dokumente uploaden können, die ich dann prüfen muss. Diese Dateien möchte ich gern für Außenstehende schützen. Wie stelle ich das an? Auch über htaccess?

Sicherheit beim Datei Upload

Die große Sicherheitslücke wäre dann natürlich auch der PHP Upload. Ich habe schon gelese, dass es Sinn macht den Dateinamen vorzugeben. (Außerdem werden die Dateien natürlich auf Größe und Dateiformat geprüft. Das ist Standard in meinen Uploads!)

Error Reporting

Ich wollte die Errorreports ausstellen im Live-Betrieb. Zudem werden die Errors in eine Log Datei geschrieben.

Eingaben überprüfen

Die Eingaben werden natürlich auf logische Dinge geprüft. (Stichwort: intval, Zeichenlänge, etc.) Dazu kommen dann die Prepared Statements in MySQLi bzw. vordefinierte Arrays die zugelassen sind (soweit möglich). Die Frage muss ich dann noch zusätzlich mit real_escape arbeiten? Oder reichen die prepared Statements? Gibt es noch andere Dinge die zu beachten sind?

Ausgaben prüfen

Die Ausgaben würden ich dann zusätzlich mit htmlspecialchars prüfen, damit kein Schadcode implementiert werden kann.

Login Sicherheit / Sessions

Ich möchte auf Cookies verzichten und Sessions nutzen. Ein Login gelingt nur, wenn Username, Passwort (passwort_hash), Email und ID in der Datenbank gefunden werden. Diese vier Dinge werden immer bei Eingaben geprüft, damit sich niemand als anderer User ausgeben kann.

Gibt es hier sonst noch Dinge zu beachten?


Ich glaube das waren so die grundsätzlichen Fragen / Dinge.

Danke für eure Hilfe im Voraus!

LG
Ronny
 
Hoi Ronny,

ich finde es immer wieder schön, wenn sich jemand in der Sache weitere Gedanken macht, leider passiert das ja viel zu selten. Oft weist man auf Sicherheitsprobleme in dem Code hin und dann kommt eigentlich immer "mach ich sobald es läuft" oder "läuft eh nur intern".

So, da du ja einige Fragen zu dem Thema hast, werde ich diese der Reihe nach durchgehen und beantworten.

Verzeichnis Sicherheit

Wie genau sollte ich meine Verzeichnisse sichern? Ich möchte selbstverständlich gerade mein zu includierenden PHP Dateien absichern. Reicht hier eine htaccess Datei mit dem Inhalt deny from all? Oder sollte ich hier noch auf andere Art und Weise absichern?
Je nachdem, welchen Webserver du verwendest musst du die Dateien anders absichern. Die .htaccess wird z.B. nur vom Apache gelesen und behandelt, der nginx oder lighttpd kennen das Format nicht. Wenn du die Software entwickelst und betreibst, dann weisst du ja ob es ein Apache ist. Sobald du den Code aber öffentlich machst oder andere deine Software auf ihren Servern installieren solltest du andere Codeschnipsel mitliefern, btw. aufschreiben, welche Verzeichnisse geschützt werden sollten.
Um die Dateien innerhalb von PHP abzusichern könntest du in deiner Einsprungs-PHP (index.php) eine globale Variable / Konstante definieren. Diese prüfst du dann in deinen Includes. Sobald diese nicht gesetzt ist, kannst du mit die() abbrechen und einen Fehler anzeigen.

Verzeichnis Sicherheit

Wie schütze ich denn sensible Daten von den Usern? Meine User sollen Dokumente uploaden können, die ich dann prüfen muss. Diese Dateien möchte ich gern für Außenstehende schützen. Wie stelle ich das an? Auch über htaccess?
Würde ich aus einer Mischung aus htaccess, sowie einem DL-PHP-Script umsetzen. Keine direkten Links auf die Dokumente, sondern nur auf z.B. "download.php?file=servus.docx". In dem Downloadscript kannst du dann weitere Prüfungen durchführen, wie zum Beispiel Zugriffsrechte.

Sicherheit beim Datei Upload

Die große Sicherheitslücke wäre dann natürlich auch der PHP Upload. Ich habe schon gelese, dass es Sinn macht den Dateinamen vorzugeben. (Außerdem werden die Dateien natürlich auf Größe und Dateiformat geprüft. Das ist Standard in meinen Uploads!)
Genau, der Dateiname sollte von dir generiert werden. Wenn du die Datei als Download für den Browser mit dem echten Dateinamen anbieten willst, so kannst du in einer Datenbank den Dateinamen hinterlegen und von dem Eintrag die ID als Dateinamen auf dem Server verwenden. Somit ist der Dateiname einmalig und keine Datei wird versehentlich überschrieben.
Das Problem mit vom Client vorgegebenen Dateinamen ist, dass böse User auch "../index.php" als Dateiname angeben könnten. Wenn du dies nicht vollständig überprüfst, dann könnte er die Seite übernehmen. Sprich, er könnte einfach Dateien überschreiben.
Grösse solltest du auch prüfen, wobei hier ja inder php.ini ein generelles Maximum angegeben ist. Müsste aus dem Kopf so bei 16 MiB liegen.
Dateiformat ist auch wichtig. Hier aber nicht nur auf die Dateiendung prüfen, sondern direkt auf den Typ. Hier gibt es sogenannte Magic-Bytes am Anfang einer Datei, die könntest du anschauen. Das geht zum Beispiel mit der PHP-Extension FileInfo oder dem "file"-Befehl auf jedem Linuxrechner.

Error Reporting

Ich wollte die Errorreports ausstellen im Live-Betrieb. Zudem werden die Errors in eine Log Datei geschrieben.
Richtig so. Technische Fehlermeldungen sind sehr kontraproduktiv. Zum einen verwirren diese die normalen User, zum anderen helfen diese den Angreifern mehr über die Struktur deiner Seite rauszufinden. Ganz böse ist dies zum Beispiel bei Datenbankfehlern: https://stackoverflow.com/q/6455018/1164913

Eingaben überprüfen

Die Eingaben werden natürlich auf logische Dinge geprüft. (Stichwort: intval, Zeichenlänge, etc.) Dazu kommen dann die Prepared Statements in MySQLi bzw. vordefinierte Arrays die zugelassen sind (soweit möglich). Die Frage muss ich dann noch zusätzlich mit real_escape arbeiten? Oder reichen die prepared Statements? Gibt es noch andere Dinge die zu beachten sind?
Prepared Statements reichen vollkommen aus, wenn du mit escpaes arbeitest, dann landen diese auch 1:1 so in der Datenbank. Das schöne an PS ist, dass hierbei die SQL-Befehle hart von den SQL-Daten getrennt werden. Soweit ich weiss hat das noch keiner geschafft zu umgehen und ist im Moment der sicherste Weg.
Grunsätzlich gesagt muss du alles, was irgendwie von einem Client kommt prüfen. Sprich auf dem Weg zur Datenbank mit PS arbeiten, von der DB zum Client dann entsprechend mit htmlentities() oder json_encode() (Je nachdem ob du HTML oder JSON lieferst)


Ausgaben prüfen

Die Ausgaben würden ich dann zusätzlich mit htmlspecialchars prüfen, damit kein Schadcode implementiert werden kann.
Nicht prüfen, nur escapen. Was du im Backend hast solltest du als gültig erachten und einfach nur so weitergeben. Mit dem richtigen escaping natürlich, siehe Punkt oberhalb.

Login Sicherheit / Sessions

Ich möchte auf Cookies verzichten und Sessions nutzen. Ein Login gelingt nur, wenn Username, Passwort (passwort_hash), Email und ID in der Datenbank gefunden werden. Diese vier Dinge werden immer bei Eingaben geprüft, damit sich niemand als anderer User ausgeben kann.
Gibt es hier sonst noch Dinge zu beachten?
In deinem ersten Satz widersprichst du dir selbst ;) Sessions werden zwar auf dem Server gespeichert, aber der Client speichert die Session-ID in einem Cookie. Aber ich weiss was du meinst, man sollte möglichst wenig Daten in den Cookies speichert und falls möglich auch verschlüsseln und signieren.
Ich bevorzuge zum Login in etwa folgendes SQL: "SELECT COUNT(1) FROM users WHERE (username = ? or email = ?) and password = ?". Eine 0 bedeutet irgenwas ist falsch, eine 1 bedeutet dass der User gefunden wurde. Sollte es einen User mit dem Namen "test@foo.bar" und einen User mit der EMail "test@foo.bar" geben, so liefert die Query 2 zurück, was ich als Fehler interpretiere. Wichtig ist hier noch, dass du dem User nicht sagst, was falsch ist. Also nicht "Unbekannter Benutzer" oder "Falsches Passwort", sondern lediglich ein "Es konnte kein Benutzer mit den eingegeben Daten gefunden werden".
Zu beachten ist hier noch, dass du einen aktuellen Hashalgorithmus verwendest. Also nicht md5 oder sha1, sondern entweder mindestens sha256 oder noch besser PBKDF oder bcrypt. Hier solltest du auch nicht nur "einmal" hashen, sondern mit mehreren Iterationen / Rounds arbeiten. Ich verwende z.B. BCrypt SHA256 mit 50_000+ rounds.

Falls du noch Fragen hast, einfach her damit :)

Grüsse,
BK
 
Zuletzt bearbeitet:
Je nachdem, welchen Webserver du verwendest musst du die Dateien anders absichern. Die .htaccess wird z.B. nur vom Apache gelesen und behandelt, der nginx oder lighttpd kennen das Format nicht. Wenn du die Software entwickelst und betreibst, dann weisst du ja ob es ein Apache ist. Sobald du den Code aber öffentlich machst oder andere deine Software auf ihren Servern installieren solltest du andere Codeschnipsel mitliefern, btw. aufschreiben, welche Verzeichnisse geschützt werden sollten.
Um die Dateien innerhalb von PHP abzusichern könntest du in deiner Einsprungs-PHP (index.php) eine globale Variable / Konstante definieren. Diese prüfst du dann in deinen Includes. Sobald diese nicht gesetzt ist, kannst du mit die() abbrechen und einen Fehler anzeigen.

Also ich hab einen Apachen. Hab einen managed Server von einem Host. Damit bin ich immer am besten gelaufen, weil ich mich mit Sachen betreffend Server nicht gut genug auskenne. Das vertraue ich lieber Leuten an die Ahnung haben.

Das mit der Variable klingt gut. Ich habe mal gelesen dass es auch Codes gibt die überprüfen, ob ein direkter Aufruf erfolgt ist. Kennst Du solche? Sind diese sinnvoll bzw. Besser als eine konstante Variable? Zu dem Schutz über deny from all hast Du leider nichts gesagt. Wäre das sicher?

Es geht ja vor Allem darum zu unterdrücken das Dateien direkt aufgerufen werden sollen. Sprich ich rufe die config.php auf weil ich als Angreifer mitbekommen habe die liegt in Domain/meinordner/config.php. Dann hätte ich schonmal die Infos zur Datenbank.

Würde ich aus einer Mischung aus htaccess, sowie einem DL-PHP-Script umsetzen. Keine direkten Links auf die Dokumente, sondern nur auf z.B. "download.php?file=servus.docx". In dem Downloadscript kannst du dann weitere Prüfungen durchführen, wie zum Beispiel Zugriffsrechte

Also als erstes geht es nicht nur darum dass die User darauf zugreifen können. Ich persönlich muss da drauf, um diese zu verifizieren. Die Dateien sollen vor Allem für Außenstehende gesperrt sein. Ich selbst kann ja via FTP drauf. Kein Problem. Der User kann via Login drauf.

Das direkte PHP Script drauf hat welchen Vorteil? Die User wissen nicht wo die Datei liegt? Wie sähe das in der Praxis aus? Ich müsste ja definitiv gewisse Arrays vorgeben, um zu prüfen ob die Datei existiert. Damit keiner hier über GET was einschleusen kann, richtig? Oder reicht es eine Datei Datenbank zu machen wie Du weiter unten vorgeschlagen hast und dann per prepared zu prüfen?

Genau, der Dateiname sollte von dir generiert werden. Wenn du die Datei als Download für den Browser mit dem echten Dateinamen anbieten willst, so kannst du in einer Datenbank den Dateinamen hinterlegen und von dem Eintrag die ID als Dateinamen auf dem Server verwenden. Somit ist der Dateiname einmalig und keine Datei wird versehentlich überschrieben.
Das Problem mit vom Client vorgegebenen Dateinamen ist, dass böse User auch "../index.php" als Dateiname angeben könnten. Wenn du dies nicht vollständig überprüfst, dann könnte er die Seite übernehmen. Sprich, er könnte einfach Dateien überschreiben.
Grösse solltest du auch prüfen, wobei hier ja inder php.ini ein generelles Maximum angegeben ist. Müsste aus dem Kopf so bei 16 MiB liegen.
Dateiformat ist auch wichtig. Hier aber nicht nur auf die Dateiendung prüfen, sondern direkt auf den Typ. Hier gibt es sogenannte Magic-Bytes am Anfang einer Datei, die könntest du anschauen. Das geht zum Beispiel mit der PHP-Extension FileInfo oder dem "file"-Befehl auf jedem Linuxrechner.

Okay das werde ich berücksichtigen.

16 MB brauchen die definitiv nicht.
Mal eine ergänzende Frage zum Thema Antivirus. Gibt es hier eine Möglichkeit zu prüfen? Mein Provider sagt zwar die lassen täglich alles durchlaufen. Aber sicher ist eben sicher, wenn es die Möglichkeit gibt.

Nicht prüfen, nur escapen. Was du im Backend hast solltest du als gültig erachten und einfach nur so weitergeben. Mit dem richtigen escaping natürlich, siehe Punkt oberhalb.

Hab ich mich blöd ausgedrückt. Das meinte ich natürlich!

In deinem ersten Satz widersprichst du dir selbst ;) Sessions werden zwar auf dem Server gespeichert, aber der Client speichert die Session-ID in einem Cookie. Aber ich weiss was du meinst, man sollte möglichst wenig Daten in den Cookies speichert und falls möglich auch verschlüsseln und signieren.
Ich bevorzuge zum Login in etwa folgendes SQL: "SELECT COUNT(1) FROM users WHERE (username = ? or email = ?) and password = ?". Eine 0 bedeutet irgenwas ist falsch, eine 1 bedeutet dass der User gefunden wurde. Sollte es einen User mit dem Namen "test@foo.bar" und einen User mit der EMail "test@foo.bar" geben, so liefert die Query 2 zurück, was ich als Fehler interpretiere.
Zu beachten ist hier noch, dass du einen aktuellen Hashalgorithmus verwendest. Also nicht md5 oder sha1, sondern entweder mindestens sha256 oder noch besser PBKDF oder bcrypt. Hier solltest du auch nicht nur "einmal" hashen, sondern mit mehreren Iterationen / Rounds arbeiten. Ich verwende z.B. BCrypt SHA256 mit 50_000+ rounds.

Habe ich schon gelesen. Md5 und sha sind unsicher. Die meisten empfehlen die PHP hash Funktion. Die werde ich nutzen dazu. Die Frage ist nur, muss ich noch einen vorgegebenen Array hinzufügen der fest definiert wird von mir um mehr Sicherheit zu haben? Mir fällt gerade der Fachausdruck dazu nicht ein ;)

Macht es Sinn den Nutzernamen oder auch die ID (Hash von "User ID #")ebenfalls als Hash in der Session zu speichern? Was unsicher sein wird, ist sicher loggedin=true richtig? Muss ich dann bei jeder Aktion die Daten mit der User Tabelle über mysqli prüfen? Oder geht das auch einfacher?

Danke das Du dir die Zeit genommen hast so ausführlich zu Antworten! :)
 
Also ich hab einen Apachen. Hab einen managed Server von einem Host. Damit bin ich immer am besten gelaufen, weil ich mich mit Sachen betreffend Server nicht gut genug auskenne. Das vertraue ich lieber Leuten an die Ahnung haben.
Perfekt, das ist die richtige Einstellung um eine Webseite zu betreiben :)

Das mit der Variable klingt gut. Ich habe mal gelesen dass es auch Codes gibt die überprüfen, ob ein direkter Aufruf erfolgt ist. Kennst Du solche? Sind diese sinnvoll bzw. Besser als eine konstante Variable?
Keine Ahnung, ich kenne mich zwar mit PHP aus, habe aber schon seit längerem nicht mehr so viel in dem Bereich zu tun. Müsstest du aber per Google und Tests rausfinden können. Ich kenne halt nur die Variante mit der Konstante.

Zu dem Schutz über deny from all hast Du leider nichts gesagt. Wäre das sicher?
Habe ich etwas umständlich geschrieben. Solange die Seite auf einem Apachen läuft: Ja

Es geht ja vor Allem darum zu unterdrücken das Dateien direkt aufgerufen werden sollen. Sprich ich rufe die config.php auf weil ich als Angreifer mitbekommen habe die liegt in Domain/meinordner/config.php. Dann hätte ich schonmal die Infos zur Datenbank.
Nein, da das Script ja durch die PHP-Endung interpretiert und nicht ausgegeben wird. Von daher würde man lediglich eine leere Seite sehen. Die Endung ist wichtig, habe auch schon "config.php.inc" gesehen. Hier hättest du ein Problem, aber mit "config.inc.php" oder "config.php" nicht.
Selbiges gilt für alle anderen Includes. Solange diese nichts von selber ausgeben erscheint nur eine leere Seite. Dies liefert nur die Info, dass die Datei auf dem Server existiert aber nicht den Inhalt. Aber auch die Info, wo eine Datei liegt sollte nicht unbedingt nach aussen. Von daher auch per .htaccess schützen und gut ist.

Also als erstes geht es nicht nur darum dass die User darauf zugreifen können. Ich persönlich muss da drauf, um diese zu verifizieren. Die Dateien sollen vor Allem für Außenstehende gesperrt sein. Ich selbst kann ja via FTP drauf. Kein Problem. Der User kann via Login drauf.
htaccess gilt nur für den Apachen, dem FTP ist die egal. Wenn der User per Login drauf soll, dann kannst du das nicht alleine per htaccess lösen sondern musst über ein PHP-DL-Script gehen, welche den Login prüft und die Datei ausliefert.

Das direkte PHP Script drauf hat welchen Vorteil? Die User wissen nicht wo die Datei liegt? Wie sähe das in der Praxis aus? Ich müsste ja definitiv gewisse Arrays vorgeben, um zu prüfen ob die Datei existiert. Damit keiner hier über GET was einschleusen kann, richtig? Oder reicht es eine Datei Datenbank zu machen wie Du weiter unten vorgeschlagen hast und dann per prepared zu prüfen?
Vorteil? Siehe oben. Bei einem direkten Link kannst du nicht prüfen, ob der User angemeldet ist. Apache sieht die Datei aus dem GET, findet diese und liefert diese einfach klanglos aus. PHP Download scripts müsstest du eigentlich per Google finden. Einige davon sind sicherlich nicht sicher oder gar falsch, aber vom Aufbau / der Idee her sollte es genügend Input sein, dass du das selber schreiben kannst. Ist gar nicht so kompliziert wenn man mal weiss, was man machen soll :)
Ich würde die Dateiennamen (auf Server und für Client) in einer Datenbanktabelle ablegen. ID, StorageName, ExternalName.
Ein GET auf "/download.php?id=42" löst per SQL also zu der Datei "data/42.pdf" (StorageName) auf, welcher dem Client als "foobar.pdf" zum Download angeboten wird.

Mal eine ergänzende Frage zum Thema Antivirus. Gibt es hier eine Möglichkeit zu prüfen? Mein Provider sagt zwar die lassen täglich alles durchlaufen. Aber sicher ist eben sicher, wenn es die Möglichkeit gibt.
Von PHP aus? Nur über externe Programme. Im Linux-Server Umfeld kenne ich da zum Beispiel nur ClamAV. Den könntest du sicher über "shell_exec" aufrufen und das Ergebnis verarbeiten.

Habe ich schon gelesen. Md5 und sha sind unsicher. Die meisten empfehlen die PHP hash Funktion. Die werde ich nutzen dazu.
Ich persönlich verwende die crypt-Function mit entsprechend sicheren Parametern. Ich muss aber auch Passwörter erzugen, welche ich über PAM auch verifizieren kann. (Linux-System-User) Ich denke für deine Anwendungsfälle sollte die php-hash auch passen. Aber wie gesagt, auf die Parameter achten :)

Die Frage ist nur, muss ich noch einen vorgegebenen Array hinzufügen der fest definiert wird von mir um mehr Sicherheit zu haben? Mir fällt gerade der Fachausdruck dazu nicht ein ;)
Was meinst du mit Array? Was willst du da drin speichern?

Macht es Sinn den Nutzernamen oder auch die ID (Hash von "User ID #")ebenfalls als Hash in der Session zu speichern? Was unsicher sein wird, ist sicher loggedin=true richtig? Muss ich dann bei jeder Aktion die Daten mit der User Tabelle über mysqli prüfen? Oder geht das auch einfacher?
In der Session speicherst du normalerweise die UserId und alle Daten, welche du oft benötigst. Das spart teure Abfragen an die Datenbank. Was hilft dir nur die Info, dass jemand eingeloggt ist ohne zu wissen wer?
Hashing macht wenig Sinn, da du ja z.B. die UserID für Anfragen an die Datenbank brauchst. Hier würde ja ein Hash nicht viel helfen:
INSERT INTO blog_post (ID, UserID, Test) VALUES (NULL, ?????, 'lorem ipsum');

Die Session selbst bleibt auf dem Server, der Client hat keinerlei Zugriff auf diese. Von daher kannst du unsensible Daten da drin einfach ablegen. Sensible Daten, wie Konto-Infos würde ich (wenn du wirklich nicht anders kannst und das in die Session legen musst) dann vorher aber Verschlüsseln, z.B. per AES.

Grüsse,
BK
 
Mit dem Array meinte ich eine feste Erweiterung des Passwortes. Sprich sowas in die Richtung: $zu hashendes_pw = $pw."Fester Ausdruck";

Hoffe das ich mich verständlich ausgedrückt habe

Aber wenn ich die User ID offen lege, dann könnte ein Angreifer doch diese abändern in den Cookies oder nicht? Wenn er dann da statt seiner 217 die 1 eingibt, weil er denkt, Nunja das ist bestimmt der Admin, dann wäre er doch auch drin, oder?

Vielleicht sollte ich mir das nochmal genau ansehen. Kennst Du ein gutes Tutorial für einen Login mit Sessions, wo Sicherheit mit thematisiert wird?
 
Hi

Etwas sehr spät hier, das nimmt teilweise noch auf den ersten Beitrag Bezug:

Um von unten anzufangen...
Gibt es hier sonst noch Dinge zu beachten?
Ja, unendlich viele.

...Was ich damit sagen will:
Ob etwas akzeptabel sicher ist hängt immer davon ab, was es ganz genau macht, wie und in welcher Umgebung es verwendet wird, wogegen man sich genau schützen will (sehr genau), und vom Level der Paranoia (nicht abwertend gemeint). Es gibt eine Reihe "Best Practices", aber die ersparen einem nicht, beim eigenen Programm genau nachzudenken (bzw., wenn es irgendwie möglich ist, mehrere erfahrene externe Leute zusammen drüber nachdenken zu lassen).

Und zweitens, es gibt keine absolute Sicherheit. Auch wenn man noch so viel macht gibt es immer ein Risiko, dass wer rein kommt. Weil man nicht allwissend ist, Fehler machen kann, nicht jede Codezeile aller Programme/OS/... prüfen kann, ...

...

Zu den Sachen, die du selber angesprochen hast, ergänzend zu Bratkartoffel:

Verzeichniszugriff verbieten:
"deny from all" heißt inzwischen "Require all denied".

Uploads:
Wie Bratkartoffel sagt, das Problem mit ../ um in höhere Verzeichnisse zu kommen, und dass die Endung .png nicht bedeutet dass es wirklich ein Bild ist...

Der ideale Name wäre eine vom Server zugewiesene Nummer und nicht mehr (also auch ohne Endung); die restlichen Infos in einer DB oder so (Zum Typ-Finden auch das Stichwort "Mime"). Mögliche weitere Probleme, wenn man ../ usw. prüft, aber den Dateinamen oder einen Teil trotzdem übernimmt, sind zB.: Je nach OS problematische Dateinamen wie CON auf Windows, teilweise Shellsyntax wie "echo bla >/a/b/c", ungültige Bytemuster (Unicode-Verwirrtheiten, Backspace, ...), Endungen wie ".php" undd ann aufrufen, usw.

Zum Inhalt, auch wenns noch so geprüft ist, wärs ideal die Daten neu zu kodieren. Wenn man zB. nur Bilder akzeptiert, einmal zum BMP konvertieren, und dann wieder zu PNG um speicher zu sparen. Bei Zips einmal entpacken und wieder packen. PDFS auch neu durch Ghostscript lassen. Usw. (Wenn man natürlich beliebige Dateidarten akzeptiert geht das nicht so ganz). Gründe sind Möglichkeiten von bestimmten Dateiarten, Bytemuster zu enthalten, die entweder beim Server oder im Browser (bäsartige) Probleme verursachen können (speziell ohne Updates), oder versteckte Infos die beim Nutzertracking helfen (speziell in Zusammenarbeit mit Browsercaches usw.)

Hier noch etwas mehr: https://www.owasp.org/index.php/Unrestricted_File_Upload

Errors:
Davon, immer automatisch in Dateien zu loggen, würde ich abraten (uA. DOS usw. PHP ist sehr verbose. Außerdem gleichzeitiger Zugriff).

Außerdem beachten, dass PHP Fehler und Startup-Fehler hat. Die ersten sind die "normalen" wie zB. DB-Exceptions, nicht vorhandene Variablen usw., die zweiten sind Parsefehler usw. bevor mit dem Ausführen überhaupt erst angefangen wird. In der PHP-Datei error_reproting usw. ausschalten hilft da natürlich nicht, es muss in der Serverconfig erfolgen; und außerdem gibt es eine eigenen separate Einstellung dafür.

Eingabenprüfung:
Bei SQL ist noch zu beachten, dass alles (DB-Spalten, eigentliche Daten in PHP, und auch die Einstellungen für die DB-Verbindung mit Mysqli/PDO) charsetmäßig zusammenpasst. In ein paar bestimmten Kombinationen von nicht-zusammenpassenden kann man trotz escapen/preparen Steuerzeichen reinbringen.

Btw., ein mögliches Problem ist auch "zu lang für die DB-Spalte".

Etwas im weiteren Sinn: "Allen" externen Sachen nicht trauen. Userformulareingaben klar, Dateiuploads klar, aber auch zB. Anfragen an andere Webdienste, Inhalte der eigenen DB (zB. hatte ein Bekannter einen Fall, wo sich User ID soundso löschen wollte, dieser User hatte in der DB einen Benutzernamen NULL, und über zehn Ecken hat der PHP-Code deshalb kein WHERE zum DELETE gehängt ... alle User weg), die Sachen die ein Nutzer angelich vom eigenen Server im Browsercache hat, Ajax-Requests der eigenen Seite, ...

und auch keinem Clientgerät in irgendeiner Form trauen. Auf PCs kann wirklich alles (absichtlich oder nicht) ausgelesen und/oder gefälscht werden, Smartphones kommen sowieso >50% mit Malware schon aus der Fabrik, ...

und serverseitig: Suchen ob der Prozessor vPro, AMT, ME oder irgendso ein Buzzword unterstützt und hoffen dass es nicht so ist. (Worum es geht: Eine in den Prozessor eingebaute Fernsteuerungsmöglichkeit, kann buchstäblich alles mit dem Computer machen, sogar wenn ausgeschaltet. Und es gibt inzwischen mehr als 10 bekannte Sicherheitsprobleme (ob die absichtlich waren lass ich mal offen)). Halbe Lösung: Bios-Update bzw. dem Hoster Druck machen, und prüfen ob es OS-seitig wenigstens nicht eingeschaltet ist (https://security.stackexchange.com/...ism-sbt-escalation-of-privilege-vulnerability mehrere Antworten lesen). Bessere Lösung, wenn man an den Server drankommt und sich das zutraut: Firmware modifizieren https://github.com/corna/me_cleaner (Das ist für Intel; AMD hat aber auch sowas Ähnliches. Leider gibts dort noch nicht so ein schönes Entferntool).

Ausgaben:
Wie Bratkartoffel sagt ist htmlspecialchars keine Prüfung, sondern eine Umwandlung von der Ausgabe. Speziell um Text, der HTML und/oder JS enthält, im Browser nicht auszuführen, sondern als Text anzuzeigen.

Zu den Downloads über PHP: Nur, damit es nicht vergessen wird, das Verzeichnis mit dem eigentlichen Dateien auch über htaccess sperren.

Login:
passwort_hash ist gut. Die Zusatzdaten, möglichst aus /dev/urandom, werden "Salt" genannt. Idealerweise ein eigener pro Benutzer, in der DB gespeichert.

Für die Session-ID (und auch andere Sachen) aufpassen, dass PHP einen ordentlichen CSPRNG verwendet: Halbwegs aktuelle Version, nicht Windows, und evt. zum Test mal ein paar tausend neu generierte Session-IDs in eine Datei schreiben (binär, nicht dezimal), und den Linux-Befehl "ent" darauf laufen lassen. (Falsche PHP-Einstellungen und/oder manche Virtualisierungslösungen sind da leider ein möglicher Grund, warum die Daten nicht gut genug sein könnten).

Aufpassen wo und wie die Sessiondaten am Server im Filesystem gespeichert werden (Zugriffsrechte für andere Prozesse usw.)

Beim Sessioncookie aufpassen, dass die Flags gesetzt sind, die JS-Zugriff verbieten und ggf. die Übertragung nur über HTTPS erlauben.

(Würde sogar empfehlen, ganz auf das PHP-Sessionsystem zu verzichten, und selber sowas Ähnliches mit DB-Speicherung implementieren. Also eine Tabelle SessionID-Daten-Ablaufdatum, immer wieder mal abgelaufene Sessions rauslöschen, die neuen SessionIDs vom echten /dev/urandom holen, ...)

...

Was mir sonst grad für das PHP-Umfeld so in den Sinn kommt:

Konfiguration:
DB-Verbindungen nur von lokal zulassen.

PHP nicht als Apachemodul laufen lassen (sondern FCGI).

SSH: Keys verwenden (min. 4096...), und zusätzlich trotzdem noch serverseitiges (!) Passwort (ja das geht, auch wenn viele Onlinequellen was anderes behaupten. Ist relativ neu, dieses Feature).

Updates der Software! . Im Idealfall min. täglich. Mit einer ordentlich gewarteten Distribution (Debian Stable...) ist es auch überhaupt kein Problem, das automatisiert zu machen, und nur große Versionswechsel alle paar Jahre händisch. (Aber trotzdem Logs prüfen, was da wann passiert).
(Und auch die in PHP laufenden Sachen, wie Xenforo hier usw., nicht vergessen).

Apache: Symlinks und Dateien mit falschem Besitzer nicht ausliefern (da gibts Einstellungen, weiß sie nicht auswendig...)
(Leider gibt es ein paar Websoftwares, die so nicht funktionieren. Aber das merkt man dann schon).

HTTPS. Unterstützen, mit ordentlichem Zertifikat (4096), DNS-Eintrag, und idealerweise alle HTTP-Sachen zu HTTPS umleiten. Wenn das so ist auch bei "allen" Cookies aufpassen, dass sie das HTTPS-only-Flag haben. (Falls das die Websoftware nicht mitmacht gibts auch PHP-seitig eine Einstellung). Bei den Einstellungen aufpassen dass die neueste TLS-Version bevorzugt wird.
Mit https://www.ssllabs.com/ssltest/ und gängigen Browser testen.
Wenn man das alles hat, und sich sicher ist dass es funktioniert, evt. auch noch HSTS konfigurieren (sagt den Browser praktisch "mich (Server) gibts nur über HTTPS, und wenn (angeblich) ich euch in Zukunft ohne kontaktieren sollte, traut dem nicht". Aber wirklich nur, wenn HTTPS auch funktioniert. Wenns plötzlich serverseitig nicht mehr geht ist die Webseite damit unerreichbar (zumindest für Clients, die in der letzten Zeit schon mal da waren).

Mailserver: Nicht nur von den Leuten, die Mails lossenden, oder die eigenen Mails anschauen wollen, Passwörter verlangen; sondern auch, wenn ein anderer Mailserver dem eigenen sagt, ein Mail an eine dritte Stelle weiterzuleiten (also an etwas, das kein lokaler Benutzer ist). Sonst ist man bald auf den Spamblocklisten. Außerdem gibts so Absendergarantiesachen wie DKIM und SPF.

DNSSEC/DANE usw.: Wenn man von PHP aus Anfragen an andere Domains macht (und/oder selbst einen DNS-Server hat), aufpassen dass da keine Fälschung passiert...

Iptables: ICMP-Anfragen außer Ping komplett droppen. Und natürlich eingehende Verbindungen nur auf bestimmten Ports erlauben.

ContentSecurityPolicy: Ein HTTP-Header, den PHP oder so senden kann, und dem Browser damit mitteilt von welchen Domains Bilder, JS, und andere Sachen auf den eigenen Seiten erlaubt sind. Wenn man keine externe Werbung eingebaut hat kann man zB. sagen, dass JS auf der eigenen Seite auch von der eigenen Domain stammen muss. Oder man kann alle Nicht-HTTPS-Sachen blocken, falls sich irgendwie was reingeschlichen hat. Usw.

Für PHP kann man bestimmte Funktionen verbieten, wie zB. eval (möglichst vermeiden, leider verwenden es aber einige PHP-Softwares ... also nein im eigenen Code, und wenn möglich global verbieten), Shellbefehl-Ausführungen (system, popen...) falls nicht gebraucht, ...

Es gibt auch eine Einstellung, für fopen, file_get_contents usw. externe Urls zu verbieten.

Kein FTP verwenden. SFTP (Teil von SSH, mit den selben Keys...)

Weiteres:
Fail2ban. Idealerweise auch für Logins auf Webseiten, wenn die SOftware das mitmacht (manche haben auch von sich aus ein ähnliches Feature).

Ggf. Snort, Rkhunter, Selinux, usw.usw.

Bei allem, wo es möglich ist, eher Whitelists statt Blacklists.

In OOP-Programmen: In einer Klasse die übergebenen Parameter prüfen, auch wenn man denkt dass sie schon vorher außerhalb der Klasse geprüft wurden.

Nebenläufigkeit: Mit DB-Transaktionen umgehen lernen. Alles, was mehr als eine SQL-Anweisung ist (zB. etwas auslesen, in PHP ändern, wieder speichern; oder prüfen ob vorhanden und dann auslesen) hat sonst das Risiko, dass "zwischen" den Anweisungen eine Änderung von wo anders passiert (zB. eine weitere PHP-Instanz, durch einen anderen Client gerade am laufen).
Und aus diesem Grund, in einem derartigen PHP-Setting, auch unbedingt keine Dateien schreiben/lesen, wo mehrere PHP-Instanzen gleichzeitig aktiv sein könnten. (es gibt verschiedene Lockingmöglichkeiten in Linux, aber die haben alle eines gemeinsam: So wirklich brauchbar sind sie nicht. Über die Probleme könnte man ein Buch schreiben ... jedenfalls, es ist nicht nur theoretisch: Es gibt ein sehr reales Risiko dass Filelocks nicht so locken wie du das gern hättest.)

In einer Fehlersituation im Code (nicht eingeloggt, Daten haben nicht gepasst, ...), wo man eine Fehlermeldung ausgibt und/oder auf eine andere Seite redirected (Loginseite oder so): Sicherstellen, dass das Programm dann sofort beendet wird. Weil: Es gibt Fälle, wo man zB. wegen Nicht-Eingeloggtheit bei einer privaten Seite auf die Loginseite umgeleitet wird, und das PHP-Programm nach Senden des Umleitungsheaders trotzdem die privaten Infos rausschreibt. Wenn man mit einem speziellen Client arbeitet, dem man sagen kann "Umleitungen ignorieren", sieht man alles...

Verwende kein Wordpress und kein Flash. Beide sind so grauenhaft unsicher dass mir allein das Schreiben davon schon wehtut.

In Situationen wo ein Nutzer Bilder (etc.) von externen Quellen angeben kann, die dann in einem img-Tag (etc.) für andere Nutzer von der eigenen Seite angezeigt wird (also zB. das Forum hier; man kann mit dem BB-Tag [img] eine Bildurl von einem anderen Server angeben, und das Bild: wird im Beitrag dann angezeigt):
Was hier viel besser wäre: Der Server erkennt das beim Beitragsspeichern, ladet das Bild (der (eigene) Server!), speichert es, und gibt zeigt dann anderen Benutzern das lokale Bild an. Damit der Speicherverbrauch bei vielen Bildern ok bleibt, zB. nur die Bilder lokal haben die in der letzten Woche angefragt wurde (dann leeren. Falls es wieder angefragt wird neu laden, an den Benutzer weiterleiten, und wieder für eine Woche speichern).
3 Gründe:
a) Der Server kann die selben Prüfungen und Sanitierungen wie bei den Dateiuploads oben machen.
b) Ein HTTP-Bild auf einer HTTPS-Seite anzeigen erzeugt Browserwarnungen und unterminiert die ganze HTTPS-Sicherheit. Der eigene Server kann das lokal gespeicherte Bild auch über HTTPS ausliefern.
und, wichtig, c) Datenschutz. Wenn die originale Bildurl verwendet wird kann der fremde Server, wo das Bild ist, genau mitprotokollieren, welche IPs wann das Bild anschauen, wo auf der eigenen Seite es überall verwendet wird, falls es sowas wie Foren-Benutzernamen gibt: welcher Forenbenutzer zu welcher IP gehört, welcher Browser/OS/Bildschirmgröße/... vom Forenbenutzer verwendet werden, Cookies können gesetzt werden (und auf ganz anderen Webseiten wieder ausgelesen: genaues Tracking), und mehr. Durch die Zwischenstation am eigenen Server ist davon nichts mehr möglich.

Auf Seiten, wo man sich registriert, einloggt, Passwort ändert usw.: Unbedingt keine externen JS-Dateien laufen lassen (zB. Werbung, Google Analytics, usw.usw.usw.). Wie: CSP, oben schon erwähnt. Je nach Anwendungsfall auf weitere sehr private Seiten ausdehnen. Und "externe" JS-Dateien sind hier nicht nur welche von anderen Servern, sondern auch welche vom eigenen Server die ursprünglich von wo anders kommen (manche Werbeanbieter geben einem die JS-Dateien zu selberhosten).
Grund: JS kann in der Seite alles machen, zB. auch das Passwort während dem Eingeben mitlesen, und dann zum Speichern an den Server des Scripterstellers schicken ... (es gibt zwar eine "Same Origin Policy", ein Sicherheitsfeature das genau sowas verhindern soll, aber leider gaukelt das einem die Sicherheit nur vor. Ohne die harte CSP-Lösung ist es lächerlich einfach, Passwörter mitzulesen)

edit damit es hier auch noch steht: Backups und Raid ("und", nicht "oder").

...

Mehr Best-Practice-Sachen:
https://www.owasp.org/index.php/Category:Attack
https://www.owasp.org/index.php/Category:Control
https://www.owasp.org/index.php/Category:Vulnerability
https://www.owasp.org/index.php/Category:Principle

...

Ich weiß, das war viel.

Wenn zum irgendwas mehr Details gebraucht werden, immer gerne.
 
Zuletzt bearbeitet:
Mit dem Array meinte ich eine feste Erweiterung des Passwortes. Sprich sowas in die Richtung: $zu hashendes_pw = $pw."Fester Ausdruck";

Hoffe das ich mich verständlich ausgedrückt habe

Aber wenn ich die User ID offen lege, dann könnte ein Angreifer doch diese abändern in den Cookies oder nicht? Wenn er dann da statt seiner 217 die 1 eingibt, weil er denkt, Nunja das ist bestimmt der Admin, dann wäre er doch auch drin, oder?

Vielleicht sollte ich mir das nochmal genau ansehen. Kennst Du ein gutes Tutorial für einen Login mit Sessions, wo Sicherheit mit thematisiert wird?
Das was du meinst ist ein Salt, wie von sheel erwähnt. Ja, man sollte Salts verwenden, hierbei aber am Besten pro User einen unterschiedlichen.
Zum Login noch einen Nachtrag: 2FA ist ein sehr schönes Konzept, ich baue das inzwischen überall wo ich kann. Zum Beispiel als TOTP: https://de.wikipedia.org/wiki/Time-based_One-time_Password_Algorithmus

Die Daten der Session stehen nicht im Cookie, das ist das entscheidende. Der Client hat lediglich eine (gehashte), zufälle Buchstabenfolge, welche mit einem Zustand auf dem Server verbunden ist. Der Client schickt als Header "Cookie: PHPSESSID=fdshkfjdfkj8234hjksdhjf" und der Server lädt die Session mit dem angegeben Namen und stellt die Daten da drin dann als $_SERVER zur Verfügung. Zu keinem Zeitpunkt hat der Client Zugriff auf die Session Daten!

Grüsse,
BK
 
Um ehrlich zu sein hab ich die ersten Zeilen am Ende bereits wieder vergessen beim Lesen :D

Ganz schön viel (Mega viel!) Input, der mich fragen lässt ob man überhaupt sicher programmieren kann :rolleyes:
 
Hi,

100%ige Sicherheit gibt es nicht, du kannst nur den Aufwand, Lücken zu finden erhöhen. Jeder macht Fehler und jede Software hat Fehler. Ist nur eine Zeit der Frage, bis die einer findet und welche Auswirkungen diese haben. Stack-Overflow / Page Faults / Off-By-1 etc.

Das Thema Backups ist auch sehr wichtig, wenn es auch nicht direkt mit der Sicherheit zusammenhängt. Regelmässige Backups auf externen Servern, mit einem RAID und ansonsten auch anständig gesichert. Regelmässig prüfen, ob die Backups laufen, vollständig und widerherstellbar sind.

Grüsse,
BK
 
Ihr habt ja beide geschrieben ein automatisches Loggen von Errors ist nicht empfehlenswert.

Wie logge ich denn sonst?
 

Neue Beiträge

Zurück