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.