Jak nie zabezpieczać formularzy?

Pracuję nad utrzymaniem pewnej strony, stworzonej jednak przez kogoś innego. Zauważyłem, że formularze są zabezpieczone obrazkiem CAPTCHA, który jest generowany losowo przy każdych odwiedzinach strony. Niestety, kod z CAPTCHY przechowywany jest w ukrytym polu input. Przy próbie wysłania formularza, wartość z ukrytego inputa porównywana jest z wartością wprowadzoną przez użytkownika. Jeśli są różne, formularz słusznie nie zostanie wysłany. Na nieszczęście, kod CAPTCHY widoczny jest w źródle strony, więc nie trzeba odczytywać go z obrazka.

Można zatem użyć biblioteki cURL do odczytania źródł strony i wysłania spreparowanych danych. Przedstawię skrótowo sposób na „zaspamowanie” takiego formularza z wykorzystaniem wspomnianej biblioteki cURL.

1. Rozpoczęcie sesji cURL-a

$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, "http://adres.pl/formularz/");
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

W moim wypadku nie były wymagane żadne dodatkowe opcje, może okazać się jednak, że formularz jest lepiej zabezpieczony i konieczne będzie spreparowanie wszystkich nagłówków, tak by żądanie wyglądało jakby było wysłane przez przeglądarkę. Po więcej informacji na ten temat odsyłam do dokumentacji.

2. Wykonanie żądania

$result = curl_exec($curl);
$html = str_get_html($result);

W zmiennej $html mamy w tym momencie cały kod źródłowy strony. „Gdzieś tam” znajduje się formularz z ukrytym inputem, należy pobrać jego wartość.

3. Parsowanie pobranego dokumentu

Do „wyciągnięcia” wartości tego pola wykorzystam bibliotekę PHP Simple HTML DOM Parser. Pozwala ona na przeszukiwanie struktury dokumentu HTML w łatwy sposób, podobny do znanego z jQuery. W moim przypadku poszukiwany input ma nadaną nazwę sprawdź. Odnalezienie go w drzewie DOM z wykorzystaniem powyższej biblioteki wygląda następująco:

$ret = $html->find('input[name=sprawdz]');

Otrzymana zmienna jest tablicą obiektów kolejnych wystąpień poszukiwanego pola input. Ponieważ wiemy, że na stronie występuje on tylko raz, żądana wartość będzie dostępna pod $ret[0]->value.

4. Automatyczne wysłanie formularza

Możemy zatem przygotować dane do automatycznego wysłania. Dla pewności, sprawdźmy jak wyglądają nagłówki przy wysyłaniu formularza za pomocą przeglądarki. W Chrome Developer Tools bądź Firebugu dla Firefoxa w zakładce sieć można podejrzeć nazwy i wartości przesyłanych zmiennych POST.

Do wysłania formularza zostanie użyta biblioteka cURL z ustawionymi odpowiednimi parametrami:

curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, array(
    "imie"	=> "Romek",
    "email"	=> "emil@z.wp.pl",
    "tresc"	=> "Lorem ipsum, ipsum lorem",
    "auth"	=> $ret[0]->value,
    "sprawdz"	=> $ret[0]->value,
    "submit_f1"	=> "Wyślij"
));

$result = curl_exec($curl);
curl_close ($curl);

W pierwszej linii ustawiamy flagę umożliwiającą wysłanie zmiennych POST. Druga linia w tablicy przekazuje nazwy zmiennych i ich wartości. Przedostatnia linia wywołuje żądanie, natomiast ostatnie polecenie, kończy sesję cURL.

W zmiennej $result powinno znaleźć się źródło strony wyświetlanej po poprawnym wysłaniu formularza. Zwykle znajduje się na niej jakaś informacja zwrotna potwierdzająca wysłanie. Możemy wyświetlić zmienną i upewnić się czy źródło zawiera owe potwierdzenie.

Jak zatem skuteczniej zabezpieczyć formularz przed spamem?

Ideą zabezpieczenia CAPTCHA jest ukrycie jawnego kodu przed użytkownikiem, a ukazanie jedynie zadania – obrazka do odczytania. Do tego celu można wykorzystać sesję – w niej przechowywać kod i na jego podstawie wygenerować obrazek. Jeśli dane wpsiane przez użytkownika zgadzają się z danymi zawartymi w sesji, zezwalamy na wysłanie formularza.

Powyższe rozumowanie przedstawia kod:

  • Tworzenie CAPTCHY
session_start();
// generowanie losowego łańcucha,
// wyświetlanego później jako captcha
$str = generate_captcha();
// zapSis wygenerowanej zmiennej o sesji
$_SESSION['captcha'] = $str;
// show_captcha() generuje obrazek z losowym łańcuchem
echo show_captcha($str);
  • Sprawdzanie CAPTCHY
session_start();
if ($_POST['captcha'] === $_SESSION['captcha']) {
    unset($_SESSION['captcha']);
    // wysyłamy formularz
} else {
    // ups, captcha nie zgadza się, spróbuj ponownie
}

Sposób drugi – hashowanie ukrytego pola

Jeśli z jakiegoś powodu użycie sesji do tego celu nie byłoby możliwe, rowiązaniem mogłoby być hashowanie wygenerowanego kodu. Wprowadzone znaki przez użytkownika także poddawane były by hashowaniu, a następnie porównywane ze skrótem z ukrytego pola. Do tworzenia skrótu można użyć funkcji hash() i algorytmu SHA256. Dodatkowo, warto wygenerowany łańcuch „posolić” jakąś dodatkową wartością. Ostatecznie generowanie CAPTCHY wyglądałoby następująco: SHA256($str . $SECRET); w podobny sposób należy sprawdzić wprowadzone dane przez użytkownika: SHA256($POST['captcha'] . $SECRET).

Czas od wygenerowania do wysłania

W obu przypadkach można ponadto zabezpieczyć automatyczne wysyłanie formularza poprzez odliczanie czasu od utowrzenia kodu (odwiedzenia strony) do próby wysłania. Jeśli czas jest krótszy od np. 5s. możemy z dużą dozą prawdopodobieństwa stwierdzić, że użytkownik nie wypełnił wszystkich pól ręcznie i taką próbę wysłania formularza odrzucamy.

PS. Moja lektura na najbliższe dni:

Dziś otrzymałem przesyłkę z nowo wydaną książką dotyczącą bezpieczeństwa aplikacji internetowych, dzięki której mam nadzieję znacznie pogłębić swoją wiedzę z zakresu szeroko pojętej ochrony serwisów WWW. Opis i spis treści wyglądają bardzo zachęcająco, więc myślę, że się nie zawiodę na tej pozycji.

SKOMENTUJ