Sicherheits-Grundschutz für Webanwendungen: zehn Punkte
Wer eine Webanwendung mit personenbezogenen Daten absichern will, braucht zweiten Faktor, Rechte nach Bedarf, geprüfte Eingaben, Verschlüsselung, aktuelle Pakete und Protokolle mit Alarm. Jeder der zehn Punkte lässt sich mit einer kurzen Probe nachprüfen.
Art. 32 DSGVO verpflichtet jeden, der personenbezogene Daten verarbeitet, zu technischen und organisatorischen Maßnahmen, die dem Risiko angemessen sind. Welche das sind, sagt die Verordnung nur in Umrissen: Verschlüsselung, Vertraulichkeit und Verfügbarkeit der Systeme, rasche Wiederherstellung nach einem Zwischenfall und regelmäßige Überprüfung der Maßnahmen. Konkreter wird das BSI im IT-Grundschutz-Baustein APP.3.1 Webanwendungen und Webservices.
Die zehn Punkte unten folgen diesem Baustein und dem Application Security Verification Standard (ASVS) der OWASP-Stiftung, Version 5.0.0 vom 30.05.2025. Sie ersetzen weder eine Prüfung nach IT-Grundschutz noch einen Penetrationstest. Sie decken die Lücken ab, die sich mit wenig Aufwand finden lassen, und jeder Punkt endet mit einer Probe, die Sie oder Ihr Dienstleister in Minuten durchführen. Die Risikoliste der OWASP Top 10 selbst ist ein eigenes Thema; hier geht es um Maßnahmen.
Grundlage
Was der Grundschutz abdeckt
Die OWASP Top 10:2025 führt das OWASP-Projekt auf GitHub seit März 2026 als endgültige Fassung. An erster Stelle steht dort fehlerhafte Zugriffskontrolle, an zweiter falsche Konfiguration. Die Top 10 beschreibt Risiken; prüfbare Anforderungen enthält der ASVS. Das BSI fordert in APP.3.1 unter anderem Grenzwerte für fehlgeschlagene Anmeldungen (A1), Einschränkungen für Uploads (A4), Schutz vor unerlaubter automatisierter Nutzung (A7), gesalzene Hashes für Zugangsdaten (A14) und bestimmte HTTP-Header (A21).
Die Maßnahmen sind nach dem Weg einer Anfrage geordnet: Anmeldung, Rechte, Eingaben, Transport und Konfiguration, zuletzt Betrieb. Wie wir Sicherheit in Projekten von Anfang an mitplanen, beschreibt unsere Seite Sicherheit und Compliance.
Punkte 1 und 2
Anmeldung und Zwei-Faktor-Schutz
1. Zweiter Faktor für alle Konten mit Rechten
Ein gestohlenes Passwort allein darf nicht reichen, um in die Verwaltung einer Anwendung zu kommen. Mindestens Administratoren, Mitarbeiter mit Zugriff auf Kundendaten und Zugänge zu Hoster und Repository brauchen einen zweiten Faktor, etwa eine App mit Einmalcodes oder Passkeys. Wie Passkeys funktionieren und wie Sie sie neben dem bisherigen Login einführen, steht unter Passkeys in Webanwendungen: Anmelden ohne Passwort.
In Unternehmen mit zentralem Verzeichnis kann die Anmeldung über dieses Verzeichnis den zweiten Faktor übernehmen. Bei Athena, einem API-Backend für die Agile Robots AG, haben wir das Microsoft-SSO an die Konzern-Identität angeschlossen. Der zweite Faktor und das Sperren eines Kontos beim Austritt eines Mitarbeiters liegen dann an einer Stelle statt in jeder Anwendung einzeln.
So prüfen Sie: Melden Sie sich mit einem Administratorkonto in einem privaten Browserfenster an. Fragt die Anwendung nach Passwort und Benutzername keinen zweiten Faktor ab, fehlt Punkt 1.
2. Passwörter gesalzen speichern und Durchprobieren bremsen
Das BSI verlangt in APP.3.1.A14 gesalzene Hash-Verfahren für Zugangsdaten und in A1 Grenzwerte für fehlgeschlagene Anmeldeversuche. Laravel speichert Passwörter standardmäßig mit bcrypt und bringt mit dem RateLimiter eine Drosselung mit. Die Passwortregel von Laravel kann neue Passwörter zusätzlich mit dem Dienst Have I Been Pwned abgleichen, laut Dokumentation nach dem k-Anonymity-Verfahren, also ohne das Passwort preiszugeben.
So prüfen Sie: Geben Sie für ein Testkonto zehnmal hintereinander ein falsches Passwort ein. Die Anwendung sollte spätestens dann bremsen oder sperren, und eine Meldung sollte bei jemandem ankommen (Punkt 10).
Punkt 3
Rechte nach dem Prinzip der geringsten Rechte
3. Jede Anfrage prüft, wem der Datensatz gehört
Jedes Konto bekommt die Rechte, die seine Aufgabe braucht, und keine weiteren. Eine Navigation, die nur erlaubte Menüpunkte zeigt, schützt keine Daten. Lücken entstehen in Einzelansichten, Downloads und Schnittstellen, wenn diese prüfen, ob jemand angemeldet ist, aber nicht, ob ihm der angefragte Datensatz gehört. In Laravel gehört diese Prüfung in eine Policy, die der Controller vor jedem Zugriff aufruft.
So prüfen Sie: Melden Sie sich als Nutzer A an, öffnen Sie einen eigenen Datensatz, etwa eine Rechnung, und ändern Sie die Nummer in der Adresszeile auf eine Rechnung von Nutzer B. Erscheint sie, fehlt die Prüfung. Wiederholen Sie das für den PDF-Download und für die Schnittstelle, falls es eine gibt.
Punkte 4 und 5
Eingaben prüfen, Ausgaben maskieren
4. Eingaben auf dem Server prüfen, Ausgaben maskieren
Prüfungen im Browser sind Komfort für den Nutzer; wer angreift, schickt seine Anfrage direkt an den Server. Jede Eingabe wird deshalb auf dem Server gegen erwartete Typen, Längen und Werte geprüft. Bei der Ausgabe maskiert Blade alles zwischen doppelten geschweiften Klammern mit htmlspecialchars, so steht es in der Laravel-Dokumentation. Datenbankabfragen laufen über Query Builder oder Eloquent mit Platzhaltern, nie über Text, der aus Eingaben zusammengesetzt wird.
So prüfen Sie: Tragen Sie in ein Namensfeld den Text <b>Test</b> ein und speichern Sie. Erscheint der Name später fett, gibt die Anwendung Eingaben ungefiltert als HTML aus. Suchen Sie im Code außerdem nach {!! und nach DB::raw; jede Fundstelle wird einzeln begründet.
5. Uploads einschränken
Das BSI verlangt in APP.3.1.A4, für Uploads erlaubte Größe, Dateitypen und Speicherorte festzulegen und Ausführungsrechte restriktiv zu setzen. Hochgeladene Dateien liegen außerhalb des öffentlichen Verzeichnisses oder in einem Speicher, aus dem der Webserver keinen Code ausführt, und der Dateityp wird am Inhalt geprüft, nicht an der Endung.
So prüfen Sie: Benennen Sie eine Textdatei in rechnung.pdf um und laden Sie sie hoch. Nimmt die Anwendung sie an, prüft sie nur die Endung.
Punkte 6 bis 8
Verschlüsselung, Header und Geheimnisse
6. Nur HTTPS, mit HSTS
Alle Seiten laufen über HTTPS, und eine Anfrage über http wird dauerhaft auf https umgeleitet. Der Header Strict-Transport-Security sorgt dafür, dass der Browser die Seite danach nur noch verschlüsselt aufruft; laut MDN ignorieren Browser ihn, wenn er über unverschlüsseltes HTTP kommt. Die Option includeSubDomains setzt nur, wer sicher ist, dass alle Subdomains HTTPS sprechen; in der Middleware unten ist sie deshalb abgeschaltet, bis Sie das geprüft haben.
So prüfen Sie: Rufen Sie Ihre Adresse mit http:// statt https:// auf. Sie sollten ohne Umweg auf der verschlüsselten Seite landen.
7. Sicherheits-Header und Cookies
APP.3.1.A21 nennt mindestens Content-Security-Policy, Strict-Transport-Security, Content-Type, X-Content-Type-Options und Cache-Control, so restriktiv wie möglich, und für Cookies die Attribute secure, SameSite und httponly. Laravel setzt Content-Type und Cache-Control von sich aus und markiert das Sitzungscookie als httponly mit SameSite lax. Das Attribut secure kommt erst, wenn SESSION_SECURE_COOKIE=true in der Umgebungsdatei steht. Die übrigen Header setzt eine kleine Middleware für alle Antworten, auch für Fehlerseiten:
Die Content-Security-Policy erlaubt nur Inhalte der eigenen Domain; Skripte und Schriften fremder Anbieter müssen Sie ergänzen, bevor die Regel im Live-Betrieb greift.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
// Register in bootstrap/app.php:
// $middleware->append(\App\Http\Middleware\SecurityHeaders::class);
class SecurityHeaders
{
// true sends the policy as Report-Only: nothing is blocked, violations are only reported
// (reports need a report-to endpoint). Test on staging, then enforce with false.
private const CSP_REPORT_ONLY = false;
// Set to true only when every subdomain (www, shop, mail, ...) serves HTTPS:
// browsers then refuse plain HTTP for all of them for max-age seconds.
private const HSTS_INCLUDE_SUBDOMAINS = false;
private const CSP = "default-src 'self'; img-src 'self' data:; object-src 'none'; "
."base-uri 'self'; form-action 'self'; frame-ancestors 'none'";
private const HEADERS = [
'X-Content-Type-Options' => 'nosniff',
'Referrer-Policy' => 'strict-origin-when-cross-origin',
'Permissions-Policy' => 'camera=(), microphone=(), geolocation=()',
];
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$cspHeader = self::CSP_REPORT_ONLY
? 'Content-Security-Policy-Report-Only'
: 'Content-Security-Policy';
// A route that sets its own value keeps it.
foreach ([$cspHeader => self::CSP] + self::HEADERS as $name => $value) {
if (! $response->headers->has($name)) {
$response->headers->set($name, $value);
}
}
// HSTS only over HTTPS: browsers ignore it on plain HTTP.
if ($request->isSecure() && ! $response->headers->has('Strict-Transport-Security')) {
$hsts = 'max-age=31536000'.(self::HSTS_INCLUDE_SUBDOMAINS ? '; includeSubDomains' : '');
$response->headers->set('Strict-Transport-Security', $hsts);
}
return $response;
}
}
Was beim Einbau der Middleware zu beachten ist
Läuft die Anwendung hinter einem Load Balancer oder Proxy, der die Verschlüsselung übernimmt, erkennt Laravel HTTPS nur, wenn es diesem Proxy vertraut; sonst liefert isSecure() false, und der HSTS-Header fehlt. Die Content-Security-Policy blockiert alles, was nicht von der eigenen Domain kommt, auch eingebettete Videos und Karten. Weil sie kein 'unsafe-inline' enthält, blockiert sie außerdem Skripte und Stile, die direkt im HTML stehen, also auch style-Attribute und Code, den Livewire, Alpine.js oder ein Tag-Manager in die Seite schreibt. Solche Stellen brauchen eine Nonce oder einen Hash in der Richtlinie, oder sie wandern in eigene Dateien. Testen Sie sie deshalb auf einem Testsystem und öffnen Sie dort jede Seitenart einmal mit geöffneter Browserkonsole, bevor Sie sie live schalten.
So prüfen Sie: In den Entwicklerwerkzeugen des Browsers zeigt der Reiter Netzwerk die Antwort-Header der Seite, der Reiter Anwendung (bei Firefox: Web-Speicher) die Cookies mit den Spalten Secure, HttpOnly und SameSite.
8. Geheimnisse gehören nicht in den Code
Datenbankpasswörter, API-Schlüssel und der Anwendungsschlüssel stehen in der Umgebungsdatei .env, und die gehört laut Laravel-Dokumentation nicht in die Versionsverwaltung. APP.3.1.A14 verlangt zudem, Quelltextdateien vor unerlaubtem Abruf zu schützen. Der Debug-Modus ist im Live-Betrieb aus, weil er laut Dokumentation sonst Konfigurationswerte an Besucher ausgeben kann.
So prüfen Sie: Rufen Sie https://ihre-domain.de/.env und https://ihre-domain.de/.git/config im Browser auf. Beide Adressen müssen einen Fehler liefern. Suchen Sie im Repository mit git log -p nach Begriffen wie password und secret, auch in alten Ständen.
Punkte 9 und 10
Updates und Protokolle, die im Ernstfall helfen
9. Pakete aktuell halten
Fremde Pakete bringen eigene Sicherheitslücken in die Anwendung. Der Befehl composer audit gleicht die installierten PHP-Pakete mit bekannten Sicherheitsmeldungen ab und endet mit einem Fehlercode, wenn er etwas findet; npm audit macht dasselbe für JavaScript. Wie ein fester Rhythmus für Updates aussieht, beschreibt Patch-Disziplin: Warum kleine Updates großen Schaden verhindern.
So prüfen Sie: Lassen Sie composer audit im Projektverzeichnis laufen und fragen Sie, wann der Befehl zuletzt automatisch lief und wer das Ergebnis liest.
10. Protokolle mit Alarm
Ein Angriff, den niemand bemerkt, läuft weiter. Fehlgeschlagene Anmeldungen, Änderungen an Rechten, Exporte großer Datenmengen und Fehler mit Status 500 gehören in ein Protokoll, das nicht auf demselben Server gelöscht werden kann, und bestimmte Muster lösen eine Benachrichtigung aus. Protokolle mit personenbezogenen Daten brauchen eine Löschfrist.
So prüfen Sie: Lösen Sie fünf fehlgeschlagene Anmeldungen aus und fragen Sie, wo sie auftauchen und wer davon erfährt. Lautet die Antwort „in einer Datei auf dem Server“, fehlt der Alarm.
Zur Verfügbarkeit nach Art. 32 gehört schließlich die Wiederherstellung. Wie Sie prüfen, ob Ihre Sicherungen im Ernstfall tragen, steht unter Backups richtig testen: die Wiederherstellung zählt. Ob nach den zehn Punkten ein automatischer Scan oder ein Test von Hand folgen sollte, klärt Penetrationstest oder Schwachstellenscan: der Unterschied.
Kurz gefragt
Häufige Fragen
-
Eine Liste der zehn wichtigsten Risikokategorien für Webanwendungen, herausgegeben von der gemeinnützigen OWASP-Stiftung. Sie dient der Sensibilisierung und nennt Risiken wie fehlerhafte Zugriffskontrolle oder Injection. Für prüfbare Anforderungen, etwa in einem Vertrag oder Audit, gibt es den ASVS derselben Stiftung.
-
Ein Gesetz schreibt sie für eine gewöhnliche Webanwendung nicht ausdrücklich vor. Art. 32 DSGVO verlangt Maßnahmen nach dem Stand der Technik und dem Risiko, und für Konten mit Zugriff auf viele Kundendaten ist ein zweiter Faktor nach unserer Einschätzung Teil davon. In regulierten Bereichen wie Zahlungsdiensten gibt es eigene Pflichten zur starken Authentifizierung.
-
Automatische Prüfungen wie composer audit laufen bei jeder Auslieferung, ein Schwachstellenscan in festen Abständen. Einen Penetrationstest von Hand sieht das BSI in APP.3.1.A22 regelmäßig vor; sinnvolle Anlässe sind der Start einer Anwendung und größere Umbauten an Anmeldung, Rechten oder Schnittstellen.
Zehn Proben, und bei drei davon war die Antwort unklar?
Im kostenfreien Security-Briefing gehen wir Datenmodell, Berechtigungen, Logging und Hosting Ihrer Anwendung durch und sagen Ihnen, was zuerst kommt und wo ein externer Pentest sinnvoll ist.
Nachweise
Quellen und Stand
Stand: 14.08.2026. Die Angaben zu Art. 32 DSGVO geben den Stand zum genannten Datum wieder und sind keine Rechtsberatung. Die Middleware haben wir mit PHP 8.4 und Laravel 13 in einer neuen Laravel-Anwendung mit vier Feature-Tests geprüft: Header über HTTP und HTTPS, eigene Werte einer Route und Fehlerseiten, jeweils mit ab- und eingeschaltetem includeSubDomains.
Artikel teilen