Strona główna → Wtyczki → Antyspam w formularzu Bricksa

Zapiski z wdrożenia · Bricks Builder

Formularz Bricksa bez spamu i bez CAPTCHA.

Strona publicznej przychodni i zwykły formularz kontaktowy zbudowany w Bricksie, który trzeba było zabezpieczyć przed botami. Warunki były dwa: żadnych usług zewnętrznych, bo dane pacjentów nie mają wychodzić poza serwer, i żadnych zagadek do rozwiązania, bo strona ma spełniać WCAG 2.1 AA. Skończyło się na małym mu-pluginie: honeypot, pułapka czasowa i ciche udawanie sukcesu. Tu jest to, czego dowiedzieliśmy się po drodze z kodu Bricksa.

Bricks 2.x mu-plugin bez usług zewnętrznych bez ciasteczek WCAG 2.1 AA
Założenia

Cztery warunki, zanim powstała pierwsza linia.

✓

Nic nie wychodzi poza serwer. Żadnego skryptu z cudzej domeny, żadnych kluczy API, żadnych ciasteczek. Ocena, czy zgłoszenie jest od człowieka, zapada w całości w WordPressie.

✓

Odwiedzający niczego nie rozwiązuje. Nie klika w obrazki i nie przepisuje znaków. Dla osoby z czytnikiem ekranu formularz wygląda dokładnie tak jak przedtem.

✓

Działa na każdym formularzu Bricksa. Także na tych, które pojawią się później — w popupie, w panelu wysuwanym z boku, na nowej podstronie — bez pamiętania o dodaniu pola.

✓

Bot nie dowiaduje się, że wpadł. Dostaje ten sam komunikat „wysłano”, co człowiek. Wiadomość nie idzie nigdzie, a on nie ma powodu, żeby zmieniać taktykę.

Najpierw to, co Bricks ma sam

Od wersji 1.12.2 każde pole formularza można oznaczyć jako honeypot, a w sekcji ochrony przed spamem są Turnstile, reCAPTCHA i hCaptcha. Wbudowany honeypot warto włączyć i w wielu przypadkach wystarczy. Tutaj potrzebna była druga warstwa — pułapka czasowa — oraz to, żeby ochrona obejmowała każdy formularz automatycznie, bez edycji każdego z osobna w builderze.

Jak to działa w środku

Pierwszy plan nie przetrwał czytania kodu.

Projekt był gotowy przed pierwszą linią PHP. Dwa jego założenia zmieniły się po przeczytaniu, jak Bricks przyjmuje zgłoszenie formularza — i dobrze, że stało się to przed wdrożeniem, a nie po.

Własne pole nie może udawać pola Bricksa

Naturalny pomysł to nazwać ukryte pola tak jak pola formularza, z przedrostkiem form-field-, żeby na pewno trafiły do wysyłki. Bricks ma jednak zabezpieczenie: każde pole z tym przedrostkiem, którego nie ma w zapisanych ustawieniach formularza, powoduje odrzucenie całego zgłoszenia jako podejrzanego. Filtr PHP w czasie działania tego nie obejdzie, bo serwer sprawdza zapis z bazy. Nasze pola mają więc własne nazwy. Do wysyłki i tak trafiają, bo front Bricksa zbiera cały formularz, a zabezpieczenie po prostu ich nie dotyczy.

Czas nie może pochodzić z serwera

Pułapka czasowa sprawdza, ile minęło od wyświetlenia strony do wysłania. Znacznik czasu wypisany przez PHP zostałby zapisany w cache'u strony razem z resztą HTML-a, a wtedy pułapka po chwili przepuszczałaby każdego — boty też. Dlatego liczy przeglądarka: skrypt zapamiętuje moment startu i przy wysyłce wpisuje liczbę sekund. Bot, który wysyła żądanie z pominięciem JavaScriptu, nie ma tej wartości wcale — i to też jest sygnał.

Filtr walidacji stoi w dobrym miejscu

bricks/form/validate to filtr, nie akcja: dostaje tablicę błędów i obiekt formularza, zwraca tablicę błędów. Wywoływany jest po sprawdzeniu nonce, wbudowanego honeypota i listy pól, a przed walidacją pól wymaganych i przed akcjami — wysłaniem maila czy przekierowaniem. To dokładnie ten moment, w którym trzeba zdecydować, zanim cokolwiek wyjdzie z serwera.

„Wysłano” musi mieć dokładny kształt

Front Bricksa pokazuje komunikat sukcesu tylko wtedy, gdy odpowiedź ma success: true i data.type równe success. Po wykryciu spamu wysyłamy właśnie taką odpowiedź z komunikatem wziętym z ustawień formularza i kończymy żądanie — akcje formularza w ogóle się nie uruchamiają. Wbudowany honeypot domyślnie odpowiada błędem; da się to zmienić filtrem bricks/element/form/honeypot/result.

add_filter( 'bricks/form/validate', function ( $errors, $form ) {
    $hp = sanitize_text_field( wp_unslash( $_POST['zz_hp'] ?? '' ) );
    $t  = sanitize_text_field( wp_unslash( $_POST['zz_t'] ?? '' ) );

    $spam = '' !== trim( $hp )      // honeypot wypełniony
        || ! is_numeric( $t )       // brak czasu = brak JS
        || (int) $t < 3;           // za szybko jak na człowieka

    if ( ! $spam ) {
        return $errors;             // człowiek: Bricks robi resztę
    }

    $msg = $form->get_settings()['successMessage'] ?? 'Dziękujemy.';
    wp_send_json_success( [ 'type' => 'success', 'message' => $msg ] );
}, 10, 2 );
Co wychodzi dopiero w przeglądarce

Trzy szczegóły, od których zależy, czy nie zgubimy człowieka.

Antyspam, który zatrzymuje boty, a przy okazji raz na jakiś czas prawdziwą wiadomość, jest gorszy niż spam. Dlatego te trzy rzeczy były sprawdzane w prawdziwej przeglądarce, na kopii roboczej strony.

Formularz w popupie. Skrypt, który raz po załadowaniu strony dokłada pola do formularzy, nie widzi tych, które pojawią się później. Taki formularz wysłałby się bez pola czasu, a serwer uznałby człowieka za bota. Obserwator zmian w dokumencie dokłada pola także do formularzy dodanych po starcie strony.

Ukrycie honeypota. Pole ma zniknąć dla oka, dla klawiatury i dla czytnika ekranu, ale zostać w formularzu. Jest odsunięte poza ekran, wyjęte z kolejności tabulatora, ukryte przed technologiami asystującymi i ma wyłączone autouzupełnianie — żeby przeglądarka nie wpisała do niego czegoś w imieniu człowieka.

Cztery scenariusze na jednym formularzu. Wysyłka jak człowiek — mail doszedł. Wypełniony honeypot, wysyłka po sekundzie i żądanie bez JavaScriptu — za każdym razem komunikat „wysłano” i żadnego maila. Próg czasu to trzy sekundy i można go zmienić jednym filtrem, bez ruszania kodu.

Zacznijmy

Formularz na Bricksie zbiera spam?

Napisz, na ilu formularzach i czy strona ma ograniczenia co do usług zewnętrznych. Powiemy, czy wystarczy wbudowany honeypot, czy warto dołożyć drugą warstwę.

Opisz swój formularz →

Albo mailem: info@asymetria.com.pl