PHP Security Audit: Die Checkliste für Legacy- und KI-generierte Codebasen
Ein PHP Security Audit ist kein einzelner Scan, den Sie vor dem Launch laufen lassen. Es ist ein strukturiertes Review über mehrere Ebenen einer Codebasis hinweg: Authentifizierungsdesign, Datenflüsse, Dependency-Hygiene, Umgang mit Secrets und Laufzeitkonfiguration. Es fördert genau die Lücken zutage, die reine Static-Analysis-Werkzeuge übersehen.
Dieser Beitrag erklärt, was ein gründliches PHP Security Audit abdeckt, wie das schriftliche Ergebnis aussieht, wie KI-generierter Code die Angriffsfläche gegenüber älteren Legacy-Systemen verändert hat und wie Sie Ihre Codebasis vorbereiten, bevor Sie ein Audit beauftragen.
Warum PHP-Codebasen ein eigenes Security Audit brauchen
PHP hat einen ungewöhnlich langen Schwanz. Anwendungen, die auf PHP 5.3 gestartet sind, laufen bis heute in Produktion. Frameworks wie Symfony, Laravel und individuelle Stacks bringen jeweils eigene Sicherheitsprimitive mit, und ein Schwachstellenmuster, das in dem einen idiomatisch ist, ist im anderen katastrophal.
Gleichzeitig hat der Aufstieg KI-gestützter Entwicklung eine neue Klasse von Problemen eingeführt. Von LLMs generierter PHP-Code ist an der Oberfläche meist funktional, darunter aber inkonsistent: korrekte Parametrisierung einer Query in einer Datei, rohe String-Verkettung in der Datei, die 30 Sekunden später generiert wurde. Die Muster unterscheiden sich von klassischen Legacy-Bugs, der Wirkungsradius ist derselbe.
Ein PHP Code Audit adressiert beides: die über Jahre menschlicher Entwicklung angesammelten technischen Schulden und die Inkonsistenz, die durch generierten statt geprüften Code entsteht.
Die sieben Dimensionen eines PHP Security Audits
1. Authentifizierung und Autorisierung
Fehler bei der Authentifizierung sind laut OWASP die Risikokategorie Nummer eins für Webanwendungen. In PHP sind die häufigsten Probleme nicht exotisch, sondern strukturell.
Worauf Auditoren achten:
- Passwort-Hashing: Wird
password_hash()mitPASSWORD_BCRYPToderPASSWORD_ARGON2IDdurchgängig eingesetzt, oder liegen noch altemd5()- beziehungsweisesha1()-Hashes in der Codebasis? - Session Fixation: Wird
session_regenerate_id(true)nach dem Login aufgerufen? - Autorisierungsprüfungen: Werden sie auf Controller-Ebene erzwungen oder sind sie inkonsistent verstreut, sodass einzelne Routen ungeschützt bleiben?
- Mehrfaktor-Authentifizierung: Wird die MFA-Prüfung dort, wo sie existiert, serverseitig erzwungen oder nur im Frontend?
- Remember-me-Token: Werden sie als sichere Hashes gespeichert statt als Klartext-Session-IDs?
Speziell in Symfony prüfen Auditoren die Reihenfolge der Firewalls in der security.yaml. Eine falsch konfigurierte Firewall kann API-Routen freilegen, die geschützt sein sollten, weil Symfony das erste passende Firewall-Pattern für den Request-Pfad anwendet. Ein Endpunkt, der nach einem als offen gedachten ^/api-Pattern hinzugefügt wurde, kann diese Offenheit unbeabsichtigt erben, wenn die Reihenfolge nicht stimmt.
2. Eingabeverarbeitung und SQL-Exposition
Injection bleibt die am häufigsten ausgenutzte Schwachstellenklasse in PHP-Anwendungen, die nicht von Anfang an auf einem modernen ORM aufgebaut wurden.
Worauf Auditoren achten:
- Rohes SQL: Jede String-Verkettung oder Interpolation innerhalb einer Query wird markiert, unabhängig davon, wie sicher die Variable aussieht.
- Prepared Statements und parametrisierte Queries: Auditoren prüfen, ob sie durchgängig genutzt werden und nicht nur auf den Routen, die nutzersichtbare Formulare verarbeiten.
- ORM-Fehlnutzung: Doctrine erlaubt rohes DQL mit interpolierten Strings. Auditoren prüfen QueryBuilder-Aufrufe auf Muster wie
->where("u.email = '$email'"). - Indirekte Injection-Flächen: Suchfilter, Sortierparameter und Export-Funktionen, die Spaltennamen entgegennehmen, sind verbreitete blinde Flecken.
- XML- und LDAP-Injection: seltener, aber vorhanden in Enterprise-PHP-Codebasen, die Verzeichnisdienste anbinden oder externe XML-Feeds parsen.
3. Secrets-Management
Im Quellcode eingebettete Zugangsdaten sind ein Dauerproblem in PHP. Der typische Ablauf: Eine Entwicklerin hardcodet bei einem schnellen Fix ein Datenbankpasswort, committet es, das Secret bleibt jahrelang in der Git-Historie, das Unternehmen rotiert das Passwort, aber niemand prüft, ob der alte Wert noch irgendwo in einem Branch eingecheckt ist.
Worauf Auditoren achten:
- Ins Repository committete
.env-Dateien, einschließlich historischer Commits. - API-Schlüssel, Datenbankpasswörter oder private Keys in Konfigurationsdateien, PHP-Konstanten oder Klassen-Properties.
- Logging-Anweisungen, die Request-Parameter ausgeben und dabei Token oder Passwörter mitschreiben können.
- Credential-Dateien von Cloud-Anbietern (
~/.aws/credentials, Service-Account-JSON), die aus der PHP-Konfiguration referenziert werden. - Secrets in Symfonys
parameters.yamloderconfig/services.yaml, die ohne Substitution durch Umgebungsvariablen committet wurden.
Das Audit umfasst einen Scan der Git-Historie, nicht nur des aktuellen HEAD, denn Secrets, die aus HEAD entfernt wurden, aber in früheren Commits stehen, sind für jeden mit Repository-Zugriff vollständig wiederherstellbar.
4. Dependency- und CVE-Scanning
Eine moderne PHP-Anwendung hat typischerweise 80 bis 200 Composer-Abhängigkeiten. Jede davon ist ein potenzieller Einstiegspunkt.
Worauf Auditoren achten:
- Die Ausgabe von
composer auditund alle offenen CVEs in direkten oder transitiven Abhängigkeiten. - Die PHP-Version: Erhält die eingesetzte Laufzeitversion noch Sicherheitspatches? PHP 8.0 hat im November 2023 das End-of-Life erreicht, 8.1 im Dezember 2025.
- Abhängigkeiten, die auf eine Major-Version gepinnt sind, deren gelockte Minor-Version bekannte kritische Schwachstellen aufweist.
- Aufgegebene Pakete, die Packagist als "abandoned" kennzeichnet und die unabhängig vom CVE-Status keine Sicherheitspatches mehr erhalten.
- Die Lücke zwischen
composer.jsonundcomposer.lock: Ohne committetecomposer.lockgibt es keinen reproduzierbaren Abhängigkeitsstand und damit keine verlässliche Ausgangsbasis für das Audit.
5. Session-Handling
Die PHP-Session-Konfiguration verteilt sich über php.ini, die Optionen von session_start() und den Anwendungscode. Die Standardwerte sind für eine produktive Sicherheitslage selten angemessen.
Worauf Auditoren achten:
session.cookie_secure: muss bei jeder über HTTPS ausgelieferten Anwendung auftruestehen.session.cookie_httponly: verhindert, dass clientseitiges JavaScript auf das Session-Cookie zugreift.session.cookie_samesite:LaxoderStrictmindert CSRF durch Cookie-Diebstahl.- Session-Lebensdauer: Laufen Sessions nach Inaktivität ab oder nur beim expliziten Logout?
- Session-Speicherung: Dateibasierte Sessions auf Shared Hosting können von anderen Mandanten gelesen werden. Datenbank- oder Redis-gestützte Session-Speicher werden auf Zugriffskontrollen geprüft.
- Integrität der Session-Daten: Anwendungen, die sensible Flags wie
$_SESSION['is_admin'] = trueohne serverseitige Verifikation gegen einen Datenbankeintrag speichern, werden markiert.
6. Datei-I/O und Berechtigungsfehler
Dateibezogene Schwachstellen werden in PHP unterschätzt, weil sie für einen Exploit oft mit einer weiteren Schwäche verkettet werden müssen. In Codebasen mit Upload-Funktionen, Reportgenerierung oder dynamischen Include-Mustern treten sie jedoch häufig auf.
Worauf Auditoren achten:
- Path Traversal: nutzergesteuerte Eingaben, die ohne Normalisierung in
file_get_contents(),fopen(),include()oderrequire()fließen. - Dynamische Includes:
include $_GET['page'] . '.php'ist ein Lehrbuchfall für Remote- beziehungsweise Local File Inclusion. Auditoren markieren jeden dynamischen Include, auch wenn eine teilweise Absicherung ergänzt wurde. - Verarbeitung von Datei-Uploads: MIME-Typ-Validierung allein anhand des vom Client gelieferten
Content-Type-Headers (trivial fälschbar) statt serverseitiger Erkennung überfinfo. - Upload-Ablage: Dateien, die in ein Verzeichnis innerhalb des Web-Roots hochgeladen und direkt per URL abrufbar sind, umgehen die Anwendungslogik.
- Umgang mit temporären Dateien: Einsatz von
tmpfile()gegenüber unsicheren Mustern, die vorhersagbare Dateinamen in gemeinsam genutzten/tmp-Verzeichnissen anlegen.
7. Symfony-Firewall-Reihenfolge und framework-spezifische Risiken
Symfony-Anwendungen haben eine Klasse von Konfigurationsfehlern, die für generische Scanner unsichtbar bleibt.
Firewall-Reihenfolge: Symfony wertet Firewalls von oben nach unten aus und stoppt beim ersten Treffer. Eine als öffentlich gedachte Route, die vor dem authentifizierten Firewall-Pattern definiert ist, bleibt ungeschützt. Eine als geschützt gedachte Route, die nach einem zu breiten Pattern definiert ist, kann falsche Zugriffsregeln erben. Das Audit verfolgt jede Route bis zu ihrer wirksamen Firewall und prüft, ob die access_control-Liste dem beabsichtigten Autorisierungsmodell entspricht.
Security Voter und Rollenhierarchie: Eigene Voter, die supportsAttribute() fehlerhaft implementieren, können sich unbemerkt der Zugriffsentscheidung enthalten, sodass die konfigurierte Entscheidungsstrategie greift. Im Modus unanimous ist das sicher, im Standardmodus affirmative erlaubt ein sich enthaltender Voter, dass andere Voter den Zugriff gewähren.
CSRF-Token-Validierung: Die Form-Komponente von Symfony bringt standardmäßig CSRF-Schutz mit, er lässt sich aber pro Formular deaktivieren. Auditoren prüfen auf csrf_protection: false in Form-Types und auf AJAX-Endpunkte, die das Form-Handling vollständig umgehen, ohne eine eigene Token-Validierung mitzubringen.
Prioritätskonflikte bei Event-Listenern: Security-Listener, die mit falscher Priorität registriert sind, können durch andere Listener umgangen werden, die zuerst laufen und den Request-Zyklus abkürzen.
Wie KI-generierter Code die Angriffsfläche verändert
Legacy-PHP-Codebasen sammeln Sicherheitsschulden schrittweise an. Jemand schreibt ein unsicheres Muster, es wird kopiert, das Team lernt dazu und lässt es künftig weg, aber die alten Dateien bleiben. Die Angriffsfläche konzentriert sich auf die älteren Teile der Codebasis und gruppiert sich um dieselben Muster.
KI-generierter PHP-Code unterscheidet sich davon in zwei Punkten.
Inkonsistenz innerhalb einer einzigen Session. Ein LLM generiert eine Doctrine-Entity mit sauber parametrisierten Queries und anschließend, in derselben Session, eine Repository-Klasse, die eine Filter-Query per String-Interpolation zusammenbaut. Beides sieht auf den ersten Blick korrekt aus. Die Inkonsistenz entsteht nicht, weil das Modell das richtige Muster nicht kennt, sondern weil es Code statistisch generiert, nicht architektonisch. Security Audits KI-gestützter Codebasen finden mehr Varianz pro 1.000 Zeilen als Audits rein menschlich geschriebenen Codes.
Plausibler, aber falscher Sicherheitscode. KI-generierter Authentifizierungscode besteht oft eine oberflächliche Prüfung: Er ruft password_verify() auf, nutzt session_regenerate_id() und prüft eine Rolle, bevor er Daten zurückgibt. Häufig fehlt dem generierten Code jedoch der Kontext: Die Rollenprüfung erfolgt, bevor die Session authentifiziert ist, oder session_regenerate_id() wird ohne true aufgerufen (die alte Session bleibt aktiv), oder password_verify() ist zwar vorhanden, das Vergleichsergebnis steuert den Zugriff aber gar nicht. Solche Bugs sind schwerer zu finden als rohe SQL-Verkettung, weil sie strukturell korrekt aussehen.
Fehlende Defense in Depth. Legacy-Code hat zumindest meist die eingebauten Schutzmechanismen des Frameworks, selbst wenn der Anwendungscode schwach ist. KI-generierter Code lässt Framework-Konventionen mitunter vollständig aus, umgeht das Symfony-Form-Handling, erzeugt eigene Session-Wrapper oder baut eine eigene CSRF-Validierung. So entstehen Lücken, die das Framework sonst automatisch schließen würde.
Wie das schriftliche Ergebnis eines PHP Security Audits aussieht
Ein professionelles Security Audit liefert mehr als einen Scanner-Report. Das schriftliche Ergebnis umfasst typischerweise:
- Eine Management Summary zur allgemeinen Risikolage, zu den kritischsten Findings und zur empfohlenen Reihenfolge der Behebung.
- Ein Findings-Register, in dem jedes Problem nach Schweregrad klassifiziert ist (kritisch, hoch, mittel, niedrig), mit betroffener Datei und Zeilenbereich, einer Beschreibung der Schwachstelle, einem Proof of Concept beziehungsweise Reproduktionspfad und einer konkreten Empfehlung zur Behebung.
- Eine Referenz für sicheren Code, die das unsichere Muster dem korrigierten Muster gegenüberstellt, für die in dieser konkreten Codebasis häufigsten Problemtypen.
- Ein Konfigurations-Review zu
php.ini-Einstellungen, Webserver-Konfiguration und Sicherheitskonfiguration des Frameworks. - Eine CVE-Tabelle der Abhängigkeiten mit betroffenen Paketen, CVE-Kennungen, gepatchter Version und Kontext zur Ausnutzbarkeit (nicht jedes CVE ist in jeder Anwendung ausnutzbar).
- Aufwandsschätzungen zur Behebung, mit denen Engineering-Teams Sprint-Kapazität planen können.
Das Findings-Register ist der handlungsrelevanteste Teil. Ein gutes Audit listet nicht nur Schwachstellen auf, sondern gibt dem Entwicklungsteam einen klaren Weg vom Ist-Zustand zu einer belastbaren Ausgangsbasis.
Wie Sie Ihre Codebasis vor der Beauftragung vorbereiten
Ein PHP Code Auditing liefert bessere Ergebnisse, wenn die Codebasis vorbereitet ist. Die folgenden Schritte reduzieren die Zeit für Findings mit geringem Erkenntniswert und lenken das Audit auf das tatsächliche Risiko.
Führen Sie zuerst composer audit aus. Beheben oder dokumentieren Sie alle bekannten CVEs in Abhängigkeiten, bevor das Audit beginnt. Ein Audit, das viel Zeit auf bereits bekannte Probleme verwendet, ist kein guter Einsatz des Budgets.
Committen Sie composer.lock. Falls die Datei nicht im Repository liegt, fügen Sie sie hinzu. Das Audit braucht einen reproduzierbaren Abhängigkeitsstand.
Aktivieren Sie strikte Typen. declare(strict_types=1) in Dateien zu ergänzen, denen es fehlt, reduziert die Klasse von Typumwandlungsfehlern, die Auditoren sonst manuell nachverfolgen müssen. Außerdem wird die Codebasis im Review leichter nachvollziehbar.
Konsolidieren Sie die Umgebungskonfiguration. Stellen Sie sicher, dass alle Secrets aus Umgebungsvariablen oder einem Vault geladen werden und nicht aus committeten Dateien. Führen Sie vor dem Audit einen Scan der Git-Historie durch (mit Werkzeugen wie git-secrets oder trufflehog), um Secrets in der Commit-Historie zu identifizieren und zu rotieren.
Dokumentieren Sie die Authentifizierungsgrenzen. Eine einseitige Übersicht, welche Routen Authentifizierung erfordern, welche Rollen existieren und worauf jede Rolle zugreifen darf, spart im Audit Stunden an Reverse Engineering und führt zu einem klareren Findings-Register.
Lassen Sie Ihre Testsuite laufen. Auditoren verifizieren Korrekturen gegen die vorhandene Testabdeckung. Bei einer Codebasis ohne Tests muss die Behebungsphase das Schreiben von Tests einschließen, was die Projektlaufzeit verlängert.
Checkliste zur Selbsteinschätzung
Nutzen Sie diese Checkliste vor der Beauftragung eines vollständigen Audits, um die offensichtlichsten Lücken zu identifizieren. Jede Antwort mit "nein" ist ein Befund, der Aufmerksamkeit verdient.
| Bereich | Prüfpunkt |
|---|---|
| Authentifizierung | Passwörter mit password_hash() + BCRYPT oder ARGON2ID gehasht |
| Authentifizierung | session_regenerate_id(true) beim Login aufgerufen |
| Authentifizierung | Autorisierungsprüfungen serverseitig auf jeder geschützten Route erzwungen |
| SQL | Alle Queries nutzen Prepared Statements oder ORM ohne rohe Interpolation |
| SQL | Such-, Filter- und Sortierparameter gegen eine Allowlist validiert |
| Secrets | Keine Zugangsdaten in committeten Dateien oder in der Git-Historie |
| Secrets | Alle Secrets werden aus Umgebungsvariablen geladen |
| Secrets | Logging gibt keine Request-Parameter oder Header aus |
| Abhängigkeiten | composer.lock ins Repository committet |
| Abhängigkeiten | composer audit meldet keine offenen CVEs |
| Abhängigkeiten | Die PHP-Version erhält noch Sicherheitspatches |
| Sessions | session.cookie_secure = On in Produktion |
| Sessions | session.cookie_httponly = On |
| Sessions | session.cookie_samesite = Lax oder Strict |
| Dateiverarbeitung | Keine nutzergesteuerten Eingaben in include- oder require-Pfaden |
| Dateiverarbeitung | Hochgeladene Dateien außerhalb des Web-Roots gespeichert oder über einen Controller ausgeliefert |
| Symfony | Firewall-Reihenfolge gegen das beabsichtigte Zugriffsmodell geprüft |
| Symfony | CSRF-Schutz bei sensiblen Form-Types nicht deaktiviert |
| Symfony | Eigene Voter unter den Strategien unanimous und affirmative getestet |
Ein "nein" in den Zeilen zu Authentifizierung, SQL oder Secrets rechtfertigt eine sofortige Behebung, statt auf ein formales Audit zu warten.
Ein PHP Security Audit beauftragen
Ein PHP Security Audit ist am wertvollsten, wenn es auf ein konkretes Ergebnis zugeschnitten ist: die Vorbereitung auf eine Compliance-Prüfung, das Onboarding eines Enterprise-Kunden, der einen Bericht zur Sicherheitslage verlangt, das Abarbeiten eines Findings aus einem Penetrationstest oder die Validierung einer KI-gestützt entwickelten Codebasis vor dem ersten Produktivrelease.
Wolf-Tech führt PHP- und Symfony-Code-Audits im Rahmen unserer Praxis für Code-Quality-Consulting durch. Projekte laufen je nach Größe der Codebasis und Tiefe des Abhängigkeitsgraphen typischerweise ein bis drei Wochen. Das Ergebnis sind das oben beschriebene Findings-Register und das Konfigurations-Review, geliefert mit Unterstützung bei der Behebung.
Wenn Sie sich auf ein Audit vorbereiten oder besprechen möchten, was ein Review Ihrer konkreten Codebasis abdecken würde, schreiben Sie an hello@wolf-tech.io oder besuchen Sie wolf-tech.io.
FAQ
Was ist der Unterschied zwischen einem PHP Security Audit und einem Penetrationstest?
Ein Security Audit ist ein Review auf Code-Ebene: Auditoren lesen den Quelltext, verfolgen Datenflüsse und identifizieren Schwachstellen von innen. Ein Penetrationstest arbeitet von außen: Tester sondieren die laufende Anwendung ohne Zugriff auf den Quellcode. Beides ist sinnvoll. Ein Code-Audit findet in der Regel mehr Probleme pro Aufwandsstunde und liefert konkretere Handlungsempfehlungen.
Wie lange dauert ein PHP Security Audit?
Für eine PHP-/Symfony-Anwendung mit 50.000 bis 100.000 Zeilen und üblicher Komplexität dauert ein gründliches Audit fünf bis zehn Arbeitstage. Größere Codebasen, komplexe Abhängigkeitsgraphen oder umfangreiche KI-generierte Anteile verlängern die Laufzeit.
Deckt ein Security Audit auch das Datenbankschema ab?
Ja, soweit es im Projekt zugänglich ist. Auditoren prüfen die Tabellenstruktur auf Muster bei der Speicherung sensibler Daten (unverschlüsselte personenbezogene Daten, Zugangsdaten im Klartext) und auf Indexdesign, das Timing-Angriffe ermöglichen könnte.
Welche PHP-Version sollte ich einsetzen?
Stand Mitte 2026 PHP 8.2 oder 8.3. PHP 8.1 hat im Dezember 2025 das End-of-Life erreicht und erhält keine weiteren Sicherheitspatches. Eine nicht mehr unterstützte PHP-Version zu betreiben bedeutet, dass bekannte Schwachstellen in der Laufzeitumgebung selbst nicht behoben werden.

