Secrets für kleine PHP-Anwendungen - Zugangsdaten aus dem Code halten und austauschbar machen
Eine kleine PHP-Anwendung benötigt früher oder später ein Datenbankpasswort, ein API-Token oder einen Schlüssel zum Signieren von Nachrichten. Wo sollte dieser Wert liegen? Ihn direkt in einer PHP-Datei abzulegen ist einfach, bindet den Zugangswert aber an den Code, der ihn verwendet. Die Verlagerung in eine Umgebungsvariable wirkt sauberer, beantwortet jedoch noch nicht, wer den Wert lesen kann, wie er den Server erreicht oder was nach einem Leak geschehen soll.
Das sinnvolle Ziel ist nicht, ein unsichtbares Versteck zu finden. Eine Anwendung muss ein Geheimnis irgendwann im Klartext erhalten, wenn sie es verwenden soll. Realistischer ist es, Code und Zugangswerte zu trennen, die Reichweite jedes Zugangswerts zu begrenzen, unnötige Kopien zu reduzieren und den Austausch zu einem normalen statt beängstigenden Vorgang zu machen.
Dieser Artikel entwickelt ein solches Modell für eine kleine PHP-Anwendung auf einem VPS oder Home-Server. Er setzt keinen Enterprise-Vault voraus und behauptet nicht, dass eine Speichermethode immer die sicherste sei. Der passende Mechanismus hängt von Service-Manager, Betriebssystem, Deployment-Pfad und Bedrohungsmodell ab.
Zuerst klären, was tatsächlich geheim ist
Ein Geheimnis ist ein Wert, dessen Besitz eine bestimmte Fähigkeit oder Vertrauensstellung gewährt. Datenbankpasswörter, Bearer-API-Tokens, Webhook-Signaturgeheimnisse und private Schlüssel sind typische Beispiele. Ein öffentlicher Hostname, eine Locale, ein Log-Level oder ein Feature-Flag kann sich zwischen Deployments unterscheiden, ohne vertraulich sein zu müssen.
Diese Unterscheidung ist wichtig, weil sich Konfiguration und Geheimnisse überschneiden, aber nicht identisch sind. The Twelve-Factor App behandelt Zugangswerte als deploymentabhängige Konfiguration und fordert, solche Konfiguration vom Code zu trennen. Das ist eine nützliche Grenze. Es bedeutet weder, dass jeder Konfigurationswert verschlüsselt gespeichert werden muss, noch dass jede Umgebungsvariable wie ein Geheimnis geschützt ist.
Benutzerpasswörter bilden eine weitere Kategorie. Eine Anwendung überprüft sie normalerweise mit einem dafür vorgesehenen Passwort-Hashing-Verfahren, statt später das ursprüngliche Passwort abzurufen. Ein betriebliches Datenbankpasswort muss für die Anwendung dagegen meist wieder verfügbar sein, damit sie sich an der Datenbank authentifizieren kann. Werden diese beiden Probleme vermischt, entsteht schlechter Rat: Hashing eignet sich, wenn der ursprüngliche Wert nicht mehr benötigt wird, ersetzt aber keine Speicherung für einen Zugangswert, den die Anwendung vorlegen muss.
Warum Hard-Coding schon vor einem Angriff scheitert
MITREs CWE-798 definiert fest einprogrammierte Zugangswerte als Softwareschwäche. Die unmittelbare Gefahr ist die Offenlegung gegenüber allen, die den Quellcode oder das Artefakt lesen können. Ebenso wichtig ist jedoch das betriebliche Problem. Ein im Code eingebetteter Zugangswert wird leicht zwischen Deployments geteilt, in Beispiele kopiert und nur durch ein Code-Release geändert. Sein Lebenszyklus wird mit dem Lebenszyklus der Anwendung verknüpft.
Versionskontrolle macht eine versehentliche Offenlegung dauerhaft. .env in .gitignore aufzunehmen ist eine sinnvolle Vorsorge für eine noch nicht versionierte lokale Datei. Die Git-Dokumentation stellt jedoch klar, dass Ignore-Regeln bereits versionierte Dateien nicht betreffen. Die Datei umzubenennen oder im neuesten Commit zu löschen, macht einen Zugangswert in der älteren Historie ebenfalls nicht ungültig.
Auch Vorlagen erfordern Disziplin. Eine versionierte .env.example kann erforderliche Variablennamen und harmlose Beispielformate dokumentieren, sollte aber Platzhalter statt funktionierender Entwicklungs- oder Produktionszugangswerte enthalten:
APP_ENV=production
DB_HOST=127.0.0.1
DB_NAME=app
DB_USER=replace-me
DB_PASS=replace-me
WEBHOOK_SIGNING_SECRET=replace-me
Das Beispiel beschreibt eine Schnittstelle zwischen Deployment und Anwendung. Es ist selbst kein Secret Store.
Fünf Belange trennen, nicht nur zwei Dateien
„Geheimnisse aus Git fernhalten“ ist notwendig, aber unvollständig. Ein wartbares Design trennt fünf Belange:
- Code: kennt Namen und erwartete Form erforderlicher Werte, aber keine Produktionswerte.
- Speicherung: hält den maßgeblichen Wert mit Zugriffskontrollen, die zum Deployment passen.
- Bereitstellung: stellt den Wert dem vorgesehenen Prozess beim Start oder zur Laufzeit bereit.
- Verwendung: hält den Wert aus URLs, normalen Logs, Fehlerseiten und unbeteiligten Kindprozessen heraus.
- Lebenszyklus: dokumentiert Zuständigkeit, Geltungsbereich, Erstellung, Rotation, Ablauf soweit unterstützt und Widerruf.
Das OWASP Secrets Management Cheat Sheet behandelt Zugriffskontrolle, Auditing, Rotation, Widerruf, Ablauf, Backup und Wiederherstellung als Teile desselben Systems. Eine kleine Website kann sie mit einem kurzen Inventar und einem eingeschränkten lokalen Mechanismus statt mit einer großen Plattform umsetzen. Die Fragen bleiben auch bei bescheidenen Werkzeugen dieselben.
Fehlende Konfiguration zum Startfehler machen
PHP kann Prozessumgebungsvariablen mit getenv() lesen. Laut PHP-Handbuch liefert eine benannte Abfrage false, wenn die Variable nicht existiert. Erforderliche Geheimnisse sollten deshalb ausdrücklich validiert werden, statt stillschweigend auf einen leeren String, ein Testpasswort oder einen im Code eingebetteten Wert zurückzufallen.
<?php
declare(strict_types=1);
function requiredEnvironmentValue(string $name): string
{
$value = getenv($name);
if ($value === false || $value === '') {
throw new RuntimeException("Missing required configuration: {$name}");
}
return $value;
}
$pdo = new PDO(
'mysql:host=' . requiredEnvironmentValue('DB_HOST')
. ';dbname=' . requiredEnvironmentValue('DB_NAME')
. ';charset=utf8mb4',
requiredEnvironmentValue('DB_USER'),
requiredEnvironmentValue('DB_PASS'),
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
Dieses Beispiel stellt nur eine Eigenschaft her: Ohne erforderliche Werte verweigert die Anwendung den Start. Es validiert keine Hostnamen, wählt keine Datenbankrechte, verbirgt nicht die Prozessumgebung und konfiguriert PHP-FPM nicht. Das bleibt Aufgabe des Deployments. Ein Fehlerhandler für die Produktion muss außerdem verhindern, dass Exception und Stacktrace zu einer öffentlichen Antwort werden.
Sicherer ist es, den Namen einer fehlenden Variable zu protokollieren als ihren Wert. Dieselbe Regel gilt für Diagnoseseiten, Support-Pakete, CI-Ausgaben, Shell-Tracing und serialisierte Konfiguration. Nachträgliche Schwärzung ist fehleranfällig; den Wert gar nicht erst in normale Ausgaben aufzunehmen ist der bessere Standard.
Umgebungsvariablen lösen die Trennung, nicht das gesamte Bedrohungsmodell
Umgebungsvariablen sind praktisch, weil Service-Manager, Container, Shells und CI-Systeme sie bereitstellen können, ohne den PHP-Code zu ändern. Außerdem kann dasselbe Build in Entwicklung und Produktion unterschiedliche Werte verwenden. Für viele kleine Deployments ist das eine deutliche Verbesserung gegenüber versionierten Zugangswerten.
Eine Umgebungsvariable ist jedoch ein Bereitstellungskanal und Prozesszustand, kein vollständiger Secret-Management-Dienst. Ihre Exposition hängt von Betriebssystem, Service-Manager, Debugging-Werkzeugen, Prozessbeziehungen, Crash-Behandlung und den Personen ab, die die Deployment-Konfiguration einsehen können. Werte können zudem an Kindprozesse vererbt werden, sofern die Laufzeitumgebung dies nicht verhindert.
Eine lokale .env-Datei fügt eine weitere Schicht hinzu: Eine Bibliothek liest eine Klartextdatei und überträgt die Werte in die Umgebung oder Konfiguration der Anwendung. Die Datei muss daher außerhalb des öffentlichen Document Root liegen, aus der Versionskontrolle ausgeschlossen sein, nur von der erforderlichen Deployment- oder Service-Identität gelesen werden können und bewusst in der Backup-Richtlinie berücksichtigt werden. Die Bezeichnung „environment“ ändert ihr Dateisystemrisiko nicht.
Die Bereitstellung über die Umgebung kann eine angemessene Basis sein, wenn der Host nur wenige vertrauenswürdige Administratoren hat, Datei- und Service-Rechte kontrolliert werden und die Zugangswerte eng begrenzte Rechte besitzen. Weniger überzeugend ist sie, wenn viele Workloads ein Konto teilen, Deployments Werte durch mehrere Systeme kopieren oder häufige Rotation erforderlich ist.
Eine eingeschränkte Datei kann einfacher und ehrlicher sein
Eine von root bereitgestellte Konfigurationsdatei außerhalb von Repository und Web Root kann auf einem einzelnen Server eine verständliche Option sein. Das Service-Konto der Anwendung erhält Lesezugriff; Webserver und unbeteiligte Benutzer erhalten ihn nicht bloß aus Bequemlichkeit. Der Dateipfad kann stabil bleiben, während das Deployment-Werkzeug den Inhalt ersetzt.
Das ist nicht automatisch sicherer als eine Umgebungsvariable. Klartext bleibt auf dem Datenträger, Backups können ihn vervielfachen und ein kompromittierter Anwendungsprozess kann jeden Zugangswert lesen, den er berechtigterweise verwendet. Der Vorteil besteht darin, dass Dateieigentum, Rechte, Mount-Grenzen und Dateizugriff direkt nachvollzogen werden können. Der Nachteil ist, dass Lebenszyklusfunktionen wie dynamische Ausgabe, Audit-Trails und automatische Rotation an anderer Stelle geschaffen werden müssen.
Den Entschlüsselungsschlüssel neben das verschlüsselte Geheimnis zu legen löst das Problem nicht. Verschlüsselung ruhender Daten kann eine kopierte Festplatte oder ein Backup mit einem passenden Schlüsselmodell schützen. Wenn dasselbe Konto jedoch Chiffretext und Schlüssel lesen kann, bringt sie gegen die Kompromittierung dieses Kontos wenig.
Unter systemd Service Credentials erwägen
Für ein kompatibles Debian-Deployment bietet systemd eine weitere Möglichkeit. Die Projektdokumentation zu System and Service Credentials beschreibt Zugangswerte, die beim Start eines Dienstes übernommen und als Dateien unter dem in $CREDENTIALS_DIRECTORY genannten Verzeichnis bereitgestellt werden. Der Zugriff ist auf den Service-Benutzer beschränkt, und die eigentlichen Zugangsdaten werden nicht wie ein Umgebungswert entlang des Prozessbaums weitergegeben.
systemd unterstützt das Laden von Zugangswerten aus Dateien und kann auch verschlüsselte Credentials verarbeiten. Die genauen Direktiven und Verschlüsselungsfunktionen hängen von der installierten systemd-Version ab, weshalb vor dem Deployment das lokale Handbuch geprüft werden muss. Der Anwendungscode muss außerdem die Credential-Datei lesen, oder eine Bootstrap-Schicht muss sie in die von der Anwendung erwartete Schnittstelle übersetzen.
Eine Warnung wird leicht übersehen: Laut systemd sollten literale SetCredential=-Daten in einer Unit-Datei nicht für sensible Geheimnisse verwendet werden, weil Unit-Dateien lesbar sind. Direktiven zum Laden von Dateien und verschlüsselten Credentials haben andere Eigenschaften. Ein Passwort in eine neue Art öffentlicher Konfiguration zu kopieren bleibt eine Kopie des Passworts.
Die Befugnisse jedes Zugangswerts reduzieren
Speicherung kann einen unnötig mächtigen Zugangswert nicht ausgleichen. Eine öffentlich erreichbare Anwendung benötigt normalerweise kein Datenbankadministratorkonto. Ein Thumbnail-Worker braucht möglicherweise Schreibzugriff auf einen Medienbereich, aber keine Berechtigung zur Benutzerverwaltung. Ein Webhook-Prüfer benötigt das Signaturgeheimnis eines Endpunkts, nicht sämtliche externen API-Tokens.
OWASP und CWE-798 betonen beide eingeschränkten Zugriff und begrenzte Rechte. Praktisch bedeutet das, Zugangswerte nach Dienst und Umgebung zu trennen, nur die erforderlichen Datenbank- oder API-Operationen zu erlauben und einen gemeinsam genutzten „Master“-Wert zu vermeiden. Kurzlebigere Zugangswerte können die Nutzungsdauer einer Kopie verkürzen, sofern Aussteller und Deployment sie unterstützen. Ein Ablauf ohne zuverlässige Erneuerung kann jedoch selbst einen Verfügbarkeitsfehler verursachen.
Ein Inventar sollte vier grundlegende Fragen beantworten: Wer ist für dieses Geheimnis zuständig, welcher Dienst verwendet es, welche Befugnisse verleiht es und wie wird es ersetzt? Der geheime Wert gehört nicht in das Inventar. Eine Liste aus Namen, Zuständigen, Verbrauchern und Rotationsverfahren genügt, um vergessene Zugangswerte sichtbar zu machen.
Rotation vor dem ersten Notfall planen
Rotation ist mehr als die Erzeugung eines neuen zufälligen Strings. Der Aussteller muss den neuen Zugangswert akzeptieren, die Anwendung muss ihn erhalten, das Deployment muss überprüft und der alte Wert anschließend widerrufen werden. Manche Systeme erlauben eine Übergangsphase mit zwei gültigen Werten, andere verlangen einen koordinierten Wechsel. Der Vertrag des Anbieters bestimmt die sichere Reihenfolge.
- Einen Ersatz mit dem kleinsten erforderlichen Geltungsbereich erstellen.
- Ihn über den gewählten Speicher- und Bereitstellungsmechanismus verfügbar machen.
- Nur die Verbraucher neu starten oder laden, die ihn benötigen.
- Die beabsichtigte Funktion prüfen, ohne den Wert auszugeben.
- Den alten Zugangswert widerrufen und, sofern ein Test sicher ist, bestätigen, dass er nicht mehr funktioniert.
- Das Inventar aktualisieren und veraltete Kopien gemäß Richtlinie aus Deployment-Systemen und Backups entfernen.
Für eine fehlgeschlagene Rotation sollte ein definierter Rollback- oder Wiederherstellungspfad existieren. Das bedeutet aber nicht immer, den alten Wert wieder zu aktivieren. Wenn ein Kompromiss den Wechsel ausgelöst hat, stellt die Wiederherstellung des offengelegten Zugangswerts den Vorfall wieder her.
Ein Geheimnis in Git als offengelegt behandeln
Die erste Reaktion muss die Fähigkeit eindämmen, nicht das Repository sauber aussehen lassen. Die GitHub-Anleitung zu Secret Scanning empfiehlt, einen betroffenen Zugangswert sofort zu rotieren. Der Widerruf ist wichtig, weil Forks, Klone, Caches, Logs, Pull-Request-Texte und weitere Kopien eine Bearbeitung der Historie überleben können.
Eine besonnene Reaktion identifiziert Zugangswert und Geltungsbereich, widerruft oder rotiert ihn beim Aussteller, stellt den Ersatz bereit, prüft verfügbare Nutzungsprotokolle und sucht nach weiteren Kopien, ohne sie auszugeben. Eine Bereinigung des Repositorys kann weiterhin sinnvoll sein, besonders bei sensiblen Daten, die entfernt werden müssen. Sie ersetzt aber nicht die Ungültigmachung des alten Zugangswerts. Das Umschreiben einer geteilten Historie verursacht zudem Koordinationsaufwand und sollte dem aktuellen Verfahren des Hosting-Anbieters folgen.
Eine kompakte Prüfung für einen kleinen PHP-Dienst
- Kein Produktionszugangswert steckt in PHP, einem versionierten Beispiel, einem Container-Image oder einer Datei im öffentlichen Document Root.
- Erforderliche Werte führen beim Start zu einem sicheren Abbruch; Entwicklungsstandards können nicht unbemerkt die Produktion erreichen.
- Die PHP-Service-Identität kann nur die benötigten Zugangswerte lesen.
- Datenbankbenutzer und API-Tokens besitzen die engsten praktikablen Rechte.
- Logs, Fehlerseiten, Diagnosen, CI-Ausgaben und Support-Pakete geben keine geheimen Werte aus.
- Backups und kopierte Deployment-Dateien sind im Expositionsmodell enthalten.
- Jedes Geheimnis hat einen Zuständigen, Verbraucher, Zweck und ein getestetes Austauschverfahren.
- Ein offengelegter Wert kann widerrufen werden, ohne sich auf die Bereinigung der Git-Historie zu verlassen.
Fazit
Secret Management für eine kleine PHP-Anwendung beginnt nicht mit dem Kauf eines Vaults. Es beginnt damit, Zugangswerte nicht an Code zu binden und anschließend zu fragen, wo jeder Wert gespeichert wird, wie er einen einzelnen Prozess erreicht, was dieser Prozess damit tun kann und wie der Wert ersetzt werden soll.
Umgebungsvariablen können als erster Bereitstellungsmechanismus ausreichen; eine eingeschränkte Datei kann auf einem Host leichter zu prüfen sein; systemd Credentials können bei entsprechender Unterstützung eine engere Service-Grenze bieten; ein dedizierter Secret Manager kann sich lohnen, wenn die Zahl der Identitäten und Deployments sowie die Rotationsanforderungen wachsen. Keine dieser Optionen beseitigt die Notwendigkeit von Least Privilege oder einer Reaktion auf Leaks.
Der ehrliche Test lautet nicht, ob ein Geheimnis unmöglich zu finden ist. Entscheidend ist, ob unnötige Leser und Kopien entfernt wurden und ob sich ein einzelner offengelegter Zugangswert ersetzen lässt, bevor er zu einem dauerhaften Teil der Anwendung wird.
References
- OWASP Cheat Sheet Series, „Secrets Management Cheat Sheet“.
- MITRE CWE, „CWE-798: Use of Hard-coded Credentials“, zuletzt aktualisiert am 30. April 2026.
- PHP Documentation Group, „getenv“.
- Adam Wiggins, „The Twelve-Factor App: Config“, zuletzt aktualisiert 2017.
- systemd project, „System and Service Credentials“.
- GitHub Docs, „Secret scanning“.
- Git project, „gitignore Documentation“.
