Szukaj

Jak zabezpieczyć formularze przed spamem?

Jakiś czas temu w pewnej w zacnej firmie pojawił się uciążliwy, zjadliwy i zgryźliwy problem. Był to spam wysyłany z udostępnionych formularzy. Po przeprowadzeniu jego powierzchownej analizy okazało się, że były to przede wszystkim oferty farmaceutyków pokroju znanej wszystkim niebieskiej pigułki.

W przypadku wystąpienia powyżej opisanej sytuacji lub/i zbliżonej ukierunkujmy się na opracowanie uniwersalnego i szybkiego rozwiązania pozwalającego zabezpieczyć każdy z istniejących oraz przyszłych formularzy przed spamującymi robotami internetowymi (z ang. web crawlers, lub potocznie crawlers). Warto też unikać zmniejszania komfortu korzystania z utrzymywanych przez nas stron. Podsumowując, rozwiązanie najlepsze jest:

  • pozbawione captchy,
  • łatwe w implementacji,
  • nie wymagające utrzymania,
  • ukryte przed użytkownikiem,
  • przenośne (raz stworzone może być zastosowane w każdym formularzu),
  • skuteczne.

Prostym spełnieniem tych wymagań byłoby wprowadzenie ukrytego pola w formularzu, niewidocznego dla użytkownika, a „na pewno” parsowanego przez spamujące roboty. Wystarczyłoby wtedy nazwać to pole w jakiś kuszący dla nich sposób, np. „name”, zaimplementować prostą weryfikację po wysłaniu formularza i wszystko gotowe. Wypełnienie go oznacza, że wysyłający nie jest człowiekiem. Genialne, proste, piorunujące.

Nie do końca. Co w przypadku, gdy bot potrafi stwierdzić, czy pole jest ukryte i uznając je za nikczemną zasadzkę – zignorować? Napisanie tego typu programu nie stanowi obecnie problemu. Jak spytacie? Chociażby wykorzystując do tego Selenium, pozwalający automatyzować działanie przeglądarki. Wykorzystywany jest głównie do realizacji automatycznych testów funkcjonalnych lub innego rodzaju powtarzalnych zadań. Przedstawione powyżej rozwiązanie może zabezpieczyć nas przed crawlerami opartymi o parsowanie stron przykładowo z wykorzystaniem prostych wyrażeń regularnych, ale nie przed bardziej cwanymi bestiami.

Jakie może być inne rozwiązanie? Zastanówmy się co jest najbardziej istotne dla autora tego typu crawlerów. Robot ma przejść jak najszybciej po jak największej ilości stron, co zwiększa potencjalną grupę „odbiorców”. W takim przypadku kluczowym czynnikiem jest czas – im szybciej dana instancja robota wypełni i wyśle dany formularz, tym szybciej będzie mogła przejść do kolejnego i powtórzyć operację. Załóżmy więc, że robot wypełnia i wysyła formularz natychmiastowo lub po prostu szybciej niż człowiek byłby w stanie to zrobić. Posługując się zdrowym rozsądkiem ustanawiamy minimalny czas wypełniania formularza, który pozwala określić, czy zawarta treść jest spamem.

Wybornie można rzec, ale jak to zaimplementować w miarę przyzwoity sposób? Nasuwają się dwa rozwiązania – z wykorzystaniem javascriptu oraz bez. Zacznijmy od tego pierwszego (uwaga, komentarze do kodu pisane zgrabnie łamaną angielszczyzną):

  1. Umieszczamy w dowolnym miejscu naszego formularza lub formularzy (między tagami <form></form>) następujący kod HTML:

    <input name=„form-display-time” type=„hidden” value=„0″>

  2. Następnie wklejamy między tagi <head></head> LUB na końcu naszego dokumentu (strony mają tendencję do ładowania się szybciej w przypadku umieszczania javascript’u tuż przed tagiem </body>) kod:
    <script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
    <script type="text/javascript">
    var incrementInterval;
    // First, let's wait for the document..
    $(document).ready(function () {
        // IncrementDisplayTime() function will be executed every second 
        // until clearInterval(incrementInterval) is called
        incrementInterval = setInterval('incrementDisplayTime()', 1000);
    });
    
    /**
     * Increments every form's hidden input value with name="form-display-time" by 1 
     * (of course only if any was added)
     * 
     * @return void
     */
    function incrementDisplayTime() 
    {
        $('form input[type="hidden"][name="form-display-time"]').each(function() {
            // We use + 1 because increment postfix doesn't work in javascript 
            // for values returned by functions 
            $(this).val(parseInt($(this).val()) + 1);
        });
    }
    </script>

    Powyższy kod wymaga frameworka jQuery. Oczywiście możemy być purystą lub/i minimalistą, dla takich delikwentów bardziej zadowalający (lub i nie) może być poniższy skrypt (umieszczany w ten sam sposób jak poprzedni):

    <script type="text/javascript">
    var incrementInterval;
    if (document.readyState === "complete") { 
        // Anonymous function will be executed every second 
        // until clearInterval(incrementInterval) is called
        incrementInterval = setInterval(function () {
           var elements = document.getElementsByName('form-display-time');
            for (var i = 0; i < elements.length; i++) {
                elements[i].setAttribute(
                            'value', 
                            parseInt(elements[i].getAttribute('value')) + 1
                );
            }
        }, 1000);
    }
    </script>

    Oba skrypty zwiększają co sekundę o 1 wartość każdego ukrytego pola umieszczonego w punkcie pierwszym – pozwalają zabezpieczać mnogą ilość formularzy znajdujących się na stronie.

  3. Ustalamy przeciętny minimalny czas, jaki potrzebny jest człowiekowi do wypełnienia formularzu/y. Przykładowo, formularz kontaktowy składający się z pola na imię/pseudonim, adres e-mail oraz wiadomość nie zostanie najpewniej wypełniony szybciej niż w przeciągu 6. Poniżej tego czasu – wiadomość uznawana jest za spam.
  4. Pozostaje implementacja obsługi formularza po stronie serwera – w pliku, do którego kieruje atrybut action w tagu <form>. Jeżeli wykorzystujemy lub chcemy wykorzystać język PHP na naszej stronie a formularz ma przypisany atrybut method=”POST”, weryfikację spamu można przeprowadzić następująco:
    // First we check if any form has been submitted and if spam 
    // validation field has been passed
    if (isset($_POST['form-display-time']) ) {
    	if ($_POST['form-display-time'] < 6) {
    		// Some anti-spam operations
    	} else {
    		// Operations concerning non-spam messages
    	}
    }

Drugim sposobem jest wykorzystanie sesji użytkownika:

  1. Na początku pliku wyświetlającego np. formularz kontakowy, umieszczamy następujący kod:
    <?php 
    	// Start user session if we haven't done that before
    	start_session();
    	// Save current time in seconds on document request
    	$_SESSION['contactRequestTime'] = time();
    ?>
    <!-- Rest of the code goes here, eg. <form method=”POST” action=”contact-send.php”>
    ... </form> -->
  2. Wykonujemy działania 3 i 4 przedstawione w poprzednim rozwiązaniu, z tą różnicą, że kod odpowiedzialny za weryfikację formularza, który umieszczamy w pliku wskazanym przez atrybut action formularza, może wyglądać następująco:
    <?php 
    	// Bind user session located on server to cookie 
    	// containing identificator located in his browser
    	start_session();
    	// Check if any form data has been sent
    	if ($_POST != null && isset($_SESSION['contactRequestTime'])) {
    		// Check if form hasn't been sent before minimal time passed
    		if (floor(time() - $_SESSION['contactRequestTime']) < 6) {
    			// Some anti-spam operations
    		} else {
    			// Operations concerning non-spam messages
    		}
    	}
    ?>

W ten sposób możemy np. dodać do tytułu wiadomości prefix „spam” (użyteczne jeżeli chcemy zweryfikować skuteczność), zaniechać zapisu wiadomości w bazie lub jej wysyłki na określony adres.

Tak przygotowane mechanizmy, przy odpowiednim dopasowaniu progów czasowych dla poszczególnych formularzy, powinny spełniać wymagania ustalone wcześniej. Pierwszy sposób zabezpieczenia wyklucza wszelkie roboty nie wykonujące javascriptu a jednocześnie zabezpiecza nas przed nieco cwańszymi bestiami. Może jednak generować fałszywe notyfikacje o spamie od użytkowników z włączoną blokadą javascriptu. Drugi ze sposobów nie wymaga użycia javascriptu i może być prostszy w implementacji. Utrzymanie sesji użytkownika wymaga jednak umieszczenia ciasteczka z identyfikatorem sesji (pozwala ustalić, która sesja na serwerze, jeżeli utworzona, należy do jakiego użytkownika) w jego przeglądarce. W związku z nowymi zmianami w przepisach odnośnie przetrzymywania informacji na urządzeniach użytkownika może tego nie chcieć. Poza tym użytkownik może mieć wyłączoną obsługę ciasteczek lub ograniczyć ją do wybranych stron. Oczywiście wspomnianie wady dotyczą bardzo niewielkiej, najpewniej specyficznej grupy użytkowników – szansa na to jest bliska temu, że strzelają z oczu laserami.

Jak widać artykuł jest w miarę techniczny, wiele trudnych słów i ogólnie to jakoś tak bez marketingu w nim. Aby więc nakarmić umęczonych marketingowców, przygotowałem to co lubią najbardziej – ładne i kolorowe słupki, troche cyferek oraz przypadek studium! Dotyczą one wyłapanego przez pierwsze zabezpieczenie (oparte o javascript) spamu wysyłanego przez formularze zamieszczone na stronie www.webdoctor.pl.

Wykres przedstawiający przechwycony spam w stosunku do zwykłych wiadomości

Łącznie zarejestrowano od czasu wprowadzenia skryptu 333 wiadomości, z czego 44 zostało uznanych za spam, stanowiąc 13% całości. Każda taka przesyłka otrzymywała prefix [SPAM] w tytule wiadomości, celem ręcznej weryfikacji. Dalsza analiza ujawniła, że tylko jedna została uznana za spam niesłusznie. Daje to łączną poprawność filtrowania wynoszącą ok. 97%. Ponadto znaki na ziemi i niebie wskazywały, że ten jeden użytkownik miał w przeglądarce wyłączoną obsługę javascriptu.

Piotr Jankowski

Z zawodu i zamiłowania deweloper aplikacji webowych, desktopowych oraz mobilnych. Gorliwy wyznawca minimalistycznej estetyki oraz wysokiego usability. W wolnym czasie zwolennik aktywności fizycznej i różnorakiej rozrywki.

zobacz posty autora

Spodobał ci się ten post? 8 0

9 komentarzy

  • Jarek

    Sprytne i proste rozwiązanie. Dzięki, na pewno się przyda :)

    08.01.2013, g.09:51

  • marcin

    Dzięki za pomysł, wykorzystam na 100 % to rozwiązanie :)

    08.01.2013, g.10:29

  • Ryszard Ochódzki

    Wykresów za mało ;) A umieściłeś akurat ten najmniej istotny. Nie chodzi o to ile złapałeś spamów, pośród wypełnionych formularzy przez istoty żywe, tylko ile z ich było niesłusznie potraktowanych jako spam, a ile nie zostało wyłapanych w ogóle w stosunku do całości filtrowania.
    Rozumiem, że ten „wykres” to żart, ale można zażartować jednocześnie karmiąc tych, do których żart kierujesz :]

    08.01.2013, g.11:37

    • Piotr Jankowski

      Kierując się zasadą umiarkowania w konsumowaniu oraz minimalizacją redundancji pochłanianej treści łakomstwu mówię.. że jest bardzo kuszące, no ale nie można aż tak rozpieszczać.

      08.01.2013, g.12:06

  • Nauczyciel angielskiego

    Teraz już wiem dlaczego czasami, przy próbach wpisania krótkiego komentarza, otrzymywałem komentarz zwrotny „Slow down cowboy” :-) Cóż, poczekanie kilku sekund nie jest wysoką ceną za utrzymywanie strony w czystości.
    Przy kolejnej modyfikacji strony wykorzystam Wasze pomysły, dzięki.

    09.01.2013, g.12:52

  • Igor

    Przetestuje to dziś :)
    Średnio 3/4 razy dziennie dostaje taki spam przez formularz w swoim serwisie, mimo że ma on token.

    24.03.2013, g.17:35

  • Toms

    Autorze posta, rzeczywiście te opisy po angielsku mogłeś sobie darować i wstawić polski opis ;-)
    A swoją drogą bardzo fajny „mini-tutek” ;-) i chwała Ci za to.
    Fajnie jednak byłoby, żeby formularz wypełniony w czasie krótszym niż ustawiony, wyświetlał komentarz zwrotny o konkretnej treści.

    16.05.2013, g.20:59

  • Tomek

    Używamy sblam! i spisuje się rewelacyjnie

    06.06.2013, g.11:08

  • Przemek

    Genialne w swojej prostocie :) Wielkie dzięki za przyjemny sposób pozbycia się spamu ;)

    27.12.2013, g.23:15

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>