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) vor session_start() — verhindert Session-Kollisionen
  • password_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_uid gesetzt 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-User wptransformer mit Rechten nur auf wptransformer_db
  • Passwörter nie im Klartext gespeichert (bcrypt)
  • FTP-Passwörter werden absichtlich nicht in wpt_tenants.deploy gespeichert — nur fĂŒr den jeweiligen Request verwendet
  • reset_token als 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.php mit starkem PASSWORD_PEPPER (32+ Byte) erstellen
  • [ ] Admin-Passwort nach Erstinstallation Ă€ndern (Default: password)
  • [ ] migrate_103.php nach AusfĂŒhrung löschen
  • [ ] setup.php nach 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.sql aus 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