LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Blog-Übersicht /WordPress-Plugins & Tricks / Sanitizing, Escaping, Nonces, Capabilities im Plugin-Review
Diesen Artikel in anderen Sprachen lesen:

Sanitizing, Escaping, Nonces, Capabilities im Plugin-Review

Sanitizing, Escaping, Nonces, Capabilities im Plugin-Review

Management Summary

WordPress-Plugins benötigen mehrere ergänzende Schutzmaßnahmen beim Speichern von Einstellungen und Anzeigen von Inhalten. Berechtigungen legen fest, welche Konten Änderungen vornehmen dürfen. Anfrageprüfungen erschweren fremd ausgelöste Aktionen; Eingaberegeln halten ungeeignete Daten fern. Eine sichere Ausgabe verhindert, dass gespeicherter Text im Browser als unerwünschter Befehl wirkt. Diese Aufgaben sind nicht austauschbar. Die Unterscheidung hilft, Plugin-Qualität über einen erfolgreichen automatischen Test hinaus zu beurteilen.

Inhalt
  1. Was die einzelnen Prüfungen bedeuten
  2. 1. Typen prüfen, Werte bereinigen und Regeln durchsetzen
  3. 2. Den Speicherhandler selbst schützen
  4. 3. Die endgültige HTML-Ausgabe absichern
  5. 4. SQL benötigt Platzhalter und erlaubte Namen
  6. 5. REST-Berechtigung und bereinigte Werte prüfen
  7. Was die Prüfungen belegen
  8. Fragen und Antworten
  9. Quellen

Ein Plugin kann eine gefälschte Anfrage abweisen und trotzdem unsichere Daten speichern. Ebenso können alle Felder bereinigt sein, während ein unberechtigtes Konto Einstellungen ändern darf. Vier getrennte Prüfungen verhindern unterschiedliche Fehler: Berechtigung, Anfragekennzeichen, gültige Eingaben und sichere Ausgabe.

Die Beispiele verwenden einen Hinweis mit Beschriftung, optionalem Link, eingeschränktem HTML und einer Zeilenzahl. Die Codeblöcke sind zusammengehörige Lehrbeispiele, kein vollständig installierbares Plugin: Einstellungsseite, Plugin-Header, Übersetzungen, Installation und Behandlung von Datenbankfehlern gehören weiterhin in das umgebende Plugin.

Was die einzelnen Prüfungen bedeuten

PrüfungKonkrete FrageBeispiel
CapabilityDarf dieses Konto Website-Einstellungen ändern?current_user_can('manage_options')
NonceEnthält die Anfrage ein gültiges Kennzeichen für Aktion und Sitzung?wp_verify_nonce()
ValidierungIst die Zeilenzahl eine ganze Zahl zwischen 1 und 50?-1, 1.5 und Arrays abweisen.
BereinigungWelche Teile des Texts oder HTML dürfen bleiben?sanitize_text_field(), wp_kses_post()
EscapingKann der Wert sicher an dieser HTML-Stelle erscheinen?esc_attr() für einen Attributwert.

Eine WordPress-Nonce ist kein Einmalpasswort und beweist weder eine menschliche Absicht noch die Verwendung eines bestimmten Formulars. Das Kennzeichen hilft gegen Cross-Site Request Forgery (CSRF): Eine fremde Seite versucht dabei, eine Aktion über eine bestehende Anmeldung auszulösen. Während der Gültigkeitsdauer ist das Kennzeichen wiederverwendbar; standardmäßig beträgt diese ungefähr 12 bis 24 Stunden. Rechteprüfungen bleiben erforderlich.

Drei getrennte Gruppen: Recht und Kennzeichen, gültige Werte sowie Speicherung mit Platzhaltern und kontextgerechte Ausgabe.
Berechtigung, Anfrageschutz und Datenverarbeitung benötigen eigene Prüfungen.

1. Typen prüfen, Werte bereinigen und Regeln durchsetzen

Ein Feld namens link kann über link[]=wert als Array, also als Sammlung von Werten, ankommen. Funktionen für Zeichenketten sollten solche Eingaben nicht ungeprüft erhalten. Die Hilfsfunktion unten erwartet die vier benötigten Felder als Zeichenketten, ignoriert Zusatzfelder und liefert entweder ein bereinigtes Array oder WP_Error. Bei WordPress-Formulardaten erwartet die Funktion bereits mit wp_unslash() behandelte Eingaben.

PHP
<?php
function lw_nb_review_label( $raw ) {
    if ( ! is_string( $raw ) ) {
        return new WP_Error( 'invalid_label', 'Label must be text.', array( 'status' => 400 ) );
    }
    $label = sanitize_text_field( $raw );
    if ( 1 !== preg_match( '/^.{1,100}$/us', $label ) ) {
        return new WP_Error( 'invalid_label', 'Label must contain 1 to 100 characters after cleaning.', array( 'status' => 400 ) );
    }
    return $label;
}

function lw_nb_review_values( $input ) {
    if ( ! is_array( $input ) ) {
        return new WP_Error( 'invalid_input', 'Expected form fields.', array( 'status' => 400 ) );
    }
    foreach ( array( 'label', 'link', 'message', 'rows' ) as $key ) {
        if ( ! isset( $input[ $key ] ) || ! is_string( $input[ $key ] ) ) {
            return new WP_Error( 'invalid_input', 'A required text field is missing or malformed.', array( 'status' => 400 ) );
        }
    }
    $label = lw_nb_review_label( $input['label'] );
    if ( is_wp_error( $label ) ) {
        return $label;
    }
    if ( 1 !== preg_match( '/^(?:[1-9]|[1-4][0-9]|50)$/D', $input['rows'] ) ) {
        return new WP_Error( 'invalid_rows', 'Rows must be an integer from 1 to 50.', array( 'status' => 400 ) );
    }
    $link = sanitize_url( $input['link'], array( 'http', 'https' ) );
    $parts = wp_parse_url( $input['link'] );
    if ( '' !== $input['link'] && (
        '' === $link || false === $parts || empty( $parts['host'] ) ||
        empty( $parts['scheme'] ) ||
        ! in_array( strtolower( $parts['scheme'] ), array( 'http', 'https' ), true )
    ) ) {
        return new WP_Error( 'invalid_link', 'Link must be empty or an absolute HTTP(S) URL.', array( 'status' => 400 ) );
    }
    return array(
        'label' => $label,
        'link' => $link,
        'message' => wp_kses_post( $input['message'] ),
        'rows' => (int) $input['rows'],
    );
}

wp_unslash() entfernt die zusätzlichen Backslashes, die WordPress Formulardaten hinzufügt. Das ist keine Sicherheitsprüfung. REST-Parameter benötigen diesen formularspezifischen Schritt nicht. Auch absint() validiert nicht: Aus -5 kann 5 werden, Nachkommastellen werden abgeschnitten. Das Muster für die Zeilenzahl weist stattdessen Eingaben außerhalb des gewählten Formats zurück.

Die Beschriftung wird auch nach der Bereinigung geprüft. Beispielsweise enthält <b></b> vorher Zeichen, wird danach aber leer. Die Hilfsfunktion weist diesen Fall ab. Die Grenze von 100 Zeichen zählt Unicode-Codepunkte, nicht unbedingt 100 sichtbare Symbole; ein Buchstabe mit kombinierbarem Akzent kann zwei Codepunkte verwenden. Die URL-Regel erlaubt einen leeren Wert oder eine absolute HTTP(S)-Adresse mit Host. Die Regel bestätigt weder die Vertrauenswürdigkeit des Ziels noch die Sicherheit eines serverseitigen Abrufs.

2. Den Speicherhandler selbst schützen

Ein geschützter Menüeintrag schützt keinen separaten admin_post_...-Handler. Die Berechtigung ist dort erneut zu prüfen. Das Formular verwendet method="post", zeigt auf die mit esc_url() geschützte Adresse admin_url('admin-post.php'), enthält die versteckte Aktion lw_nb_review_save und gibt wp_nonce_field('lw_nb_review_save', 'lw_nb_nonce') aus. Der folgende Handler prüft Methode, Berechtigung und Kennzeichen vor dem Speichern:

PHP
<?php
add_action( 'admin_post_lw_nb_review_save', 'lw_nb_review_save' );

function lw_nb_review_save() {
    if ( 'POST' !== ( $_SERVER['REQUEST_METHOD'] ?? '' ) ) {
        wp_die( 'POST required.', '', array( 'response' => 405 ) );
    }
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die( 'Permission denied.', '', array( 'response' => 403 ) );
    }
    $nonce = isset( $_POST['lw_nb_nonce'] ) && is_string( $_POST['lw_nb_nonce'] )
        ? sanitize_text_field( wp_unslash( $_POST['lw_nb_nonce'] ) )
        : '';
    if ( ! wp_verify_nonce( $nonce, 'lw_nb_review_save' ) ) {
        wp_die( 'Invalid request token.', '', array( 'response' => 403 ) );
    }
    $values = lw_nb_review_values( wp_unslash( $_POST ) );
    if ( is_wp_error( $values ) ) {
        wp_die( esc_html( $values->get_error_message() ), '', array( 'response' => 400 ) );
    }
    update_option( 'lw_nb_review_notice', $values );
    wp_safe_redirect( admin_url( 'options-general.php' ) );
    exit;
}

Die Capability bezeichnet eine konkrete Berechtigung statt eines Rollennamens. Für einen einzelnen Beitrag eignet sich eine objektbezogene Prüfung wie current_user_can('edit_post', $post_id). Ein eigenes gültiges Kennzeichen verschafft einem Abonnentenkonto keine Berechtigung manage_options. Auch ein gestohlenes Kennzeichen eines anderen Kontos passt nicht automatisch zur Sitzung des Abonnentenkontos.

3. Die endgültige HTML-Ausgabe absichern

Gespeicherte Daten sind nicht automatisch vertrauenswürdig: Ein Import, eine ältere Plugin-Version oder ein anderer Schreibpfad könnte Werte verändert haben. Escaping gehört an die Stelle, an der HTML endgültig entsteht, auch bei einer vom Shortcode zurückgegebenen Zeichenkette. Die Funktion hängt von der Ausgabestelle ab:

StelleFunktion
Text zwischen Tagsesc_html()
Attributwert in Anführungszeichenesc_attr()
URL in href, src oder actionesc_url()
Inhalt einer Textareaesc_textarea()
Eingeschränktes HTML wie in Beiträgenwp_kses_post()

Beispielsweise gibt esc_html($label) eine Beschriftung als Text aus; wp_kses_post($message) erhält dagegen erlaubtes HTML in einer Nachricht. Diese Aufgaben sind nicht austauschbar. sanitize_url() bereinigt eine URL zum Speichern und ersetzt nicht esc_url() bei der HTML-Ausgabe. Eine JSON-Antwort hat ein anderes Ausgabeformat: HTML-Escaping erfolgt dort, wo ein Client den Wert später in HTML einfügt.

4. SQL benötigt Platzhalter und erlaubte Namen

Bereinigter Text macht das Zusammenfügen von SQL-Zeichenketten nicht sicher. Das folgende Beispiel setzt eine vorhandene eigene Protokolltabelle voraus. Erlaubt sind nur drei Sortierspalten und eine begrenzte Ergebniszahl. Die Variablen stammen aus geprüften Werten statt direkt aus Anfrageparametern.

PHP
<?php
function lw_nb_review_get_log( $orderby, $limit ) {
    global $wpdb;
    $allowed = array( 'saved_at', 'label', 'user_id' );
    if ( ! is_string( $orderby ) || ! in_array( $orderby, $allowed, true ) ) {
        $orderby = 'saved_at';
    }
    if ( ! is_int( $limit ) || $limit < 1 || $limit > 50 ) {
        $limit = 10;
    }
    return $wpdb->get_results(
        $wpdb->prepare(
            'SELECT user_id, label, saved_at FROM %i ORDER BY %i DESC LIMIT %d',
            $wpdb->prefix . 'lw_nb_log',
            $orderby,
            $limit
        )
    );
}

%i schützt einen Tabellen- oder Spaltenbezeichner und benötigt WordPress ab 6.2. Die Positivliste legt fest, welcher Bezeichner erlaubt ist; der Platzhalter allein entscheidet das nicht. Werte verwenden Platzhalter wie %s und %d oder eine Formatliste bei $wpdb->insert(). Eine neue Datenbankspalte gehört nicht automatisch auf die Liste erlaubter Sortierspalten.

5. REST-Berechtigung und bereinigte Werte prüfen

Eine REST-Route ist eine Adresse, über die Programme Daten abrufen oder Änderungen anfordern. Solche Routen benötigen einen ausdrücklichen permission_callback. Ohne diesen Callback kann eine Route trotz WordPress-Warnung öffentlich erreichbar bleiben. Bei absichtlich öffentlichen Daten macht __return_true die Entscheidung sichtbar. Die folgende Schreibroute erlaubt nur Konten mit manage_options und verwendet ausschließlich POST.

PHP
<?php
add_action( 'rest_api_init', function () {
    register_rest_route( 'lw-nb-review/v1', '/notice', array(
        'methods' => WP_REST_Server::CREATABLE,
        'permission_callback' => function () {
            return current_user_can( 'manage_options' );
        },
        'args' => array(
            'label' => array(
                'type' => 'string',
                'required' => true,
                'minLength' => 1,
                'maxLength' => 100,
                'validate_callback' => 'rest_validate_request_arg',
                'sanitize_callback' => 'lw_nb_review_label',
            ),
        ),
        'callback' => function ( WP_REST_Request $request ) {
            $label = lw_nb_review_label( $request->get_param( 'label' ) );
            if ( is_wp_error( $label ) ) {
                return $label;
            }
            $stored = get_option( 'lw_nb_review_notice', array() );
            $stored = is_array( $stored ) ? $stored : array();
            $stored['label'] = $label;
            update_option( 'lw_nb_review_notice', $stored );
            return rest_ensure_response( array( 'label' => $label ) );
        },
    ) );
} );

Die ausdrückliche Schemaprüfung kontrolliert Typ und Länge der eingehenden Daten. Die eigene Bereinigung prüft zusätzlich die fertige Beschriftung und verhindert dadurch leere Werte nach dem Entfernen von Tags. Bei Cookie-Authentifizierung prüft der Core eine Nonce zur Aktion wp_rest aus X-WP-Nonce oder _wpnonce. Ohne dieses Kennzeichen gelten solche REST-Anfragen als nicht angemeldet. Andere Verfahren wie Anwendungspasswörter haben einen eigenen Authentifizierungsablauf; die Rechteprüfung gilt weiterhin.

Was die Prüfungen belegen

Die Hilfsfunktionen bestanden 41 isolierte Fälle mit WordPress 7.1.3 und PHP 8.4.27: Arrays, fehlende Felder, negative und gebrochene Zeilenzahlen, leere bereinigte Beschriftungen, URL-Protokolle und erlaubtes HTML. Auch die PHP-Syntaxprüfung für Hilfsfunktionen, Formularhandler und REST-Beispiel war erfolgreich. Speicherhandler und REST-Route wurden nicht gegen Live-Einstellungen ausgeführt. Die Ergebnisse behaupten weder eine vollständige Plugin-Prüfung noch einen fehlerfreien Plugin-Check-Bericht.

Eine vollständige Integration benötigt zusätzlich Tests mit berechtigtem Konto, Konto mit weniger Rechten und ohne Anmeldung, mit fehlenden oder veralteten Kennzeichen, Datenbankfehlern und HTML-Ausgabe im Browser. Plugin Check liefert hilfreiche Hinweise; ein leerer Bericht beweist jedoch nicht, dass jede Aktion passend abgesichert ist.

Fragen und Antworten

Benötigt ein rein lesender Sortierparameter eine Nonce?

Eine andere Anzeigereihenfolge verändert allein keine gespeicherten Daten. Typprüfung und Positivliste bleiben nötig; private Ergebnisse benötigen weiterhin eine Zugriffsprüfung. Eine Nonce ersetzt keine dieser Aufgaben.

Verhindert eine Nonce wiederholte Übermittlungen?

Nein. WordPress-Kennzeichen sind während ihrer Gültigkeitsdauer wiederverwendbar. Zahlungen oder andere nur einmal zulässige Aktionen benötigen eine eigene Absicherung gegen doppelte Ausführung.

Reicht einmaliges Bereinigen aus?

Nein. Eingaberegeln, Rechteprüfungen, SQL-Platzhalter und kontextgerechte Ausgabe haben unterschiedliche Aufgaben. Eine bereinigte Beschriftung kann weiterhin ein Anführungszeichen enthalten, das im HTML-Attribut geschützt werden muss.

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Kontakt

Eine Frage oder eine Idee für ein Webprojekt? Schreib mir kurz, worum es geht. Das Formular nutzt reCAPTCHA zum Schutz vor Spam.

Kommentar schreiben

Erfahrungen mit anderen Plugins oder Hosting-Umgebungen und Rückfragen zur Einrichtung sind hier willkommen.

Die E-Mail-Adresse wird nicht veröffentlicht. Pflichtfelder sind mit einem Stern versehen.