Hallo
@Tutorialwork (und die restlichen Leser),
Ich finde es sehr gut, dass du diese Rückfragen gestellt hast und die Sache nicht einfach als "Mir doch egal, was so ein Typ im Internet faselt" hinnimmst. Im nachfolgenden habe ich einmal versucht deine Fragen zu beantworten und dazu immer relevante Informationen verlinkt, die ich selbst für qualitativ hochwertig erachte. Am Ende findest du noch weitere Ressourcen, die dir das Leben leichter machen sollten und die du dir mal ansehen kannst. Gerne kann man mich auch privat kontaktieren, ich stehe über die üblichen Kanäle zur Verfügung. Da man auf MCSEU keinen Discord Tag verlinken kann, setze ich diesen hier einmal. Man kann mir dort gerne Fragen stellen, nur bitte keine
Metafragen.
Discord: Taminoful#0001
How to...
... implement CSRF Tokens
Das Problem des Cross-Site-Request-Forgery Angriffs besteht darin, dass gesicherte Anfragen an den Server vorgetäuscht werden können.
Das ganze Sicherheitsproblem basiert auf der Statuslosigkeit des HTTP-Protokolls. Der Browser sendet nach einmaliger Authentifizierung jedes Mal seine kompletten Sitzungsdaten an den Server (Cookies etc).
Als Angreifer kann ich mir das ganze zu nutze machen und dem Browser einen Request unterjubeln, der bspw. dafür sorgt, dass ein neuer administrativer Benutzer für den Angreifer im System angelegt wird. Hierfür muss der Angreifer lediglich das System oder in diesem Fall die URI kennen. Klickt der Administrator der Seite also auf eine Seite (Link) welche einen solchen Angriff durchführt während er eine gültige Session besitzt, wird die Aktion auf dem System ausgeführt ohne, dass er direkt etwas davon mitbekommt.
Deswegen ist es, anders als es Wikipedia in diesem Fall rät,
nicht sinnvoll einen CSRF Token als Cookie zu implementieren, dieser würde nämlich auch einfach an den Server geschickt werden.
Best Practice ist es in diesem Fall einen CSRF Token als "Hidden Field" in ein Formular einzubauen, welcher sich bei jedem Seitenabruf ändert.
Dieser wird zusammen mit den Formulardaten an den Server gesendet und dort überprüft. Das ganze ist deswegen sicher, da der Angreifer den CSRF Token nicht einfach auslesen kann um diesen so ebenfalls an den Server zu schicken.
Das ganze lässt sich zwar recht einfach selbst implementieren, es wird aber angeraten eine Hausmittel von Frameworks oder externe Libraries zu verwenden.
... set an initial password
Das Problem lässt sich recht einfach lösen, wie ich gesehen habe, hast du dich damit bereits einmal auseinandergesetzt und eine BCrypt Klasse in das Java Projekt eingebaut. Beim Erstellen des Webaccounts kannst du einen zufällig generierten Wert erzeugen den du für den Nutzer vorhältst. Diesen zufälligen String steckst du in die kryptographische Einwegfunktion um den BCrypt Hash in die Datenbank zu speichern. Den Zufällig generierten String kannst du dem Nutzer einfach via Privatnachricht ausgeben, dies ist das Initialpasswort des Accounts. Das Passwort sollte natürlich nach erstmaligem Login geändert werden.
... escape user output properly
Generell bietet es sich an seine Webanwendungen nach bekannten Design Patterns wie
MVC oder
ADR zu entwickeln. Damit spart man sich hier eine Menge arbeit. Ebenfalls nehmen einem hier Template Engines wie
Twig,
Blade oder
Smarty eine Menge ab. Twig escaped beispielsweise jede Ausgabe eine Variable, außer wenn man Twig explizit anweist dies nicht zutun. Das ist besonders hilfreich, da es schnell passieren kann eine Ausgabe zu vergessen.
Arbeitet man ohne Template Engine ist es wichtig jede Ausgabe mehrfach zu überprüfen.
Um hier das XSS Problem anzusprechen, da du nachgefragt hast:
<h3>Willkommen <?php echo $_SESSION["username"]; ?></h3>
Prepared Statements vs mysqli_real_escape_string
Zunächst dazu, warum die Funktion
mysqli_real_escape_string keinen ausreichenden Schutz vor SQL Injections darstellt.
Die Funktionsweise der Funktion ersetzt rekursiv den Inhalt eines übergebenen Strings. Hierbei werden potentiell gefährliche Symbole wie NUL (ASCII 0), \n, \r, \, ', " und Control-Z. Intern wird, wenn diese Zeichen gefunden werden ein Backslash vor das Zeichen gesetzt. Man kann die Funktion umgehen, allerdings nur unter sehr spezifischen Umständen auf die ich jetzt nicht näher eingehen werde. Wichtiger als das ist sowieso, dass beim der wahre Mehrwert beim Escaping mit dieser Funktion nicht durch das nutzen der Funktion kommt, sondern durch die verwendeten Singlequotes in den SQL Statements. Hierfür reicht ein Blick in
die Dokumentation um zu verstehen wieso. Werden einfache Anführungszeichen verwendet muss man einen Backslash vor eine sogenannte Escape-Sequenz einfügen. Da die Funktion dies für uns macht wird also bspw. statt einem \n ein \\n im Code stehen, was dazu führt, dass wir hier zwei unabhängige Zeichen ausgegeben bekommen und keine Escape-Sequenz.
Prepared Statements auf der anderen Seite benutzen Platzhalter für Parameterwerte. In MySQLi werden hierfür Fragezeichen verwendet und in PDO variable Bezeichnernamen. Dadurch, dass das Datenbanksystem die Gültigkeit von Parametern prüft bevor diese verarbeitet werden, verhindert man mit Prepared Statements effektiv SQL Injections. Hierfür ein kleines Beispiel.
<?php
$selectQuery = $pdoObject->prepare('SELECT username, email, avatar FROM users WHERE username=:username'); // Wir setzen :username als Platzhalter.
$selectQuery->bindParam(':username', $username); // Wir binden den Platzhalter an den Wert aus der Username Variable.
$selectQueryResult = $selectQuery->execute(); // Wir führen die SQL Query aus und speichern das Ergebnis.
In PDO ist es ebenfalls möglich mit ? zu arbeiten, hierfür würde man dann in der bindParam Funktion den Platzhalter durch den Index ersetzen. Wichtig hierbei ist, dass der Index nicht bei 0 beginnt sondern bei 1.
Allgemein sollte MySQLi nicht mehr verwendet werden, da viele wichtige Funktionen als Deprecated markiert wurden.
Die Dokumentation zu PDOs Prepared Statements erklärt nochmal besser warum Prepared Statements sicher sind.
Weiterführende Ressourcen:
Composer
Objektorientierung in PHP
PHP Data Objects (PDO)
Should I Use mysqli_real_escape_string With Prepared Statements in PHP?