WordPress-Plugin absichern: Nonces, Escaping und Sanitizing
Rechteprüfung, Nonces, Sanitizing und Escaping: welche WordPress-Funktion wann greift, mit Beispielen für Formulare, AJAX, REST-Endpunkte und Datenbankabfragen.
Ein WordPress-Plugin sichern Sie mit vier Bausteinen ab: Rechte prüfen mit current_user_can(), Absicht prüfen mit einer Nonce, Eingaben bereinigen mit Sanitizing-Funktionen und Ausgaben escapen unmittelbar vor der Ausgabe. Für Datenbankabfragen mit Variablen kommt $wpdb->prepare() hinzu. Wer diese Regeln konsequent umsetzt, verhindert den Großteil der Lücken, die in Plugins gemeldet werden: Cross-Site-Scripting, Cross-Site Request Forgery, SQL-Injection und fehlende Berechtigungsprüfungen.
Warum Plugin-Sicherheit so wichtig ist
Plugins laufen mit allen Rechten der WordPress-Installation. Sie können Beiträge löschen, Benutzer anlegen und die Datenbank lesen. Eine Lücke in einem Plugin ist deshalb eine Lücke in jeder Website, die es nutzt. Angreifer durchsuchen das Netz automatisiert nach verwundbaren Plugin-Versionen, sobald eine Schwachstelle bekannt wird. Wie Sie als Betreiber Erweiterungen generell sicher auswählen, beschreibt der Bereich Erweiterungen sicher nutzen.
Die vier Bausteine im Überblick
| Baustein | Schützt vor | Wichtige Funktionen | Wann |
|---|---|---|---|
| Rechteprüfung | unberechtigten Aktionen | current_user_can() |
vor jeder Aktion |
| Nonce | Cross-Site Request Forgery | wp_nonce_field(), check_admin_referer(), check_ajax_referer() |
bei jeder zustandsändernden Anfrage |
| Sanitizing | ungültigen und schädlichen Eingaben | sanitize_text_field(), absint(), sanitize_email() |
beim Empfang |
| Escaping | Cross-Site-Scripting | esc_html(), esc_attr(), esc_url(), wp_kses_post() |
direkt bei der Ausgabe |
1. Rechte prüfen
Prüfen Sie immer konkrete Fähigkeiten (Capabilities), nicht Rollen. Eine Rolle wie „Redakteur“ lässt sich umbenennen oder anpassen, eine Fähigkeit wie edit_posts beschreibt dagegen genau, was erlaubt ist.
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Keine Berechtigung.', 'meinplugin' ), 403 );
}
Geht es um ein bestimmtes Objekt, übergeben Sie dessen ID. So greifen auch feinere Regeln, etwa dass Autoren nur eigene Beiträge bearbeiten dürfen:
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
is_admin() ist keine Rechteprüfung. Die Funktion sagt nur, ob gerade eine Seite des Adminbereichs geladen wird, und liefert auch bei AJAX-Anfragen über admin-ajax.php true, selbst für nicht angemeldete Besucher.
2. Nonces gegen CSRF
Bei Cross-Site Request Forgery bringt eine fremde Website den Browser eines angemeldeten Administrators dazu, unbemerkt eine Anfrage an Ihre Website zu senden. Eine Nonce ist ein Token, das an Benutzer, Aktion und Zeitfenster gebunden ist. Fehlt es oder passt es nicht, wird die Anfrage abgelehnt.
Im Formular:
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<input type="hidden" name="action" value="meinplugin_speichern">
<?php wp_nonce_field( 'meinplugin_speichern', 'meinplugin_nonce' ); ?>
<input type="text" name="titel">
<?php submit_button(); ?>
</form>
Bei der Verarbeitung:
add_action( 'admin_post_meinplugin_speichern', 'meinplugin_speichern' );
function meinplugin_speichern() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( 'Keine Berechtigung.', 403 );
}
check_admin_referer( 'meinplugin_speichern', 'meinplugin_nonce' );
$titel = isset( $_POST['titel'] )
? sanitize_text_field( wp_unslash( $_POST['titel'] ) )
: '';
update_option( 'meinplugin_titel', $titel );
wp_safe_redirect( admin_url( 'options-general.php?page=meinplugin&gespeichert=1' ) );
exit;
}
check_admin_referer() bricht bei ungültiger Nonce ab. Für AJAX nutzen Sie check_ajax_referer(), für eigene Prüfungen wp_verify_nonce(), das nur false oder einen Wert zurückgibt. Links mit Aktionen, etwa zum Löschen, versehen Sie mit wp_nonce_url().
Wichtig: Nonces gehören zu angemeldeten Benutzern. Für Formulare, die anonyme Besucher abschicken, schützen sie nur eingeschränkt, dort brauchen Sie zusätzlich Maßnahmen gegen Spam und Missbrauch.
3. Eingaben bereinigen
WordPress maskiert Daten aus $_POST, $_GET und $_COOKIE historisch mit Backslashes. Entfernen Sie diese mit wp_unslash() und bereinigen Sie danach passend zum erwarteten Typ:
| Erwarteter Wert | Funktion |
|---|---|
| einzeiliger Text | sanitize_text_field() |
| mehrzeiliger Text | sanitize_textarea_field() |
| ganze Zahl ab 0 | absint() |
| E-Mail-Adresse | sanitize_email() |
| Schlüssel, Slug, interne Kennung | sanitize_key(), sanitize_title() |
| URL zum Speichern | esc_url_raw() oder sanitize_url() |
| HTML wie in Beiträgen | wp_kses_post() |
| Wert aus fester Liste | Abgleich mit in_array( $wert, $erlaubt, true ) |
Bereinigen ist nicht dasselbe wie Validieren. Validieren prüft, ob ein Wert überhaupt zulässig ist, und lehnt ihn sonst ab. Bei festen Auswahlmöglichkeiten ist eine Positivliste die sicherste Variante.
4. Ausgaben escapen
Die wichtigste Regel lautet: so spät wie möglich escapen, also direkt beim echo. Dann sieht jeder Leser des Codes sofort, dass die Ausgabe sicher ist, und es spielt keine Rolle, woher der Wert stammt.
<h2><?php echo esc_html( $titel ); ?></h2>
<a href="<?php echo esc_url( $link ); ?>" title="<?php echo esc_attr( $hinweis ); ?>">
<?php echo esc_html__( 'Mehr lesen', 'meinplugin' ); ?>
</a>
<div><?php echo wp_kses_post( $beschreibung ); ?></div>
| Kontext | Funktion |
|---|---|
| Text zwischen HTML-Tags | esc_html() |
| Wert in einem HTML-Attribut | esc_attr() |
URL in href oder src |
esc_url() |
| Inhalt einer Textarea | esc_textarea() |
| erlaubtes HTML | wp_kses_post() oder wp_kses() mit eigener Positivliste |
| Daten für JavaScript | wp_json_encode() oder wp_add_inline_script() |
| übersetzte Texte | esc_html__(), esc_attr__(), esc_html_e() |
Die Funktion muss zum Kontext passen. esc_html() in einem href-Attribut schützt zum Beispiel nicht vor javascript:-Links, dafür ist esc_url() zuständig.
„Der Wert kommt doch aus meiner eigenen Option, der ist sicher.“ Optionen, Metadaten und Beitragstitel können von anderen Plugins, Importen oder kompromittierten Konten stammen. Escapen Sie deshalb jede Ausgabe, auch Werte, die Sie selbst gespeichert haben.
Sichere Datenbankabfragen
Für eigene SQL-Abfragen mit Variablen ist $wpdb->prepare() Pflicht. Es setzt Werte über Platzhalter sicher ein:
global $wpdb;
$tabelle = $wpdb->prefix . 'meinplugin_eintraege';
$zeilen = $wpdb->get_results(
$wpdb->prepare(
"SELECT id, titel FROM {$tabelle} WHERE autor_id = %d AND status = %s",
$autor_id,
$status
)
);
Die Platzhalter %d für Ganzzahlen, %f für Kommazahlen und %s für Zeichenketten werden automatisch korrekt maskiert. Neuere WordPress-Versionen kennen zusätzlich %i für Tabellen- und Spaltennamen. Setzen Sie Platzhalter nie selbst in Anführungszeichen. Für LIKE-Suchen maskieren Sie den Suchbegriff vorher mit $wpdb->esc_like(). Wo es geht, nutzen Sie ohnehin die WordPress-APIs wie WP_Query, get_posts() oder die Options- und Meta-Funktionen, die intern bereits sicher arbeiten.
AJAX und REST-Endpunkte
Viele Lücken stecken nicht in Formularen, sondern in Schnittstellen. Zwei Regeln:
- AJAX: Aktionen, die mit
wp_ajax_nopriv_registriert sind, kann jeder Besucher aufrufen. Auch beiwp_ajax_(nur angemeldet) prüfen Sie Rechte und Nonce, denn „angemeldet“ kann auch ein Abonnent sein. - REST API: Jede Route braucht einen
permission_callback. Für wirklich öffentliche Endpunkte setzen Sie bewusst__return_true, für alle anderen eine echte Rechteprüfung.
register_rest_route( 'meinplugin/v1', '/eintrag/(?P<id>\d+)', array(
'methods' => 'POST',
'callback' => 'meinplugin_rest_speichern',
'permission_callback' => function ( $request ) {
return current_user_can( 'edit_post', (int) $request['id'] );
},
'args' => array(
'titel' => array( 'sanitize_callback' => 'sanitize_text_field' ),
),
) );
Die REST API prüft bei angemeldeten Benutzern zusätzlich eine eigene Nonce, die WordPress-Skripte automatisch mitsenden.
Weitere Schutzmaßnahmen
- Direktaufruf verhindern:
defined( 'ABSPATH' ) || exit;am Anfang jeder PHP-Datei. - Weiterleitungen absichern:
wp_safe_redirect()stattwp_redirect()für Ziele, die aus Eingaben stammen. - Datei-Uploads prüfen: Dateityp mit
wp_check_filetype_and_ext()kontrollieren und Uploads überwp_handle_upload()abwickeln. - Keine Geheimnisse im Code: API-Schlüssel gehören in Optionen oder Konstanten der
wp-config.php, nicht ins Repository. - Regelmäßig prüfen: Werkzeuge wie der „Plugin Check“ und PHP_CodeSniffer mit den WordPress-Coding-Standards finden fehlendes Escaping und ungeprüfte Eingaben automatisch.
Checkliste für jede Funktion
- Wer darf das?
current_user_can()mit passender Fähigkeit. - Ist die Anfrage gewollt? Nonce prüfen.
- Was kommt herein?
wp_unslash(), dann passend bereinigen oder validieren. - Wie geht es in die Datenbank? WordPress-APIs oder
$wpdb->prepare(). - Wie geht es hinaus? Kontextgerecht escapen, direkt bei der Ausgabe.
Den Aufbau eines Plugins, in dem diese Funktionen zum Einsatz kommen, zeigt der Artikel WordPress-Plugin erstellen. In welchen Hooks Formulare und Einstellungen verarbeitet werden, erklärt WordPress-Hooks erklärt.
Fragen und Antworten
- Ersetzt eine Nonce die Rechteprüfung?
- Nein. Eine Nonce belegt nur, dass die Anfrage aus einer Seite stammt, die WordPress zuvor für diesen Benutzer und diese Aktion erzeugt hat. Ob die Person die Aktion ausführen darf, prüfen Sie zusätzlich mit current_user_can. Erst beides zusammen schützt zuverlässig.
- Wie lange ist eine WordPress-Nonce gültig?
- Standardmäßig bis zu 24 Stunden. WordPress rechnet in zwei Zeitfenstern zu je zwölf Stunden, eine Nonce aus dem aktuellen oder vorherigen Fenster wird akzeptiert. Anders als der Name vermuten lässt, kann sie in dieser Zeit mehrfach verwendet werden.
- Reicht es, Eingaben zu bereinigen, wenn ich sie später ausgebe?
- Nein. Bereinigen beim Eingang und Escapen bei der Ausgabe sind zwei getrennte Schutzschichten. Daten können auch aus anderen Quellen in die Datenbank gelangen, etwa durch andere Plugins oder Importe. Deshalb gilt: jede Ausgabe escapen, unabhängig davon, woher der Wert stammt.
- Muss ich auch im Adminbereich escapen?
- Ja. Auch im Adminbereich können manipulierte Daten Schadcode ausführen, und dort sind die Folgen oft schlimmer, weil Administratoren mit vollen Rechten angemeldet sind. Escaping gilt für jede Ausgabe, ob Frontend, Backend, E-Mail oder JSON-Antwort.
Weiterlesen
- Plugin erstellen
Vom leeren Ordner zum funktionierenden Plugin: Header, Ordnerstruktur, Aktivierungs-Hook, ein Shortcode mit Einstellung und eine saubere uninstall.php.
- Actions und Filter
Actions führen Code aus, Filter verändern Werte: wie add_action und add_filter funktionieren, was Priorität und Argumentzahl bewirken und wie Sie eigene Hooks anbieten.
- Sicherheit
Welche Berechtigungen heikel sind, woran Sie bösartige Erweiterungen erkennen, warum Updates und Übernahmen ein Risiko sind und was im Ernstfall zu tun ist.