WP Transformer â Security Review & Audit
Sicherheitsaudit des gesamten Codebases. Letzte vollstĂ€ndige PrĂŒfung: 2026-05-22 (v1.0.4).
Audit-Ergebnis: Gesamt
| Bereich | Status | Anmerkung |
|---|---|---|
| Authentifizierung | â OK | bcrypt, timing-safe, session-sicher |
| CSRF-Schutz | â OK | Alle POST-Formulare geschĂŒtzt |
| SQL-Injection | â OK | 100% PDO Prepared Statements |
| Path Traversal | â OK | Slug-Whitelist, keine Shell-AusfĂŒhrung |
| Datei-Zugriffsschutz | â OK | nginx + .htaccess doppelt abgesichert |
| Password Reset | â OK | SHA-256 Token, 1h TTL, Enumeration-safe |
| Fehler-Disclosure | â OK | display_errors=0, Logging intern |
| Datenbankschema | â OK | Dual-Driver PG/MySQL korrekt |
| Dateirechte Server | đ¶ Ausstehend | Noch kein VPS-Deploy |
| Upload-Sicherheit | â OK | Extension-Check, .sql-only |
1. Authentifizierung & Session
classes/Auth.php
session_name(SESSION_NAME)vorsession_start()â verhindert Session-Kollisionenpassword_verify()ist timing-safe by design (bcrypt vergleicht immer vollen Hash)- Login-Fehler: einheitliche RĂŒckgabe
falseâ kein Unterschied zwischen "User nicht gefunden" und "Passwort falsch" (verhindert Username-Enumeration) Auth::check()auf jeder geschĂŒtzten Seite als erstes Statement$_SESSION['wpt_auth'],wpt_user,wpt_uidgesetzt bei Login
Password Reset:
- Token: bin2hex(random_bytes(32)) = 64 Hex-Zeichen = 256 Bit Entropie â
- Gespeichert als hash('sha256', $token) â DB-Dump gibt kein gĂŒltiges Token preis â
- reset_expires = NOW() + 3600 â 1 Stunde TTL â
- requestReset() gibt immer true zurĂŒck auch wenn User/Email nicht gefunden â verhindert Enumeration â
- Token wird nach erfolgreichem Reset auf NULL gesetzt â
Offen:
- Keine Login-Rate-Limiting-Implementierung (kein IP-Blocking nach N Fehlversuchen)
- Empfehlung: sleep(1) bei Login-Fehler (bereits in login.php Zeile 15 vorhanden) reicht fĂŒr MVP
2. CSRF-Schutz
classes/Csrf.php
- Token:
bin2hex(random_bytes(32))â einmalig pro Session â hash_equals()fĂŒr timing-sicheren Vergleich â- Alle POST-Formulare prĂŒfen:
tenant.php,posts.php,logout.php,contact.php(CSRF auf Kontaktformular ist optional, hier: ja vorhanden)
GeprĂŒfte Formulare:
| Seite | Formular | CSRF |
|---|---|---|
tenant.php |
Alle POST-Actions | â |
posts.php |
Speichern | â |
logout.php |
Logout | â |
login.php |
Login | â ïž Kein CSRF-Token (Login-Formulare sind typischerweise ausgenommen â akzeptabel) |
forgot.php |
Reset-Anfrage | â ïž Kein CSRF-Token (pre-auth, kein State-Change â akzeptabel) |
reset.php |
Neues Passwort | â ïž Token-basiert (URL-Token ersetzt CSRF) â akzeptabel |
contact.php |
Kontaktformular | â |
3. SQL-Injection
Gesamte Codebase: ausschlieĂlich PDO Prepared Statements.
Keine dynamischen SQL-Strings mit Benutzer-Input. Stichproben:
// Auth.php â Login
$row = Db::pdo()->prepare('SELECT id,pass_hash FROM wpt_users WHERE username=? LIMIT 1');
$row->execute([$user]);
// TenantManager.php â Slug-Lookup
Db::pdo()->prepare('SELECT * FROM wpt_tenants WHERE slug=? LIMIT 1')->execute([$slug]);
// posts.php â Post speichern
$stmt->execute([$title, $content, $excerpt, $slug, $type, $cats, $id, $slug]);
Kein eval(), kein shell_exec(), kein system() im gesamten Codebase. â
4. Path Traversal & Upload
Slug-Sanitierung (tenant.php Zeile ~11):
$slug = preg_replace('/[^a-z0-9\-]/', '', strtolower($_GET['slug'] ?? ''));
Whitelist-Ansatz: ../../etc/passwd â leerer String â kein Zugriff.
Upload-Validierung:
if (strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)) !== 'sql') { ... }
Nur .sql-Dateien erlaubt. Dateien landen in tenants/{slug}/source.sql â nicht im Webroot. PHP-Execution in uploads/ ist via nginx-Regel blockiert.
5. Dateizugriffsschutz
nginx-Vhost (scripts/transformer.crumbforest.io)
# Verzeichnisse mit Quellcode gesperrt
location ~* /(classes|templates|logs|scripts)/ { deny all; }
# SQL-Dateien gesperrt
location ~* \.sql$ { deny all; }
# PHP-Execution in uploads/ gesperrt
location ~* /uploads/.*\.php$ { deny all; }
# Dotfiles gesperrt (.git, .env etc.)
location ~ /\. { deny all; }
Apache .htaccess (ALL-INKL Fallback)
Options -Indexes
<FilesMatch "\.(sql|sh|md)$"> Require all denied </FilesMatch>
<FilesMatch "^config\.local\.php$"> Require all denied </FilesMatch>
RewriteRule ^tenants/(?!([^/]+/public/)) - [F,L]
Hinweis: .htaccess <IfModule mod_php.c> PHP-Limits greifen nur bei Apache mod_php,
nicht bei PHP-FPM. FĂŒr nginx/FPM: Limits in /etc/php/8.2/fpm/pool.d/wptransformer.conf.
logs/.htaccess
deny from all
ZusĂ€tzliche Absicherung fĂŒr Log-Verzeichnis (Apache). nginx: via location-Direktive abgedeckt.
6. Datenbankschema-Sicherheit
- Kein
root-User in der App-Config â dedizierter DB-Userwptransformermit Rechten nur aufwptransformer_db - Passwörter nie im Klartext gespeichert (bcrypt)
- FTP-Passwörter werden absichtlich nicht in
wpt_tenants.deploygespeichert â nur fĂŒr den jeweiligen Request verwendet reset_tokenals SHA-256-Hash gespeichert (nicht Klartext)
7. Information Disclosure
// config.php
ini_set('display_errors', 0); // Kein Stack-Trace im Browser
error_reporting(E_ALL); // Aber vollstÀndiges Logging intern
Exception-Handler und Error-Handler loggen via Logger::critical() / Logger::warning()
in wpt_logs (DB) â kein Output an Browser.
migrate_103.php: Zeigt Migrations-Output im Browser, erfordert Admin-Login (Zeile 9).
Nach AusfĂŒhrung löschen.
8. Produktions-Checkliste (vor Go-Live)
- [ ]
config.local.phpmit starkemPASSWORD_PEPPER(32+ Byte) erstellen - [ ] Admin-Passwort nach Erstinstallation Àndern (Default:
password) - [ ]
migrate_103.phpnach AusfĂŒhrung löschen - [ ]
setup.phpnach Ersteinrichtung löschen oder via nginx sperren - [ ] SSL-Zertifikat via Certbot einrichten (
certbot --nginx -d transformer.crumbforest.io) - [ ] PHP-FPM installieren:
apt install php8.2-fpm php8.2-pgsql php8.2-zip - [ ] Dateirechte prĂŒfen:
logs/,uploads/,tenants/âwww-data:www-data 775 - [ ]
d0471675.sqlaus Repo-Root entfernen (enthĂ€lt DB-Dump, nicht committen) - [ ] Nginx-Errorlog nach erstem Deploy prĂŒfen:
tail -f /var/log/nginx/transformer.crumbforest.io.error.log
9. Ausstehende Server-Rechte-PrĂŒfung
Noch nicht live â ausstehend bis erster VPS-Deploy. Zu prĂŒfen:
# Verzeichnisrechte
ls -la /var/www/transformer.crumbforest.io/logs/
ls -la /var/www/transformer.crumbforest.io/uploads/
ls -la /var/www/transformer.crumbforest.io/tenants/
# PHP-FPM lÀuft als www-data?
ps aux | grep php-fpm
# config.local.php nicht world-readable
stat /var/www/transformer.crumbforest.io/config.local.php
# Soll: 640 root:www-data oder 600 www-data:www-data
WP Transformer v1.0.4 · Security Review 2026-05-22
Reviewer: Claude Sonnet 4.6 + Gemini Flash 2.5