LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Przegląd bloga /Wtyczki i triki WordPress / Sanityzacja, escaping, Nonce i Capability w przeglądzie...
Ten artykuł w innych językach:

Sanityzacja, escaping, Nonce i Capability w przeglądzie wtyczek

Sanityzacja, escaping, Nonce i Capability w przeglądzie wtyczek

Management Summary

Wtyczki WordPressa potrzebują kilku uzupełniających się zabezpieczeń przy zapisie ustawień i wyświetlaniu treści. Uprawnienia określają, które konta mogą wprowadzać zmiany. Kontrola żądań utrudnia działania wywołane przez obce strony, a reguły wejścia odrzucają niewłaściwe dane. Bezpieczne wyświetlanie zapobiega traktowaniu zapisanego tekstu jako niepożądanych poleceń w przeglądarce. Każda ochrona ma inne zadanie; zrozumienie różnic pomaga oceniać jakość wtyczki poza samym wynikiem automatycznego testu.

Spis treści
  1. Znaczenie poszczególnych kontroli
  2. 1. Typy danych, oczyszczanie i reguły wartości
  3. 2. Ochrona samego handlera zapisu
  4. 3. Zabezpieczenie końcowego HTML
  5. 4. SQL: symbole zastępcze i dozwolone nazwy
  6. 5. Uprawnienia REST i kontrola oczyszczonej wartości
  7. Co potwierdzają wykonane próby
  8. Pytania i odpowiedzi
  9. Źródła

Wtyczka może odrzucać sfałszowane żądania, a mimo to zapisywać niebezpieczne dane. Może też oczyszczać każde pole, lecz pozwalać niewłaściwemu kontu zmieniać ustawienia. Oddzielne kontrole zapobiegają różnym błędom: sprawdzenie uprawnień, tokenu żądania, poprawności danych i bezpiecznego wyjścia.

Przykłady dotyczą komunikatu z etykietą, opcjonalnym odsyłaczem, ograniczonym HTML i liczbą wierszy. Bloki kodu są współpracującymi przykładami edukacyjnymi, a nie kompletną wtyczką do instalacji: ekran ustawień, nagłówek wtyczki, tłumaczenia, instalacja i obsługa błędów bazy nadal należą do otaczającej wtyczki.

Znaczenie poszczególnych kontroli

KontrolaKonkretne pytaniePrzykład
CapabilityCzy konto może zmieniać ustawienia witryny?current_user_can('manage_options')
NonceCzy żądanie ma token ważny dla tej akcji i sesji?wp_verify_nonce()
WalidacjaCzy liczba wierszy jest całkowita i mieści się od 1 do 50?Odrzucenie -1, 1.5 i tablic.
SanityzacjaKtóre części tekstu lub HTML mogą pozostać?sanitize_text_field(), wp_kses_post()
EscapingCzy wartość może bezpiecznie trafić w to miejsce HTML?esc_attr() dla atrybutu.

Nonce WordPressa nie jest hasłem jednorazowym i nie dowodzi zamiaru osoby ani użycia konkretnego formularza. Pomaga przeciwdziałać Cross-Site Request Forgery (CSRF), gdy obca strona próbuje wywołać działanie z wykorzystaniem istniejącego logowania. Token można ponownie wykorzystać w okresie ważności, domyślnie około 12–24 godzin. Kontrola uprawnień nadal jest konieczna.

Trzy oddzielne grupy: uprawnienie i token, poprawne wartości oraz parametryzowany zapis i zabezpieczenie wyjścia.
Uprawnienia, ochrona żądania i przetwarzanie danych wymagają osobnych kontroli.

1. Typy danych, oczyszczanie i reguły wartości

Pole link może nadejść jako tablica, czyli zbiór wartości, przez link[]=wartosc. Funkcje przeznaczone dla tekstu nie powinny otrzymywać takiej wartości bez kontroli. Poniższa funkcja pomocnicza oczekuje czterech potrzebnych pól jako ciągów znaków, pomija dodatkowe pola i zwraca oczyszczoną tablicę albo WP_Error. Dla danych formularza WordPressa oczekuje wcześniejszego użycia wp_unslash().

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() usuwa dodatkowe ukośniki odwrotne dodawane przez WordPress do danych formularza. Nie jest kontrolą bezpieczeństwa. Parametry REST nie wymagają tego kroku właściwego formularzom. Również absint() nie jest walidacją: może zmienić -5 w 5 i obciąć część ułamkową. Wzorzec liczby wierszy zamiast tego odrzuca wartości spoza wybranego formatu.

Etykieta jest sprawdzana także po oczyszczeniu. Na przykład <b></b> zawiera wcześniej znaki, ale później staje się puste. Funkcja odrzuca ten przypadek. Limit 100 znaków liczy punkty kodowe Unicode, niekoniecznie 100 widocznych symboli; litera z łączącym akcentem może zajmować dwa punkty kodowe. Reguła URL dopuszcza pustą wartość lub bezwzględny adres HTTP(S) z hostem. Nie potwierdza zaufania do celu ani bezpieczeństwa pobierania przez serwer.

2. Ochrona samego handlera zapisu

Ograniczenie dostępu do menu ustawień nie chroni osobnego handlera admin_post_.... Handler musi ponownie sprawdzić uprawnienie. Formularz używa method="post", kieruje do admin_url('admin-post.php') zabezpieczonego przez esc_url(), zawiera ukrytą akcję lw_nb_review_save oraz wynik wp_nonce_field('lw_nb_review_save', 'lw_nb_nonce'). Poniższy handler przed zapisem sprawdza metodę, uprawnienie i token:

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;
}

Capability oznacza konkretne uprawnienie, a nie nazwę roli. Dla edycji konkretnego wpisu właściwa jest kontrola uwzględniająca obiekt, na przykład current_user_can('edit_post', $post_id). Własny poprawny token subskrybenta nie nadaje uprawnienia manage_options. Token wykradziony z innego konta również nie jest automatycznie ważny dla sesji subskrybenta.

3. Zabezpieczenie końcowego HTML

Zapisane dane nadal wymagają ostrożności: import, starsza wersja wtyczki lub inna ścieżka zapisu mogły zmienić wartości. Escaping należy wykonać przy ostatecznym tworzeniu HTML, także w ciągu zwracanym przez shortcode. Funkcja zależy od miejsca wyjścia:

MiejsceFunkcja
Tekst między znacznikamiesc_html()
Wartość atrybutu w cudzysłowieesc_attr()
URL w href, src lub actionesc_url()
Zawartość textareaesc_textarea()
Ograniczone HTML jak w treści wpisówwp_kses_post()

Na przykład esc_html($label) wyświetla etykietę jako tekst, natomiast wp_kses_post($message) zachowuje dozwolone HTML w komunikacie. Te zadania nie są wymienne. sanitize_url() oczyszcza URL do zapisu, lecz nie zastępuje esc_url() przy tworzeniu HTML. Odpowiedź JSON jest innym formatem wyjścia: zabezpieczenie HTML należy wykonać tam, gdzie klient później wstawia wartość do HTML.

4. SQL: symbole zastępcze i dozwolone nazwy

Oczyszczanie tekstu nie zabezpiecza sklejania ciągów w SQL. Poniższy przykład zakłada istnienie własnej tabeli dziennika. Dopuszcza tylko trzy kolumny sortowania i ogranicza liczbę wyników. Zmienne pochodzą ze sprawdzonych wartości, a nie bezpośrednio z parametrów żądania.

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 zabezpiecza identyfikator tabeli lub kolumny i wymaga WordPressa co najmniej 6.2. Lista dozwolonych wartości rozstrzyga, który identyfikator jest dopuszczalny; sam symbol zastępczy tego nie ustala. Wartości używają symboli takich jak %s i %d albo listy formatów w $wpdb->insert(). Dodana kolumna bazy nie trafia automatycznie na listę dozwolonego sortowania.

5. Uprawnienia REST i kontrola oczyszczonej wartości

Trasa REST to adres, pod którym program żąda danych lub zmian. Takie trasy potrzebują jawnego permission_callback. Brak callbacku może pozostawić trasę publiczną mimo ostrzeżenia WordPressa. Dla celowo publicznych danych __return_true uwidacznia tę decyzję. Poniższa trasa zapisu dopuszcza tylko konta z manage_options i wyłącznie metodę 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 ) );
        },
    ) );
} );

Jawna walidacja schematu kontroluje typ i długość wejścia. Własna sanityzacja sprawdza także gotową etykietę, zapobiegając pustym wartościom po usunięciu znaczników. Przy uwierzytelnianiu ciasteczkiem rdzeń sprawdza Nonce dla akcji wp_rest z X-WP-Nonce albo _wpnonce. Bez tego tokenu takie żądania REST są traktowane jako nieuwierzytelnione. Inne metody, na przykład hasła aplikacji, mają własny przebieg uwierzytelniania; kontrola uprawnień nadal obowiązuje.

Co potwierdzają wykonane próby

Funkcje pomocnicze przeszły 41 oddzielnych przypadków z WordPressem 7.1.3 i PHP 8.4.27: tablice, brakujące pola, ujemne i ułamkowe liczby wierszy, puste oczyszczone etykiety, protokoły URL oraz dozwolone HTML. Kontrola składni PHP funkcji pomocniczych, handlera formularza i przykładu REST również zakończyła się powodzeniem. Handler zapisu i trasa REST nie zostały wykonane na ustawieniach działającej witryny. Wyniki nie oznaczają pełnego audytu wtyczki ani czystego raportu Plugin Check.

Pełna integracja wymaga także prób z kontem uprawnionym, kontem o mniejszych prawach i bez logowania, z brakującymi lub nieaktualnymi tokenami, z błędami bazy oraz wyjściem HTML w przeglądarce. Plugin Check dostarcza przydatnych wskazówek, ale pusty raport nie dowodzi właściwej ochrony każdej operacji.

Pytania i odpowiedzi

Czy parametr sortowania tylko do odczytu potrzebuje Nonce?

Sama zmiana kolejności wyświetlania nie modyfikuje zapisanych danych. Nadal potrzebna jest kontrola typu i lista dozwolonych wartości, a prywatne wyniki wymagają autoryzacji. Nonce nie zastępuje żadnej z tych czynności.

Czy Nonce zatrzymuje powtórne wysłanie?

Nie. Tokeny WordPressa można wykorzystywać ponownie w okresie ważności. Płatności i inne operacje do wykonania tylko raz potrzebują osobnego mechanizmu ochrony przed duplikatami.

Czy jednorazowe oczyszczanie wystarcza?

Nie. Reguły wejścia, kontrola uprawnień, parametryzowane SQL i ochrona wyjścia mają różne zadania. Oczyszczona etykieta może nadal zawierać cudzysłów wymagający zabezpieczenia w atrybucie HTML.

Lukas Wójcik

Lukas Wójcik

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

Masz pytanie lub pomysł na projekt internetowy? Napisz krótko, o co chodzi. Formularz korzysta z reCAPTCHA do ochrony przed spamem.

Napisanie komentarza

Doświadczenia z innymi wtyczkami lub środowiskami hostingowymi oraz pytania o konfigurację są tu mile widziane.

Adres e-mail nie jest publikowany. Pola obowiązkowe oznaczono gwiazdką.