Logo David Burdelak
Cybersecurity

Jak chronić formularze i API przed spamerami i botami?

Zabezpieczanie formularzy i punktów końcowych API przed botami i spamerami

Spam z formularza kontaktowego to najbardziej widoczny, ale najmniej groźny efekt działania botów. Przy projektach, którymi się opiekuję, częściej widzę masowe zakładanie kont, testowanie wykradzionych haseł na formularzu logowania, wysyłanie tysięcy SMS-ów przez formularz weryfikacji czy zapytania do API, które w kilka godzin zużywają miesięczny limit płatnej usługi. W tym wpisie pokazuję, jak buduję ochronę warstwami, tak żeby zatrzymać automaty, a prawdziwy użytkownik w ogóle jej nie zauważył.

Warstwy ochrony i kiedy je stosuję

WarstwaCo zatrzymujeWpływ na użytkownika
Honeypot i kontrola czasuproste skrypty wypełniające wszystkie polażaden
Limity zapytańmasowe wysyłanie z jednego adresu lub kontażaden, dopóki ktoś nie przekroczy limitu
Weryfikacja w tle (Turnstile, reCAPTCHA v3)boty udające przeglądarkęzwykle żaden, czasem dodatkowe sprawdzenie
Reguły w CDN lub WAFruch z sieci botów, znanych źródeł ataków i nietypowych krajówżaden przy dobrze ustawionych regułach
Weryfikacja przy podejrzanym ruchuataki przechodzące przez pozostałe warstwydodatkowy krok tylko dla podejrzanych sesji

Na zwykłej stronie firmowej wystarczają mi pierwsze dwie warstwy. Przy sklepach, formularzach rejestracji i publicznych API dokładam kolejne.

Honeypot i kontrola czasu wypełnienia

Honeypot to dodatkowe pole niewidoczne dla ludzi. Proste boty wypełniają wszystkie pola, które znajdą w kodzie, więc wypełnione pole-pułapka jednoznacznie wskazuje automat:

<div class="hp" aria-hidden="true">
  <label for="company_site">Strona firmy</label>
  <input type="text" id="company_site" name="company_site" tabindex="-1" autocomplete="off">
</div>

Pole ukrywam w CSS przez przesunięcie poza ekran, a nie display: none, które część botów rozpoznaje. Atrybuty aria-hidden i tabindex="-1" sprawiają, że nie trafi na nie czytnik ekranu ani użytkownik przechodzący klawiszem Tab, a autocomplete="off" chroni przed automatycznym wypełnieniem przez przeglądarkę.

Drugą prostą kontrolą jest czas. Przy generowaniu formularza zapisuję w podpisanym polu znacznik czasu, a przy odbiorze odrzucam zgłoszenia wysłane szybciej niż po 2–3 sekundach. Człowiek nie wypełni formularza w ułamku sekundy, a bot zwykle tak. Na serwerze odrzucam takie zgłoszenia bez komunikatu o błędzie, żeby nie podpowiadać, co zadziałało.

Limity zapytań do formularzy i API

Każdy endpoint, który wysyła wiadomość, zakłada konto, loguje lub kosztuje pieniądze, ma u mnie limit zapytań. Limit liczę nie tylko po adresie IP, bo boty często zmieniają adresy, ale też po koncie, adresie e-mail lub numerze telefonu, którego dotyczy akcja. Przykład dla Express.js:

import rateLimit from 'express-rate-limit';

const contactLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minut
  limit: 5,                  // 5 zgłoszeń z jednego adresu
  standardHeaders: 'draft-7',
  legacyHeaders: false,
});

app.post('/api/contact', contactLimiter, handleContact);

Przy kilku serwerach aplikacji liczniki trzymam we wspólnej pamięci, np. w Redisie, bo inaczej każdy serwer liczyłby osobno. Po przekroczeniu limitu zwracam kod 429 z nagłówkiem Retry-After. Te same mechanizmy, rozbudowane o opóźnienia i blokady kont, chronią formularz logowania, a ich dobór to podstawa ochrony przed atakami brute force.

Weryfikacja w tle zamiast zagadek obrazkowych

Klasyczna CAPTCHA z przepisywaniem liter czy klikaniem w obrazki obniża konwersję formularza i bywa nie do przejścia dla osób z niepełnosprawnościami. Dlatego korzystam z rozwiązań działających w tle, które oceniają sesję na podstawie zachowania i sygnałów przeglądarki:

  • Cloudflare Turnstile wybieram najczęściej. Jest bezpłatny, nie wymaga przenoszenia strony do Cloudflare i nie pokazuje zagadek obrazkowych.
  • Google reCAPTCHA v3 zwraca ocenę od 0,0 do 1,0, a o progu odrzucenia decyduję sam. Stosuję go, gdy klient korzysta już z innych usług Google, pamiętając o informacji w polityce prywatności i ograniczeniach darmowego planu.

W obu przypadkach najważniejsza jest weryfikacja tokenu na serwerze. Sam widżet na stronie niczego nie chroni, jeśli backend nie sprawdzi odpowiedzi w API dostawcy, a bot może wysłać żądanie bezpośrednio do endpointu, z pominięciem formularza.

Ochrona na poziomie serwera i CDN

  • Reguły w CDN lub WAF. Przy stronie za Cloudflare ustawiam osobne reguły dla ścieżek formularzy i API, np. dodatkowe sprawdzenie dla ruchu z sieci centrów danych. Pilnuję, żeby nie blokowały one robotów wyszukiwarek na stronach, które mają być indeksowane.
  • Sprawdzanie pochodzenia żądań. Formularze zmieniające dane chronię tokenem i weryfikacją nagłówka Origin, tak samo jak przy ochronie przed atakami CSRF.
  • Nagłówki odpowiedzi. Polityka CORS ograniczona do własnych domen i nagłówki bezpieczeństwa HTTP utrudniają wykorzystanie API z obcych stron.

Jak rozpoznaję, że boty atakują formularz

Ataki botów rzadko są widoczne w panelu CMS. Zauważam je zwykle w danych: nagły wzrost liczby rejestracji z jednej domeny e-mail, setki nieudanych logowań w ciągu godziny, wysyłki SMS z prefiksów zagranicznych czy rosnące koszty usług zewnętrznych. Dlatego przy formularzach, które coś kosztują, ustawiam alerty na liczbę zdarzeń w czasie, a w razie podejrzeń sięgam po analizę logów serwera, która pokazuje adresy, częstotliwość i wzorce zapytań. Na tej podstawie dobieram reguły tak, żeby blokowały konkretny atak, a nie przypadkowych użytkowników.