— build 26.06.0 —
build 26.06.0 $ cat ./quellcode.md (patch_26.06)

Quellcode

# Magazin für Web-Entwicklung, CMS-Migration und Developer-Praxis

← Magazin 01. Juni 2026
Security · 11 min

WordPress 6.7 Plugin-Sicherheit — OWASP-Top-10-Praxis

WordPress ist 2025 das CMS mit den meisten CVEs (~4.300 Einträge im Ecosystem). Wer Plugins entwickelt, sollte die OWASP Top 10 nicht abstrakt, sondern als konkrete WordPress-Patterns kennen.

WordPress 6.7 (Stand Mitte 2026) treibt nach offiziellen W3Techs-Zahlen rund 43 Prozent aller Websites weltweit. Mit dieser Reichweite kommt eine entsprechende Angriffsfläche: Patchstack und Wordfence haben für 2025 zusammen etwa 4.300 CVEs im WordPress-Ecosystem dokumentiert, fast 95 Prozent davon in Plugins, nicht im Core. Wer ein Plugin schreibt, schreibt potenziell Code, der auf 100.000 Sites läuft — und jeder Fehler skaliert mit.

Die OWASP Top 10 sind das Standard-Framework für Web-Sicherheit. Dieser Artikel übersetzt sie in konkrete WordPress-Plugin-Patterns.

A01: Broken Access Control

Die häufigste Klasse echter Plugin-Vulns. Eine Plugin-Funktion, die im Admin-Backend aufgerufen wird, aber nicht prüft, ob der aktuelle User die nötige Capability hat.

// FALSCH
add_action('admin_post_delete_item', function() {
    $id = (int) $_POST['id'];
    wp_delete_post($id, true);
});

// RICHTIG
add_action('admin_post_delete_item', function() {
    if (!current_user_can('delete_posts')) {
        wp_die('Insufficient permissions', 403);
    }
    check_admin_referer('delete_item_nonce');
    $id = (int) $_POST['id'];
    wp_delete_post($id, true);
});

Zwei Checks, die in jeder schreibenden Plugin-Funktion stehen müssen: current_user_can() für die Capability und check_admin_referer() (oder check_ajax_referer() für AJAX) gegen CSRF. Capabilities sind feiner als Rollen — eine schreibende Funktion sollte die spezifische Capability prüfen, nicht pauschal manage_options.

REST-API-Endpoints brauchen ihren eigenen permission_callback:

register_rest_route('myplugin/v1', '/items/(?P<id>\d+)', [
    'methods'             => 'DELETE',
    'callback'            => 'myplugin_delete_item',
    'permission_callback' => fn() => current_user_can('delete_posts'),
]);

Ein permission_callback der '__return_true' zurückgibt, ist ein offener Endpoint. Das ist Mitte 2026 immer noch die zweithäufigste Quelle von WordPress-CVEs.

A02: Cryptographic Failures

WordPress-Core verwendet seit 6.4 password_hash() mit PASSWORD_BCRYPT als Default. Plugin-Code, der eigene User-Tabellen pflegt (Membership-Plugins, B2B-Portale), sollte niemals md5/sha1 für Passwörter verwenden.

// FALSCH
$hash = md5($password);

// RICHTIG
$hash = wp_hash_password($password);
// oder, wenn explizit Core-unabhängig:
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);

Für Verifikation:

if (wp_check_password($password, $stored_hash, $user_id)) { ... }

Sensible Daten in der Datenbank (API-Keys, OAuth-Refresh-Tokens) gehören in wp_options mit autoload = 'no' und verschlüsselt mit openssl_encrypt (AES-256-GCM) unter einem in wp-config.php gespeicherten Schlüssel — nicht im Klartext.

A03: Injection

SQL-Injection ist die archetypische WordPress-Vuln. Der Core liefert mit $wpdb->prepare() das Werkzeug — Plugin-Code, das es nicht nutzt, ist unentschuldbar.

global $wpdb;

// FALSCH
$results = $wpdb->get_results(
    "SELECT * FROM {$wpdb->prefix}items WHERE status = '{$_GET['status']}'"
);

// RICHTIG
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}items WHERE status = %s",
        $_GET['status']
    )
);

Die Placeholder sind streng typisiert: %s für String, %d für Integer, %f für Float. Seit WordPress 6.2 wird %i für Identifier (Tabellennamen, Spaltennamen) unterstützt — vorher mussten Identifier mit Allow-Lists validiert werden.

Für XSS gilt: jede Ausgabe escapen. Die Faustregel ist „escape late, escape close to output”:

echo esc_html($user_input);
echo esc_attr($attr_value);
echo esc_url($url);
echo wp_kses_post($html_with_some_tags_allowed);

wp_kses_post() erlaubt die HTML-Tags, die auch im Post-Editor erlaubt sind — sicherer Mittelweg zwischen „kompletter Strip” und „raw HTML”.

A05: Security Misconfiguration

WordPress-Core setzt einige Sicherheits-Header (X-XSS-Protection, X-Content-Type-Options), aber CSP und X-Frame-Options nicht. Plugins, die ein Admin-UI rendern, sollten Sicherheits-Header via send_headers-Hook ergänzen:

add_action('send_headers', function() {
    if (is_admin()) {
        header('X-Frame-Options: DENY');
        header(
            "Content-Security-Policy: default-src 'self'; " .
            "script-src 'self' 'unsafe-inline'; " .
            "img-src 'self' data: https:; " .
            "connect-src 'self';"
        );
        header('Referrer-Policy: strict-origin-when-cross-origin');
    }
});

CSP mit 'unsafe-inline' ist nicht ideal — aber die WordPress-Admin-UI selbst nutzt inline-Scripts an vielen Stellen, ein striktes CSP würde das Backend kaputt machen. Strikte CSP ist nur auf isolierten Plugin-Settings-Pages realistisch, die du selbst kontrollierst.

A06: Vulnerable and Outdated Components

Plugins, die composer.json für vendor-Dependencies nutzen, sollten regelmäßig composer audit laufen lassen:

composer audit --format=json > audit.json

In CI als Quality-Gate verankern — ein neuer CVE in einer Composer-Dependency bricht den Build. Für JavaScript-Build-Tools (npm audit, yarn npm audit) analog.

WordPress selbst pflegt mit wp_get_plugin_dependencies() (seit 6.5) eine Mechanik, um Inter-Plugin-Dependencies zu deklarieren. Wer auf Patchstack abonniert ist, bekommt Plugin-CVE-Feeds automatisiert.

A07: Identification and Authentication Failures

WordPress-Core liefert keine 2FA out of the box. Plugin-Entwickler, deren Plugin Admin-Funktionen exponiert, sollten mit etablierten 2FA-Plugins (Two-Factor von dem Core-Contributor-Team, WP 2FA von Melapress) zusammenarbeiten — nicht eigene 2FA-Implementierungen schreiben.

Rate-Limiting für Login-Endpoints ist ebenfalls nicht Core. Für plugin-eigene Login-Flows (REST-Endpoint für eine Mobile-App):

add_action('rest_api_init', function() {
    register_rest_route('myplugin/v1', '/login', [
        'methods'             => 'POST',
        'callback'            => 'myplugin_rest_login',
        'permission_callback' => function() {
            $ip = $_SERVER['REMOTE_ADDR'];
            $key = "myplugin_login_attempts_{$ip}";
            $attempts = (int) get_transient($key);
            if ($attempts >= 5) return false;
            set_transient($key, $attempts + 1, MINUTE_IN_SECONDS * 15);
            return true;
        },
    ]);
});

Fünf Versuche pro fünfzehn Minuten, IP-basiert. Im Produktiv-Setup hinter Cloudflare lieber CF-Connecting-IP auswerten, sonst sieht das Plugin nur die Cloudflare-Edge-IP.

A08: Software and Data Integrity Failures

WordPress-Plugin-Updates aus dem offiziellen Repository sind nicht digital signiert — der Update-Server liefert das ZIP, der Core verifiziert den SHA1-Hash gegen die Repository-Metadaten, mehr nicht. Premium-Plugins mit eigenem Update-Server sollten zumindest ein signiertes Manifest ausliefern:

add_filter('upgrader_pre_download', function($reply, $package, $upgrader) {
    if (str_starts_with($package, 'https://updates.myplugin.com/')) {
        $sig_url = $package . '.sig';
        $sig = wp_remote_get($sig_url);
        if (!verify_signature($sig['body'], MYPLUGIN_PUBLIC_KEY, $package)) {
            return new WP_Error('bad_signature', 'Update signature invalid');
        }
    }
    return $reply;
}, 10, 3);

Für die Signatur-Verifikation sodium_crypto_sign_verify_detached() aus libsodium (in PHP seit 7.2 verfügbar).

A09: Security Logging and Monitoring Failures

WordPress hat keinen Audit-Log out of the box. Für sicherheitskritische Aktionen im eigenen Plugin solltest du wenigstens ein einfaches Logging via error_log() oder eine dedizierte Tabelle führen:

function myplugin_audit_log(string $action, array $context = []): void {
    $entry = [
        'time'    => gmdate('c'),
        'user'    => get_current_user_id(),
        'ip'      => $_SERVER['REMOTE_ADDR'] ?? '',
        'action'  => $action,
        'context' => $context,
    ];
    error_log('MYPLUGIN_AUDIT: ' . wp_json_encode($entry));
}

WP-CLI hat seit 2.10 einen wp doctor check-Befehl, der Security-Posture-Checks fährt — in CI nach jedem Deployment einbauen.

A10: Server-Side Request Forgery

Plugins, die externe URLs abrufen (Webhook-Subscriber, Image-Importer), öffnen SSRF-Vektoren. wp_remote_get() selbst hat keinen URL-Filter — der muss manuell stehen.

function myplugin_safe_remote_get(string $url) {
    $allowed_hosts = ['api.partner.com', 'cdn.partner.com'];
    $host = wp_parse_url($url, PHP_URL_HOST);
    if (!in_array($host, $allowed_hosts, true)) {
        return new WP_Error('blocked_host', 'Host not on allow-list');
    }
    $ip = gethostbyname($host);
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
        return new WP_Error('private_ip', 'Resolved to private IP');
    }
    return wp_remote_get($url, ['timeout' => 5, 'redirection' => 0]);
}

Wichtig sind drei Schichten: Host-Allow-List, DNS-Rebinding-Schutz (IP-Range-Check), und redirection = 0, damit der externe Server keinen 302 auf http://169.254.169.254/ schicken kann.

Pen-Test-Tools und Security-Plugins

Für ein finales Audit vor Release:

  • WPScan. CLI-Tool und Online-DB für bekannte WordPress-/Plugin-/Theme-Vulns. wpscan --url https://staging.example.com --enumerate vp,vt,u.
  • Sucuri SiteCheck. Web-basierter Scanner, findet Defacements und Blacklist-Einträge.
  • Wordfence Security. Plugin mit Web-Application-Firewall, Brute-Force-Schutz, Malware-Scan. Free-Version reicht für die meisten Sites.
  • Sucuri Security. Konkurrenz-Plugin mit Activity-Audit-Log.
  • iThemes Security (jetzt Solid Security). File-Change-Detection, 2FA, Brute-Force-Schutz.

Fazit

OWASP Top 10 ist kein abstraktes Framework — für WordPress-Plugins lässt sich jeder Punkt in zwei bis fünf konkrete Code-Patterns übersetzen. Wer diese Patterns in seinen Plugin-Boilerplate übernimmt (PHPStan WordPress-Erweiterung + WordPress-Coding-Standards + die hier gezeigten Capability-/Nonce-/Prepare-Patterns), eliminiert die häufigsten 80 Prozent der realistischen Vulns. Die letzten 20 Prozent (Logikfehler, Race Conditions, Crypto-Edge-Cases) bleiben — dafür existiert das externe Audit.


Ressort: Security