Otwarte na nowe projekty +48 887 255 528 · kontakt[at]kosari.pl

Audyt uprawnień autorskiej wtyczki WordPress: 7 typowych dziur i jak je łatać

Audyt uprawnień wtyczki WordPress krok po kroku: 7 typowych dziur w autorskich wtyczkach, wzorce poprawek i checklista do sprawdzenia u siebie.

11 min czytania Bezpieczeństwo WordPressa
Audyt uprawnień autorskiej wtyczki WordPress: 7 typowych dziur i jak je łatać

Zwykły przycisk „usuń slot” w panelu instruktora. Wywołany przy lekcji bez przypisanego slotu nie usuwa jednej pozycji, tylko dopasowuje wszystkie lekcje bez slotu i anuluje je jednym kliknięciem – razem z mailami i zmianami w kalendarzu. Takie rzeczy wychodzą dopiero wtedy, gdy zrobisz audyt uprawnień wtyczki WordPress: przegląd nie pod kątem tego, czy funkcje działają, tylko kto i na jakich danych może je wywołać. W jednym takim przeglądzie systemu rezerwacji znaleźliśmy siedem dziur tej wagi i załataliśmy je, zanim którakolwiek komukolwiek zaszkodziła.

Kontekst: wtyczka, która rosła funkcja po funkcji

Przykład z audytu: studio tańca, którego cały biznes stoi na autorskiej wtyczce: formularze zapisu, panel klienta, panel każdej z kilku instruktorek, rezerwacje lekcji, płatności, maile automatyczne i synchronizacja z Kalendarzem Google. Kilkuset klientów, codzienne zmiany terminów, kilkanaście zadań cyklicznych.

Przez lata wtyczka rosła tak, jak rosną systemy szyte na miarę: nowa potrzeba, nowa funkcja, test, wdrożenie. Każda zmiana była sprawdzana pod kątem tego, czy działa. Żadna nie była sprawdzana pod kątem tego, kto jeszcze może ją wywołać i co się stanie, gdy dostanie nietypowe dane. To nie jest zarzut wobec nikogo – tak wygląda większość wtyczek pisanych pod konkretny biznes. Dlatego w pewnym momencie trzeba usiąść i przejrzeć całość pod tym jednym kątem.

Jak przeprowadziliśmy audyt

Przejrzeliśmy wtyczkę moduł po module, a potem przekrojowo: uprawnienia, akcje masowe, dane z przeglądarki, zapisy częściowe, kaskady usuwania, wysyłka maili. Łącznie 62 przeglądy. Powstało z nich 492 surowe zgłoszenia, w większości drobiazgi, powtórzenia albo fałszywe alarmy. Każde zgłoszenie krytyczne weryfikowaliśmy na kodzie i na danych, zanim uznaliśmy je za realne. Zostało siedem.

Fałszywy alarm, który nim nie był: jedną z akcji początkowo odrzuciliśmy jako niegroźną, bo strona, z której jest wywoływana, wymaga uprawnień administratora. Dopiero sprawdzenie wszystkich miejsc, które wołają tę samą funkcję, pokazało, że korzysta z niej także panel instruktorki. Zanim uznasz akcję za niedostępną, przeszukaj cały kod pod kątem jej wywołań, a nie tylko menu.

Siedem dziur, które przez lata nikogo nie ugryzły

1. Przejęcie konta przez wpisanie cudzego maila

Publiczny formularz zapisu wysyłał link do automatycznego logowania do panelu klienta. Problem: robił to także wtedy, gdy ktoś podał adres e-mail istniejącego klienta. Wystarczyło znać czyjś mail, żeby dostać link do jego panelu – z terminami, płatnościami i danymi. Poprawka: link auto-logowania dostaje tylko osoba, której konto powstało w tym właśnie zgłoszeniu. Istniejący klient trafia na zwykłe logowanie kodem.

2. Instruktorka przestawiała lekcje innych instruktorek

W kalendarzu instruktorka widziała tylko swoje lekcje. Ale funkcja zamiany terminów po stronie serwera nie sprawdzała, czyja jest lekcja. Filtr istniał wyłącznie w widoku. Każdy, kto wysłał żądanie z innym identyfikatorem, mógł przestawić dowolną lekcję w studiu.

3. Akcje na zapisach działały na dowolnym ID

Usuń, archiwizuj, oznacz jako spam – te akcje przyjmowały identyfikator z przeglądarki i wykonywały się na nim bez sprawdzenia, czy zapis należy do instruktorki, która klika. Ten sam błąd co wyżej, w innym module.

4. Złośliwy kod z zewnątrz w panelu administratora

Moduł monitorujący zapisywał w logu informacje o żądaniach wysyłanych przez niezalogowanych gości i wyświetlał je w panelu admina bez odpowiedniego zabezpieczenia znaków. Dowolna osoba z internetu mogła więc umieścić w logu treść, która wykonywała się w przeglądarce administratora (klasyczny stored XSS). Poprawka: poprawne escapowanie w widoku i logowanie od gości wyłącznie nazwy akcji.

5. Przycisk, który mógł anulować wszystkie lekcje bez slotu

Nowa wersja kalendarza trzyma część lekcji bez przypisanego slotu, z wartością slot_id = 0. Akcja „usuń slot” wywołana przy takiej lekcji budowała zapytanie „anuluj wszystkie lekcje z tego slotu” z zerem w warunku. Zero pasowało do każdej lekcji bez slotu, a takich bywa kilkadziesiąt.

Akcja masowa z identyfikatorem zero przed poprawką i po poprawcePrzed poprawką kliknięcie usuń slot przy lekcji bez slotu wysyła slot_id równe 0, zapytanie dopasowuje wszystkie lekcje bez slotu i anulowałoby je naraz. Po poprawce wartość mniejsza lub równa zero jest odrzucana i nic się nie dzieje.PRZEDKlik „usuń slot” przy lekcjibez slotuSerwer dostaje: slot_id = 0Warunek pasuje do każdejlekcji bez slotudziesiątki lekcji narazdo anulowania jednym kliknięciemPOTen sam klik, to samo żądanieSerwer sprawdza: slot_id <= 0Odmowa przed zapytaniemdo bazy0 lekcji anulowanychkomunikat zamiast katastrofy
Zero to nie „brak wartości”. W warunku zapytania to pełnoprawna wartość, która może pasować do setek rekordów.

6. Zapis karty klienta nadpisywał pola, których w formularzu nie było

Formularz edycji klienta wysyłał tylko część pól. Funkcja zapisu traktowała brakujące pola jak puste i nadpisywała nimi rekord. Dodatkowo kopiowała sumę wpłat do innego rekordu i przy każdym zapisie wysyłała klientowi maila o przypisaniu instruktorki, nawet gdy nic się nie zmieniło. Poprawka: zapisujemy tylko pola faktycznie wysłane przez formularz, a automatyzacje dostają informację wyłącznie o realnych zmianach.

7. Usunięcie formularza kasowało maile całego produktu

Trwałe usunięcie formularza z kosza kasowało powiązane warianty maili po kluczu produktu, a nie formularza. Jeśli produkt miał kilka formularzy, znikały treści używane przez pozostałe. Poprawka: kasujemy tylko to, co nie jest używane przez żaden inny formularz.

Jeśli Twoja firma też stoi na wtyczce lub systemie pisanym latami „na szybko”, taki przegląd jest częścią naszej opieki nad stronami WordPress.

Odkrycie: filtr w widoku to nie jest uprawnienie

Cztery z siedmiu dziur miały ten sam rdzeń: interfejs pokazywał użytkownikowi tylko jego dane, a serwer wykonywał akcję na tym, co dostał. Przeglądarka jest po stronie użytkownika. Identyfikator w żądaniu można zmienić w kilka sekund, bez żadnych narzędzi hakerskich.

Gdzie musi być sprawdzenie uprawnieńWidok w przeglądarce pokazuje tylko własne lekcje, ale żądanie z dowolnym identyfikatorem trafia do serwera. Bezpieczeństwo zapewnia dopiero sprawdzenie właściciela rekordu w funkcji obsługującej żądanie, przed zapisem do bazy.Przeglądarkawidok filtruje lekcjeID da się podmienićHandler na serwerzeczyja to lekcja?ID większe od zera?Baza danychzapis tylko poobu sprawdzeniachto nie jest zabezpieczenietu jest zabezpieczenieUprawnienie sprawdzasz tam, gdzie wykonuje się akcja
Ukrycie przycisku albo rekordu w widoku poprawia wygodę. Bezpieczeństwo zapewnia dopiero sprawdzenie w funkcji, która wykonuje akcję.

Rozwiązanie: trzy wzorce, które warto mieć w każdej wtyczce

Sprawdzaj właściciela rekordu w handlerze

Każda akcja wywoływana przez admin-ajax albo REST API, poza nonce i uprawnieniem ogólnym, sprawdza, czy konkretny rekord należy do osoby, która wysyła żądanie:

$lekcja = pobierz_lekcje( absint( $_POST['lekcja_id'] ?? 0 ) );
if ( ! $lekcja ) {
    wp_send_json_error( 'Nie ma takiej lekcji', 404 );
}
$admin = current_user_can( 'manage_options' );
if ( ! $admin && (int) $lekcja->instructor_id !== id_zalogowanej_instruktorki() ) {
    wp_send_json_error( 'Brak uprawnień do tej lekcji', 403 );
}

Akcja masowa odrzuca zero i puste zbiory

Każde zapytanie, które może dotknąć wielu rekordów naraz, zaczyna się od sprawdzenia, czy warunek ma sensowną wartość. Brak identyfikatora to powód do odmowy, a nie do wykonania zapytania z zerem:

$slot_id = absint( $_POST['slot_id'] ?? 0 );
if ( $slot_id <= 0 ) {
    wp_send_json_error( 'Ta lekcja nie ma slotu - nic nie anuluję' );
}

Formularz częściowy zapisuje tylko to, co wysłał

Funkcja zapisu bierze wyłącznie pola obecne w żądaniu, porównuje je z bazą i dalej przekazuje tylko realne zmiany. Dzięki temu automatyzacje „przy zmianie instruktorki wyślij maila” uruchamiają się wtedy, gdy instruktorka faktycznie się zmieniła:

$zmiany = array();
foreach ( array( 'telefon', 'uwagi', 'instructor_id' ) as $pole ) {
    if ( ! array_key_exists( $pole, $_POST ) ) {
        continue; // pola nie było w formularzu - nie ruszamy go
    }
    $nowa = sanitize_text_field( wp_unslash( $_POST[ $pole ] ) );
    if ( (string) $klient->$pole !== $nowa ) {
        $zmiany[ $pole ] = $nowa;
    }
}

Jak testowaliśmy poprawki na żywym systemie bez śladów

Studio działa codziennie, więc poprawki trzeba było sprawdzić na prawdziwych danych, ale bez żadnego skutku dla klientów. Zbudowaliśmy do tego osobne narzędzie testowe, które:

  • uruchamia cały test w transakcji bazy danych i na końcu ją wycofuje,
  • blokuje wysyłkę maili,
  • blokuje ruch HTTP do Google – biblioteka Kalendarza Google wysyła żądania własnym klientem HTTP, więc standardowy filtr WordPressa jej nie zatrzymuje i trzeba ją odciąć u źródła,
  • porównuje odcisk wszystkich tabel wtyczki przed testem i po nim, a każda różnica kończy test błędem.

Zestawy asercji dla kolejnych poprawek przeszły w całości: 7 z 7, 9 z 9, 8 z 8, 5 z 5 i 32 z 32.

Pułapka przy testach uprawnień: test renderujący ekran administratora podstawiał użytkownika o ID 1. W tej instalacji takie konto nie istniało, więc ekran wyrenderował się jak dla niezalogowanego gościa, a wszystkie asercje i tak przeszły. Błąd wyszedł dopiero przy wywołaniu akcji, która odpowiedziała „brak uprawnień”. Test uprawnień uruchamiaj zawsze jako istniejący użytkownik danej roli i sprawdzaj w nim, jakie uprawnienia faktycznie ma.

Efekt

Klientka nie musiała uczyć się niczego nowego – interfejs został taki sam, przyciski w tych samych miejscach. Zniknęły za to scenariusze, w których jedno kliknięcie, literówka w adresie e-mail albo ciekawski gość kosztowałyby ją dziesiątki telefonów z przeprosinami, ręczne odtwarzanie terminów i utratę zaufania klientów. Pozostałe, mniej groźne zgłoszenia z audytu weryfikujemy i wdrażamy w kolejnych paczkach, każdą z własnym zestawem testów.

Jak sprawdzić uprawnienia we własnej wtyczce

  1. Wypisz wszystkie akcje admin-ajax i trasy REST API wtyczki. Przy każdej sprawdź trzy rzeczy: nonce, uprawnienie ogólne i właściciela konkretnego rekordu.
  2. Znajdź każde zapytanie, które usuwa albo zmienia wiele rekordów naraz. Sprawdź, co się stanie, gdy dostanie 0, pustą wartość albo pustą listę.
  3. Przy każdej funkcji wywoływanej z panelu admina przeszukaj kod pod kątem innych miejsc, które ją wołają – zwłaszcza paneli innych ról.
  4. Sprawdź, czy formularze edycji zapisują tylko wysłane pola, a nie cały rekord.
  5. Poszukaj miejsc, w których dane od niezalogowanych użytkowników trafiają do panelu administratora, i upewnij się, że są escapowane przy wyświetlaniu.
  6. Prześledź kaskady usuwania: co jeszcze znika razem z usuwanym obiektem i czy klucz powiązania jest właściwy.
  7. Testuj na kopii albo w transakcji, z zablokowaną wysyłką maili i połączeniami do zewnętrznych usług.

Wniosek

Wtyczka, która działa poprawnie przy normalnym użyciu, może być jednocześnie niebezpieczna przy nietypowym. Testy funkcji sprawdzają pierwsze, audyt uprawnień drugie. Przy systemach, na których stoi cały biznes klienta, regularny przegląd pod kątem „kto może to wywołać i na jakich danych” traktujemy dziś jak kopię zapasową – nie jako luksus, tylko jako zabezpieczenie przed jednym złym kliknięciem. Podobną zasadę stosujemy przy każdej automatyzacji, która działa na wielu rekordach naraz, o czym pisaliśmy przy okazji fałszywych dłużników w WooCommerce.

FAQ

Czym jest audyt uprawnień wtyczki WordPress?

To przegląd kodu pod kątem tego, kto może wywołać każdą akcję i na jakich danych. Sprawdza się nonce, uprawnienia ról, właściciela rekordów, akcje masowe, zapisy częściowe i miejsca, w których dane od użytkowników trafiają do panelu admina. Nie sprawdza, czy funkcje działają, tylko czy da się ich użyć w sposób niezamierzony.

Czy nonce w WordPressie wystarczy do zabezpieczenia akcji?

Nie. Nonce chroni przed wywołaniem akcji z obcej strony, ale nie mówi nic o tym, czy zalogowany użytkownik ma prawo do konkretnego rekordu. Do tego potrzebne są sprawdzenie uprawnień i sprawdzenie właściciela danych w funkcji obsługującej żądanie.

Dlaczego ukrycie przycisku w panelu nie wystarcza?

Bo przycisk jest tylko wygodnym sposobem wysłania żądania do serwera. To samo żądanie można wysłać ręcznie, z dowolnym identyfikatorem. Jeśli serwer nie sprawdza uprawnień, ukryty przycisk niczego nie chroni.

Jak często robić audyt własnej wtyczki?

Przy każdej większej zmianie dotyczącej ról, paneli albo akcji masowych oraz okresowo przy systemach, które rosną funkcja po funkcji. Im dłużej wtyczka rośnie bez przeglądu, tym więcej dziur ma szansę się nawarstwić.

Czy da się bezpiecznie testować poprawki na produkcji?

Da się, ale tylko z kompletną izolacją: transakcja wycofywana na końcu, zablokowane maile, zablokowane połączenia do zewnętrznych usług i porównanie stanu bazy przed i po. Sam rollback bazy nie cofnie maila ani wydarzenia utworzonego w zewnętrznym kalendarzu.

Co zrobić, jeśli dane już zostały uszkodzone przez błąd?

Najpierw zatrzymać źródło, czyli wdrożyć poprawkę. Potem ustalić zakres na podstawie danych i kopii zapasowych i naprawić rekordy osobnym, sprawdzonym skryptem. Kolejność jest ważna: naprawa danych przed poprawką kodu zostanie po prostu nadpisana.

Twoja firma stoi na wtyczce pisanej latami? Napisz do nas

Masz autorski system zapisów, rezerwacji albo panel klienta na WordPressie i nikt nigdy nie przejrzał go pod kątem uprawnień? Przeprowadzamy taki audyt, weryfikujemy zgłoszenia na kodzie i danych, a poprawki wdrażamy z testami, które nie zostawiają śladów u Twoich klientów. Zobacz też, jak podchodzimy do automatyzacji procesów w firmie.

Napisz do nas – sprawdzimy, kto może kliknąć co w Twojej wtyczce

Grzegorz Rówiński · Kosari

Buduję strony, sklepy i platformy kursowe, automatyzuję procesy i wdrażam AI w firmach. Ten wpis powstał z realnego wdrożenia, nie z teorii.

Napisz do mnie →

Przeczytaj też

Masz podobny problem i nie chcesz go debugować sam?

Napisz krótko co się dzieje. Odpowiadamy konkretnie, bez zobowiązań - powiemy z czego to najpewniej wynika i ile kosztuje naprawa.

Napisz do nas

Wolisz porozmawiać? Zadzwoń: +48 887 255 528

Zadzwoń +48 887 255 528