Laravel Security Audit: praktische Checkliste für produktive Anwendungen

Eine priorisierte Audit-Checkliste für Angriffsfläche, Konfiguration, Abhängigkeiten, Berechtigungen, Datenflüsse, Jobs und Deployment.

Ein Laravel Security Audit ist weder ein pauschaler Scannerlauf noch ein lineares Abhaken der OWASP Top 10. Es verbindet Angriffsfläche, Framework-Konfiguration, Fachlogik, Quellcode, Datenflüsse und produktiven Betrieb. Ziel ist nicht die längste Findings-Liste, sondern die belastbare Antwort auf drei Fragen: Was ist realistisch ausnutzbar, welche gemeinsame Ursache erzeugt weitere Varianten und wie verhindert ein Test das Wiederauftreten?

Ein gutes Audit folgt Vertrauensgrenzen und Daten, nicht nur verdächtigen Funktionsnamen.

Diese Checkliste richtet sich an Teamleads und Laravel-Entwickler, die ein bestehendes System prüfen oder einen externen Review vorbereiten. Für einzelne Fehlermuster mit konkreten sicheren Implementierungen dient 10 häufige Sicherheitsfehler in Laravel-Projekten als technischer Pillar-Artikel.

Audit-Ziel, Tiefe und Nachweis festlegen

Vor dem ersten Tool-Aufruf braucht das Audit einen Auftrag:

  • Welche Anwendungen, APIs, Domains, mobilen Clients und Repositories gehören zum Scope?
  • Ist Quellcode verfügbar oder handelt es sich um einen Blackbox-Test?
  • Welche Rollen, Mandanten und Testdaten stehen bereit?
  • Welche Aktionen sind verboten, beispielsweise Lasttests oder produktive E-Mails?
  • Wie werden kritische Befunde während der Prüfung eskaliert?
  • Soll nur eine Version geprüft werden oder auch Deployment und Lieferkette?

Ein Security Review mit Quellcode findet andere Probleme als ein externer Penetrationstest. Beides ist wertvoll. Die Begriffe sollten jedoch nicht austauschbar verwendet werden. Ein kombinierter Auftrag kann Quellcode-Hypothesen kontrolliert am laufenden System bestätigen.

Ergebnis: Eine Scope-Liste, Rules of Engagement, Testkonten, Kommunikationsweg und Definition für reproduzierbare Evidenz.

Die vollständige Angriffsfläche inventarisieren

Laravel-Anwendungen besitzen mehr Einstiegspunkte als routes/web.php:

  • Web-, API- und Console-Routes
  • Broadcast Channels und WebSockets
  • Livewire- oder Inertia-Aktionen
  • Webhooks und OAuth-Callbacks
  • Queue-Jobs und Scheduler
  • Storage-Downloads und temporäre URLs
  • Health-, Debug- und Monitoring-Endpunkte
  • separate Admin-, Partner- und Mobile-Backends

Ein erster lokaler Überblick:

php artisan route:list --except-vendor
php artisan about --only=environment
composer audit --locked
composer outdated --direct

Die Ausgabe ist eine Startposition, kein Ergebnis. Dynamisch registrierte Routes, Subdomain-Routing, Paket-Endpunkte und Infrastrukturregeln müssen ergänzt werden. Ebenso wichtig ist die Frage, welcher Guard, welche Middleware und welche Rate Limits pro Einstieg gelten.

$routes = collect(Route::getRoutes())->map(fn (\Illuminate\Routing\Route $route) => [
    'methods' => $route->methods(),
    'uri' => $route->uri(),
    'name' => $route->getName(),
    'middleware' => $route->gatherMiddleware(),
]);

Prüfung: Suchen Sie nach schreibenden GET-Routen, ungeschützten internen Endpunkten, inkonsistenten Middleware-Gruppen und Aktionen, die nur in der Oberfläche verborgen wurden.

Produktionskonfiguration und Deployment prüfen

Konfigurationsfehler können solide Anwendungskontrollen vollständig umgehen. Relevant sind mindestens:

Bereich Sicherheitsfrage
Debugging Ist APP_DEBUG in Produktion garantiert deaktiviert?
Schlüssel Sind APP_KEY, OAuth-, Webhook- und API-Secrets getrennt und rotierbar?
Cookies Stimmen Domain, Secure, HttpOnly und SameSite?
Proxy Werden nur bekannte Proxies und Hosts vertraut?
CORS Sind Origins, Methoden und Credentials minimal begrenzt?
Storage Liegen vertrauliche Dateien außerhalb des öffentlichen Webroots?
Logs Werden Tokens, Cookies und personenbezogene Felder redigiert?
Worker Laden Queues nach Deployments und Secret-Rotation neue Konfiguration?

Sicherheitskritische Voraussetzungen sollten beim Start oder in einem dedizierten Deployment-Check fehlschlagen:

final class ProductionSecurityConfiguration
{
    public static function assertValid(): void
    {
        if (! app()->isProduction()) {
            return;
        }

        throw_if(config('app.debug'), RuntimeException::class, 'APP_DEBUG is enabled.');
        throw_unless(config('session.secure'), RuntimeException::class, 'Secure cookies are disabled.');
        throw_unless(filled(config('services.billing.webhook_secret')), RuntimeException::class, 'Webhook secret is missing.');
    }
}

Der Check gehört in Build und Release-Pipeline. Ein Fehler erst beim ersten Kundenrequest kommt zu spät.

Abhängigkeiten und Lieferkette bewerten

composer audit erkennt bekannte Advisories im Lockfile. Es beantwortet nicht, ob ein Paket aufgegeben, ein Installationsskript riskant oder eine neue Version kompromittiert ist. Ebenso gefährlich ist es, Sicherheitsupdates dauerhaft aufzuschieben.

Ein Audit prüft deshalb:

  • composer.lock ist versioniert und Deployments installieren daraus reproduzierbar.
  • Direkte und transitive Pakete sind sichtbar.
  • Composer Scripts, GitHub Actions und externe Build-Actions sind nachvollziehbar gepinnt.
  • Abhängigkeiten mit Zugriff auf Authentifizierung, Dateisystem oder Serialisierung erhalten besondere Aufmerksamkeit.
  • Updates laufen mit Tests, Review und kontrollierbarer Herkunft.
  • AI-generierte Paketnamen werden nicht ungeprüft installiert.
composer show --direct
composer audit --locked
composer validate --strict
npm audit

Prüfung: Ein grüner Advisory-Scan ist kein Beleg für eine sichere Lieferkette. Das Audit dokumentiert Herkunft, Aktualisierungsprozess und privilegierte Abhängigkeiten.

Authentifizierung als Lebenszyklus testen

Inventarisieren Sie Registrierung, Login, SSO, API-Token, Passwort-Reset, E-Mail-Wechsel, MFA, Recovery, Impersonation und Logout. Für jeden Weg werden Enumeration, Rate Limits, Token-Ablauf, Einmaligkeit, Session-Rotation und Widerruf geprüft.

Besondere Fragen:

  • Bleiben andere Sessions nach Passwort-Reset aktiv?
  • Kann eine unbestätigte neue E-Mail bereits für Recovery oder Rollen verwendet werden?
  • Verlangt der Wechsel eines MFA-Faktors eine aktuelle Bestätigung?
  • Funktioniert ein Magic Link in einer anderen vorbereiteten Session oder für einen anderen Benutzer?
  • Darf ein gesperrtes Konto weiterhin API oder Broadcast Channels verwenden?

Die vollständige Umsetzung steht in Laravel-Authentifizierung und Sessions sicher umsetzen.

Autorisierung über Rollen hinaus verfolgen

Authentifizierung beantwortet nur, wer eine Anfrage stellt. Das Audit muss für jedes schützenswerte Objekt prüfen:

  1. Gehört das Objekt zum erwarteten Mandanten?
  2. Darf dieser Benutzer diese Aktion ausführen?
  3. Erlaubt der aktuelle fachliche Zustand die Aktion?
  4. Gilt dieselbe Entscheidung für HTML, API, Export, Download und Queue?

Eine reine Rollenprüfung ist meistens zu grob:

// Zu breit für ein mandantenfähiges System.
return $user->role === 'manager';

// Die Policy bildet Kontext, Beziehung und Aktion ab.
return $user->tenant_id === $invoice->tenant_id
    && $user->can('invoice.approve')
    && $invoice->status === InvoiceStatus::Pending;

Negative Tests kreuzen Benutzer, Mandanten, Objekte und Zustände bewusst. Details enthält Mandantenfähigkeit und Berechtigungen in Laravel richtig trennen.

Eingaben bis zu sicherheitsrelevanten Senken verfolgen

Validierung bedeutet nicht automatisch Sicherheit. Entscheidend ist, wo ein Wert landet. Im Review werden Eingaben bis zu folgenden Senken verfolgt:

  • Raw SQL und dynamische Spalten
  • Blade Raw Output und HTML-Sanitizer
  • Shell-Kommandos und Prozessaufrufe
  • Dateipfade und Downloads
  • HTTP-Requests und Redirect-Ziele
  • Header, E-Mail-Templates und Logfelder
  • Deserialisierung und dynamische Klassen
  • AI-Tools mit externen Seiteneffekten

Schnelle Suchmuster helfen bei der Hypothesenbildung:

rg -n "whereRaw|orderByRaw|selectRaw|DB::raw" app
rg -n "\{!!|shell_exec|proc_open|Process::run" app resources
rg -n "Http::|Storage::disk|temporarySignedRoute" app
rg -n "withoutGlobalScope|unguard|guarded = \[\]" app

Jeder Treffer braucht Kontext. Ein statischer String in selectRaw() ist nicht automatisch verwundbar; ein scheinbar sicherer Helper kann dagegen Nutzereingabe in einen Dateipfad oder Host überführen.

Livewire und client-synchronisierte Zustände als Eingabe behandeln

Öffentliche Livewire-Properties sehen serverseitig wie normale PHP-Felder aus, können aber vom Client verändert werden. Ein ausgeblendetes Eingabefeld oder fehlendes wire:model macht die Property nicht vertrauenswürdig.

use Livewire\Attributes\Locked;
use Livewire\Component;

final class EditInvoice extends Component
{
    #[Locked]
    public int $invoiceId;

    public string $reference = '';

    public function save(): void
    {
        $invoice = Invoice::findOrFail($this->invoiceId);
        $this->authorize('update', $invoice);

        $validated = $this->validate([
            'reference' => ['required', 'string', 'max:100'],
        ]);

        $invoice->update($validated);
    }
}

#[Locked] verhindert in Livewire 4 die Änderung dieser Property durch den Client, ersetzt aber nicht die Policy: Servercode innerhalb der Komponente kann sie weiterhin ändern, und ein erlaubter Wert kann fachlich trotzdem unzulässig sein. Öffentliche Properties und Action-Parameter sind grundsätzlich als nicht vertrauenswürdige Eingabe zu behandeln. Hydration, Model Binding und verschachtelte Daten verdienen gezielte Manipulationstests.

Dateien, URLs und Integrationen separat testen

Uploads und serverseitige URL-Abrufe verbinden die Anwendung mit untrusted content und fremden Systemen. Das Audit prüft:

  • echte Größen- und Typgrenzen statt nur Dateiendungen,
  • zufällige Storage-Namen und private Ablage,
  • Autorisierung bei jedem Download,
  • Umgang mit SVG, HTML, Office- und Archivformaten,
  • Bildtransformation und Parser-Isolation,
  • SSRF-Schutz für Previews, Imports, Webhooks und AI-Agenten,
  • Redirect-, DNS-, IP- und Response-Size-Regeln,
  • Netzwerk-Egress als zweite Schutzschicht.

Eine Allowlist bekannter Integrationen ist belastbarer als ein Validator für beliebige URLs:

$integration = Integration::query()
    ->where('tenant_id', $request->user()->tenant_id)
    ->whereKey($request->integer('integration_id'))
    ->firstOrFail();

abort_unless($integration->isProvisionedAndAllowed(), 403);

$response = Http::baseUrl($integration->validated_base_url)
    ->connectTimeout(2)
    ->timeout(5)
    ->withoutRedirecting()
    ->get('/status');

validated_base_url muss beim Anlegen der Integration serverseitig gegen die erlaubten Hosts und Netzbereiche geprüft werden; der Feldname allein ist keine Kontrolle. withoutRedirecting() ist hier bewusst gewählt. Laravels HTTP-Client kann Redirects begrenzen oder deaktivieren, validiert Redirect-Ziele aber nicht automatisch als SSRF-Schutz. Soll die Integration Redirects unterstützen, muss jedes neue Ziel vor dem nächsten Request erneut gegen die erlaubten Hosts und IP-Bereiche geprüft werden.

Asynchrone und versteckte Datenpfade prüfen

Queue-Jobs, Events, Notifications, Broadcast Channels, Exporte, Suche und Caches laufen außerhalb des offensichtlichen Controllers. Häufige Audit-Funde sind:

  • Job lädt ein Objekt nur über globale ID.
  • Berechtigung wird beim Dispatch, aber nicht bei Ausführung geprüft.
  • Cache-Key enthält weder Tenant noch Rolle.
  • Broadcast Channel prüft Benutzer-ID, aber nicht Kontostatus.
  • Export verwendet einen absichtlich deaktivierten Global Scope.
  • Notification rendert sensible Daten, obwohl der Empfänger inzwischen gesperrt ist.
Broadcast::channel('tenant.{tenant}.invoice.{invoice}', function (User $user, Tenant $tenant, Invoice $invoice): bool {
    return $user->is_active
        && $user->tenant_id === $tenant->id
        && $invoice->tenant_id === $tenant->id
        && Gate::forUser($user)->allows('view', $invoice);
});

Prüfung: Nehmen Sie eine fachliche Sicherheitsregel und verfolgen Sie sie durch HTTP, API, Queue, Broadcast, Cache, Suche und Dateiablage.

Geschäftslogik und Nebenläufigkeit angreifen

Scanner erkennen selten, dass ein Gutschein doppelt eingelöst, ein Freigabelimit durch Parallelität umgangen oder ein abgelaufener Zustand durch eine ungewöhnliche Reihenfolge reaktiviert werden kann.

Das Audit modelliert daher:

  • erlaubte Zustandsübergänge,
  • Wert- und Mengenlimits,
  • Reihenfolge abhängiger Aktionen,
  • wiederholte und parallele Requests,
  • Idempotenz externer Events,
  • Rollenwechsel zwischen Queue und Ausführung,
  • Differenzen zwischen Vorschau und tatsächlicher Mutation.

Eine Datenbanktransaktion hilft nur, wenn die relevante Zeile gesperrt und die Invariante innerhalb derselben Transaktion erneut geprüft wird. Eindeutige Constraints bilden eine wichtige letzte Grenze.

Befunde reproduzierbar und entwickelbar machen

Ein nützlicher Befund enthält:

  1. betroffene Komponente und Vertrauensgrenze,
  2. Voraussetzungen und exakten Angriffspfad,
  3. minimale reproduzierbare Evidenz,
  4. technische und fachliche Auswirkung,
  5. gemeinsame Ursache und mögliche Varianten,
  6. konkrete Behebung auf Architektur- und Codeebene,
  7. einen vorgeschlagenen Regressionstest,
  8. Priorität unter Berücksichtigung der realen Erreichbarkeit.

CVSS kann die technische Schwere vergleichbar machen. Die Reihenfolge der Behebung benötigt zusätzlich Datenklasse, Exposition, vorhandene Kontrollen und Geschäftsauswirkung. Ein hoher Score ohne erreichbaren Pfad kann hinter einem mittleren, öffentlich zuverlässig ausnutzbaren Mandantenleck liegen.

Kompakte Laravel-Security-Audit-Checkliste

Prüfbereich Mindestnachweis
Scope Routes, Hosts, Rollen, Repositories und Ausschlüsse dokumentiert
Deployment Debug, Secrets, Cookies, Proxies, CORS und Worker geprüft
Supply Chain Lockfiles, Advisories, privilegierte Pakete und Actions geprüft
Authentifizierung Login, Recovery, MFA, Sessions und Widerruf negativ getestet
Autorisierung Cross-User-, Cross-Tenant- und Statusfälle geprüft
Eingaben SQL, HTML, Prozesse, Pfade, URLs und Logs verfolgt
Dateien Typ, Größe, Transformation, Ablage und Download autorisiert
Asynchronität Jobs, Broadcasts, Events, Suche, Cache und Exporte geprüft
Geschäftslogik Replay, Reihenfolge, Limits und Parallelität getestet
Betrieb Monitoring, Rotation, Backup, Restore und Incident-Pfad geprüft
Bericht Reproduktion, Auswirkung, Ursache, Fix und Regressionstest vorhanden

Wann Review, Pentest oder beides?

  • Code Review: sinnvoll bei komplexen Berechtigungen, Datenflüssen und Framework-spezifischen Risiken.
  • Penetrationstest: sinnvoll für die real erreichbare Angriffsfläche und bestätigte Exploit-Ketten.
  • Kombination: sinnvoll bei geschäftskritischen Plattformen, weil Code-Hypothesen am laufenden System bestätigt und Blackbox-Beobachtungen bis zur Ursache verfolgt werden können.
  • Kontinuierliche Prüfung: sinnvoll bei häufigen Releases, vielen Internet-Assets oder regelmäßig wechselnden Integrationen.

Umfangreiche Prüfungen werden über DSecured umgesetzt. Der Entwicklungsbezug ist wichtig, weil ein Befund erst dann wertvoll wird, wenn das Team ihn sicher beheben und durch Tests dauerhaft schließen kann.

Verwandte Laravel-Sicherheitsartikel

Quellen und Vertiefung

Fazit

Ein belastbares Laravel Security Audit beginnt bei Systemgrenzen und endet nicht beim ersten Finding. Es verbindet externe Erreichbarkeit, Quellcode, Framework-Verhalten, Fachlogik und Betrieb. Der eigentliche Qualitätsnachweis ist eine reproduzierbare Angriffskette, eine ursächliche Korrektur und ein negativer Test, der dieselbe Sicherheitsannahme bei jedem künftigen Release bewahrt.

Eine ähnliche Entscheidung steht in Ihrem Projekt an?

Schildern Sie den Kontext. Ich ordne die technischen Optionen, Risiken und einen sinnvollen nächsten Schritt ein.

Projektfrage besprechen ↗