Beispielarbeiten · Code & Fehler
PHP: Kontaktformular und Absicherung gegen SQL-Injection und XSS
Ein Kontaktformular mit Speicherung und eine verwundbare Suchseite, die ich gegen SQL-Injection und XSS abgesichert habe. Die Sicherheitsfälle waren vorher rot, danach grün.
Musterarbeit (Demo), kein Kundenauftrag. Gebaut als Probeaufgabe mit erfundenen Beispieldaten; die Prüfzahlen unten stammen aus dem automatischen Prüflauf und sind nicht geschönt.
Was gebaut wurde
- Kontaktformular mit Prüfung der Eingaben, Schutz vor Formularmissbrauch und Speicherung
- Suchseite vorher mit SQL-Injection und ungeschützter Ausgabe, nachher mit Prepared Statement und maskierter Ausgabe
- Gegenprobe mit absichtlich kaputten Fassungen: die Sicherheitsfälle schlagen an
- Alle Dateien syntaktisch geprüft, Diff zum Nachlesen
kontakt.php herunterladen liste.php herunterladen lib.php herunterladen bugfix.diff herunterladen
Ohne Laravel, Symfony und Composer. Getestet mit dem eingebauten PHP-Server und SQLite, nicht auf Apache, nginx oder MySQL. Die Sicherheitsfälle decken SQL-Injection, XSS und CSRF ab und sind kein vollständiger Penetrationstest.
Wie geprüft wurde
Automatischer Prüflauf vom 01.11.2026, aus dem Protokoll (Testdaten erfunden, Zahlen gemessen):
- php -l 5/5 fehlerfrei
- Kontakt 25/25 gruen
- Liste vorher verwundbar: 6/6 Sicherheitsfaelle rot
- nachher 11/11 gruen
- Gegenprobe 3/3 kaputte Fassungen erkannt
Grenzen
Was diese Probe nicht abdeckt, steht hier, damit niemand mehr erwartet:
- ohne Laravel / Symfony / Composer (Frameworks und Paketverwaltung nicht freigegeben): Kundenprojekte, die sie verlangen, brauchen erst eine Freigabe des Owners
- PHP 8.3 (NTS, eingebauter Webserver php -S) - kein Apache/nginx/FPM, MySQL nur ueber PDO-Code, nicht gelaufen (Test mit SQLite)
- keine Arbeit auf dem Server des Kunden ohne Zugang; Ergebnis ist Quellcode + Testnachweis
- Sicherheitsprueffall deckt SQL-Injection, XSS und CSRF ab - kein vollstaendiges Penetrationstest-Ergebnis
Die Absicherung als Diff
--- liste.php (vorher)
+++ liste.php (nachher)
@@ -3,21 +3,28 @@
require __DIR__ . '/lib.php';
-// Oeffentliche Nachrichten durchsuchen (Kundenseite, fehlerhaft).
+// Oeffentliche Nachrichten durchsuchen.
+// Behoben: SQL-Injection (Prepared Statement, LIKE-Zeichen maskiert) und XSS (htmlspecialchars an jeder Ausgabe).
$q = $_GET['q'] ?? '';
+if (!is_string($q)) {
+ $q = '';
+}
$pdo = db();
-$sql = "SELECT id, name, nachricht FROM nachrichten WHERE oeffentlich = 1 AND nachricht LIKE '%" . $q . "%' ORDER BY id";
-$zeilen = $pdo->query($sql)->fetchAll();
+$stmt = $pdo->prepare(
+ "SELECT id, name, nachricht FROM nachrichten WHERE oeffentlich = 1 AND nachricht LIKE :q ESCAPE '\\' ORDER BY id"
+);
+$stmt->execute([':q' => '%' . addcslashes($q, '%_\\') . '%']);
+$zeilen = $stmt->fetchAll();
echo kopf('Nachrichten');
echo '<h1>Nachrichten</h1>';
-echo '<form method="get" action="/liste.php"><input name="q" value="' . $q . '"> <button>Suchen</button></form>';
+echo '<form method="get" action="/liste.php"><input name="q" value="' . h($q) . '"> <button>Suchen</button></form>';
if ($q !== '') {
- echo '<p>Suche nach: ' . $q . '</p>';
+ echo '<p>Suche nach: ' . h($q) . '</p>';
}
echo '<ul>';
foreach ($zeilen as $z) {
- echo '<li><strong>' . $z['name'] . '</strong>: ' . $z['nachricht'] . '</li>';
+ echo '<li><strong>' . h($z['name']) . '</strong>: ' . h($z['nachricht']) . '</li>';
}
echo '</ul>';
echo fuss();
So etwas für deinen Betrieb?
Beschreib kurz, was du brauchst. Du bekommst einen Festpreis, bevor ich anfange; ohne dein Ja passiert nichts.