główna strona
wróć
[PHP,HTML]Zabezpieczenie formularza przed robotami (inna CAPCHA)
Ostatnia zmiana: 11.02.2016, Autor artykułu: Waldemar Miotk
artykul-0074.jpg

W celu zabezpieczenia strony przez robotami, automatami wysyłającymi spam przez formularze umieszczone na stronach, stosuje się wiele różnych mechanizmów. Podstawową zasadą jest to, aby były jak najprostsze dla użytkownika-człowieka a jak najtrudniejsze dla robota. Częstym rozwiązaniem są bloki z trudno rozpoznawalnym tekstem, który tylko człowiek jest w stanie odczytać i wpisać do pola tekstowego. Jeżeli wpisany tekst jest tym samym co wyświetlało się w sposób zniekształcony, oznacza to, że z dużym prawdopodobieństwem jest to człowiek. Mechanizm wymaga jednak rozpoznania tekstu przez człowieka, co bardzo często jest utrudnione, gdyż tekst jest za bardzo zniekształcony.

W internecie znajdziemy wiele innych rozwiązań tego problemu, również takie, które odwołują się do naszej inteligencji: "wskaż wszystkie pola gdzie znajduje się jedzenie" a również tekstowe zadania: "ile to jest 1 + pięć".

Ja proponuję inne rozwiązanie, które ma ten plus, że jest bardzo proste. Użytkownik strony używając myszki ma kliknąć tylko w jeden element typu radio wskazując odpowiednie pole. Działa to tak: podajemy komunikat użytkownikowi: "Zaznacz pole pod znakiem 5". Wyświetlamy kilka znaków graficznych nieco zniekształconych, odwróconych, skompresowanych i pod każdym znakiem pole typu radio. Użytkownik ma wskazać w którym miejscu znajduje się znak 5. Proste jasne i czytelne. To rozwiązanie zastosowałem tutaj na tej stronie do zabezpieczenia komentarzy przed robotami. Jak na razie spisuje się znakomicie.

Implementacja zabezpieczania też jest dosyć prosta. Wykonana jest w PHP. Dane pomiędzy skryptami przesyłamy przy pomocy zmiennych sesyjnych.

Ale po kolei.

Przypuśćmy że chcemy zabezpieczyć prosty formularz z jednym polem tekstowym:

Grafika-01

Kod HTML powyższej strony wygląda tak:

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8" />
</head>
<body>
<form method="POST" action="index.php?wyslany=1">
<input type="text" name="wiadomosc" value="" />
<input type="submit" name="wyslij" value="Wyślij wiadomość" />
</form>
</body>
</html>

Po wciśnięciu przycisku "Wyślij wiadomość" trafia do nas zmienna $_POST['wiadomosc'] z treścią wiadomości, którą możemy na przykład zapisać w bazie danych:

<php
mysqli_query($baza, "INSERT INTO tabela (wiadomosc) VALUES ('".$_POST['wiadomosc']."')");
?>

(pomijam tutaj szczegóły podłączenia do bazy danych, gdyż nie o tym jest ten wpis, zresztą możemy z przesłanymi danymi zrobić wiele innych rzeczy)

Aby zastosować proponowane tutaj rozwiązanie problemu zabezpieczeń przed robotami, pierwszą rzeczą którą musimy zrobić to wygenerować znaki które przedstawimy użytkownikom strony. Proponuję takie rozwiązanie:

<?php
   $tabznak = array('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', '&', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '%');
   $znak1 = rand(0,36);
   do {
      $znak2 = rand(0,36);
   } while ($znak2 == $znak1);
   do {
      $znak3 = rand(0,36);
   } while (($znak3 == $znak1) or ($znak3 == $znak2));
   do {
      $znak4 = rand(0,36);
   } while (($znak4 == $znak1) or ($znak4 == $znak2) or ($znak4 == $znak3));
   do {
      $znak5 = rand(0,36);
   } while (($znak5 == $znak1) or ($znak5 == $znak2) or ($znak5 == $znak3) or ($znak5 == $znak4));
   $literki = $tabznak[$znak1];
   $literki.= $tabznak[$znak2];
   $literki.= $tabznak[$znak3];
   $literki.= $tabznak[$znak4];
   $literki.= $tabznak[$znak5];
?>

Zmienna $tabznak to tablica zawierająca wszystkie znaki, które mogą być wybrane dla użytkownika. Dobrze jest pozbyć się znaków, które mogą być problematyczne do rozpoznania, czyli chociażby litery "O" która często myli się z cyfrą "0", ale za to możemy dodać inne znaki, tak jak w zastosowanym tutaj przykładzie znaki "&" oraz "%". Nie będą się one myliły z żadnymi innymi znakami.
Ilość znaków w tablicy: 37. Aby wyświetlić 5 losowych znaków musimy je wylosować. Konieczne jest jednak aby znaki się nie powtarzały, gdyż użytkownik mógłby nie wiedzieć który ze znaków "R" wybrać jeżeli wyświetliłyby się dwa takie same. Dlatego przy pomocy pętli do while testujemy, czy liczba nie została już wylosowana. Więc po kolei.

Do zmiennej $znak1 zapisujemy losową liczbę z zakresu o 0 do 36.
Do zmiennej $znak2 zapisujemy losową liczbę z zakresu od 0 do 36 a następnie sprawdzamy, czy nie ma jej już w zmiennej $znak1. Jeżeli jest losujemy ponownie do skutku.
Do zmiennej $znak3 zapisujemy losową liczbę z zakresu od 0 do 36 a następnie sprawdzamy, czy nie ma jej już w zmiennych $znak1 oraz $znak2. Jeżeli jest w którejś z nich, losujemy ponownie. I tak dalej, aż do prawidłowego wylosowania ostatniego znaku, w naszym przypadku do zmiennej $znak5.

Po całej procedurze losowania zapisujemy wylosowane literki do zmiennej $literki aby tworzyły ciąg znaków, którzy przekażemy do skryptu generującego graficzną ich reprezentację.

Teraz musimy uruchomić obsługę sesji w PHP:

<?php
   session_start();
?>

Do zmiennej sesyjnej o nazwie "literki" dodajemy wygenerowany wcześniej ciąg znaków:

<?php
    $_SESSION['literki'] = $literki;
?>

I teraz możemy napisać skrypt o nazwie grafika.php, który będzie generował graficzną reprezentację literek:

[01] <?php
[02]
[03] session_start();
[04] $lit = $_SESSION['literki'];
[05] $image_x = 360;
[06] $image_y = 40;
[07] $czcionka_size = 16;
[08] $czcionka_x = 20;
[09] $czcionka_y = 25;
[10] $czcionka_skok_x = 50;
[11]
[12]   
[13] header("Content-type: image/jpeg");
[14] $img = imagecreate($image_x, $image_y);
[15] $background = imagecolorallocate($img, 255, 255, 255);
[16] imagettftext($img, $czcionka_size, rand(-70,70), $czcionka_x, $czcionka_y, imagecolorallocate($img, 50, 50, 50), 'arial.ttf', $lit[0]);
[17] imagettftext($img, $czcionka_size, rand(-70,70), $czcionka_x + (1 * $czcionka_skok_x), $czcionka_y, imagecolorallocate($img, 50, 50, 50), 'arial.ttf', $lit[1]);
[18] imagettftext($img, $czcionka_size, rand(-70,70), $czcionka_x + (2 * $czcionka_skok_x), $czcionka_y, imagecolorallocate($img, 50, 50, 50), 'arial.ttf', $lit[2]);
[19] imagettftext($img, $czcionka_size, rand(-70,70), $czcionka_x + (3 * $czcionka_skok_x), $czcionka_y, imagecolorallocate($img, 50, 50, 50), 'arial.ttf', $lit[3]);
[20] imagettftext($img, $czcionka_size, rand(-70,70), $czcionka_x + (4 * $czcionka_skok_x), $czcionka_y, imagecolorallocate($img, 50, 50, 50), 'arial.ttf', $lit[4]);
[21] imagejpeg($img, null, 10);
[22] imagedestroy($img);
[23]
[24] ?>

W krótkich słowach omówię co skrypt robi.
W linii [03] uruchamia obsługę sesji
W linii [04] do zmiennej $lit wczytuje dane ze zmiennej sesyjnej $_SESSION['literki'], które zostały wysłane z poprzedniego skryptu index.php
W linii [05] ustala szerokość generowanego obrazu
W linii [06] ustala wysokość generowanego obrazu
W linii [07] ustala rozmiar czcionki która zostanie wyświetlona na obrazie
W linii [08] ustala pozycję x pierwszego znaku w obrazie
W linii [09] ustala pozycję y pierwszego znaku w obrazie
W linii [10] ustala odległość pomiędzy poszczególnymi znakami
W linii [13] wysyłana jest do przeglądarki informacja, że generowany plik to .jpeg i tak należy go traktować
W linii [14] tworzymy obraz o rozmiarach wcześniej ustalonych
W linii [15] ustalamy kolor tła na biały. Tutaj też możemy wybrać dowolny inny kolor tła obrazu.
W linii [16] generujemy pierwszy znak na obrazie. Proszę zwrócić uwagę na czcionkę która została tutaj użyta. Można wykorzystać dowolne czcionki .ttf więc możliwości zmian wyglądu generowanego obrazka jest bardzo dużo. Proszę pamiętać, iż plik czcionki w tym przypadku arial.ttf powinien znajdować się w tym samym katalogu co plik skryptu.
W linii [17], [18], [19] i [20] generujemy kolejne literki.
W linii [21] tworzymy obraz jpeg
W linii [22] usuwamy obraz z pamięci komputera.

To wszystko. Teraz możemy wyświetlić obraz użytkownikowi. Po wszystkich przeróbkach nasz skrypt wygląda teraz tak:

<?php

    session_start();
    
   $tabznak = array('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', '&', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '%');
   $znak1 = rand(0,36);
   do {
      $znak2 = rand(0,36);
   } while ($znak2 == $znak1);
   do {
      $znak3 = rand(0,36);
   } while (($znak3 == $znak1) or ($znak3 == $znak2));
   do {
      $znak4 = rand(0,36);
   } while (($znak4 == $znak1) or ($znak4 == $znak2) or ($znak4 == $znak3));
   do {
      $znak5 = rand(0,36);
   } while (($znak5 == $znak1) or ($znak5 == $znak2) or ($znak5 == $znak3) or ($znak5 == $znak4));
   $literki = $tabznak[$znak1];
   $literki.= $tabznak[$znak2];
   $literki.= $tabznak[$znak3];
   $literki.= $tabznak[$znak4];
   $literki.= $tabznak[$znak5];
    
    $_SESSION['literki'] = $literki;

?>

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8" />
</head>
<body>
<form method="POST" action="index.php?wyslany=1">
<input type="text" name="wiadomosc" value="" />
<input type="submit" name="wyslij" value="Wyślij wiadomość" />
<br />
<img src="grafika.php" alt="literki" />
</form>
</body>
</html>

A generowany obraz w ten sposób:

Grafika-02

Teraz należy wyświetlić jeszcze elementy radio które umożliwią użytkownikowi wybranie odpowiedniego elementu oraz oczywiście wylosowanie i wyświetlenie informacji, który element ma wybrać. Losowanie wykonujemy standardowo losując jeden znak z 5:

<?php
    $wybrana = rand(1,5);
?>

Oczywiście zapisujemy ją również jako zmienna sesyjna, aby wykorzystać po przeładowaniu strony po wysłaniu formularza:

<?php
    $_SESSION['wybrana'] = $wybrana;
?>

I oczywiście wyświetlamy pola radio aby użytkownik miał co wskazać. Najlepiej wykorzystać do tego pętlę for w PHP:

<?php
    for ($petla=1;$petla<=5;$petla++) {
        echo '<input type="radio" name="znak" value="'.$petla.'" style="margin-left:30px;" />';
    }
?>

Teraz mamy już cały skrypt wyglądający tak:

<?php

    session_start();
    
   $tabznak = array('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', '&', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '%');
   $znak1 = rand(0,36);
   do {
      $znak2 = rand(0,36);
   } while ($znak2 == $znak1);
   do {
      $znak3 = rand(0,36);
   } while (($znak3 == $znak1) or ($znak3 == $znak2));
   do {
      $znak4 = rand(0,36);
   } while (($znak4 == $znak1) or ($znak4 == $znak2) or ($znak4 == $znak3));
   do {
      $znak5 = rand(0,36);
   } while (($znak5 == $znak1) or ($znak5 == $znak2) or ($znak5 == $znak3) or ($znak5 == $znak4));
   $literki = $tabznak[$znak1];
   $literki.= $tabznak[$znak2];
   $literki.= $tabznak[$znak3];
   $literki.= $tabznak[$znak4];
   $literki.= $tabznak[$znak5];
    
    $_SESSION['literki'] = $literki;
    
    $wybrana = rand(1,5);
    $_SESSION['wybrana'] = $wybrana;

?>

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8" />
</head>
<body>
<form method="POST" action="index.php?wyslany=1">
<input type="text" name="wiadomosc" value="" />
<input type="submit" name="wyslij" value="Wyślij wiadomość" />
<br />
Proszę zaznaczyć pole pod znakiem <?php echo $literki[$_SESSION['wybrana']-1]; ?><br />
<img src="grafika.php" alt="literki" /><br />
<?php
for ($petla=1;$petla<=5;$petla++) {
    echo '<input type="radio" name="znak" value="'.$petla.'" style="margin-left:30px;" />';
}
?>
</form>
</body>
</html>

A w przeglądarce efekt jego działania wygląda tak:

Grafika-03

Teraz wystarczy obsłużyć całość w naszym skrypcie po odbiorze danych:

<?php
    if ((isset($_GET['wyslany'])) and (isset($_POST['znak']))) { // Formularz został wysłany i jakiś znak został zaznaczony
        if ($_SESSION['wybrana'] == $_POST['znak']) { // Wybrany został prawidłowy znak
            echo 'Został wybrany prawidłowy znak';
            exit;
        }
    }
?>

Jeżeli formularz został wysłany i zaznaczony został jakiś znak to sprawdź czy zaznaczony znak to znak wcześniej wylosowany ($_SESSION['wybrana']). Jeżeli tak to wyświetl tekst "Został wybrany prawidłowy znak".

Na koniec warto zaznaczyć, że skrypt sprawdzający powinien być wykonany w kodzie przed fragmentem który generuje nowy wybrany znak, gdyż odnosi się jeszcze do starego losowania.

Całość wygląda teraz tak:

Plik index.php:

<?php

    session_start();
    
    if ((isset($_GET['wyslany'])) and (isset($_POST['znak']))) { // Formularz został wysłany i jakiś znak został zaznaczony
        if ($_SESSION['wybrana'] == $_POST['znak']) { // Wybrany został prawidłowy znak
            echo 'Został wybrany prawidłowy znak';
            exit;
        }
    }
    
   $tabznak = array('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', '&', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '%');
   $znak1 = rand(0,36);
   do {
      $znak2 = rand(0,36);
   } while ($znak2 == $znak1);
   do {
      $znak3 = rand(0,36);
   } while (($znak3 == $znak1) or ($znak3 == $znak2));
   do {
      $znak4 = rand(0,36);
   } while (($znak4 == $znak1) or ($znak4 == $znak2) or ($znak4 == $znak3));
   do {
      $znak5 = rand(0,36);
   } while (($znak5 == $znak1) or ($znak5 == $znak2) or ($znak5 == $znak3) or ($znak5 == $znak4));
   $literki = $tabznak[$znak1];
   $literki.= $tabznak[$znak2];
   $literki.= $tabznak[$znak3];
   $literki.= $tabznak[$znak4];
   $literki.= $tabznak[$znak5];
    
    $_SESSION['literki'] = $literki;
    
    $wybrana = rand(1,5);
    $_SESSION['wybrana'] = $wybrana;

?>

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8" />
</head>
<body>
<form method="POST" action="index.php?wyslany=1">
<input type="text" name="wiadomosc" value="" />
<input type="submit" name="wyslij" value="Wyślij wiadomość" />
<br />
Proszę zaznaczyć pole pod znakiem <?php echo $literki[$_SESSION['wybrana']-1]; ?><br />
<img src="grafika.php" alt="literki" /><br />
<?php
for ($petla=1;$petla<=5;$petla++) {
    echo '<input type="radio" name="znak" value="'.$petla.'" style="margin-left:30px;" />';
}
?>
</form>
</body>
</html>

Plik grafika.php:

<?php

    session_start();
    $lit = $_SESSION['literki'];
    $image_x = 360;
    $image_y = 40;
    $czcionka_size = 16;
    $czcionka_x = 20;
    $czcionka_y = 25;
    $czcionka_skok_x = 50;
   
   header("Content-type: image/jpeg");
   $img = imagecreate($image_x, $image_y);
   $background = imagecolorallocate($img, 255, 255, 255);
   imagettftext($img, $czcionka_size, rand(-70,70), $czcionka_x, $czcionka_y, imagecolorallocate($img, 50, 50, 50), 'arial.ttf', $lit[0]);
   imagettftext($img, $czcionka_size, rand(-70,70), $czcionka_x + (1 * $czcionka_skok_x), $czcionka_y, imagecolorallocate($img, 50, 50, 50), 'arial.ttf', $lit[1]);
   imagettftext($img, $czcionka_size, rand(-70,70), $czcionka_x + (2 * $czcionka_skok_x), $czcionka_y, imagecolorallocate($img, 50, 50, 50), 'arial.ttf', $lit[2]);
   imagettftext($img, $czcionka_size, rand(-70,70), $czcionka_x + (3 * $czcionka_skok_x), $czcionka_y, imagecolorallocate($img, 50, 50, 50), 'arial.ttf', $lit[3]);
   imagettftext($img, $czcionka_size, rand(-70,70), $czcionka_x + (4 * $czcionka_skok_x), $czcionka_y, imagecolorallocate($img, 50, 50, 50), 'arial.ttf', $lit[4]);
   imagejpeg($img, null, 10);
   imagedestroy($img);

?>

Zapraszam do eksperymentowania z tym skryptem i wprowadzania własnych ulepszeń.

 

Komentarze(6)

Podpis:
W celu potwierdzenia, zaznacz pole pod znakiem: U
Capcha
tester luster - IP: xxx.xx.244.44, Data: 06.02.2023 18:36:15
test
Gall - IP: xx.xxx.0.197, Data: 19.09.2022 14:24:09
Wszystko ok
Xenia - IP: xxx.xx.238.18, Data: 24.10.2017 11:30:51
Nie generuje mi się grafika, nie wiem dlaczego?
juz - IP: xx.xx.78.110, Data: 21.04.2017 00:54:29
super
RomanRz - IP: xx.xxx.161.241, Data: 31.03.2017 07:55:33
Fajne to, nie ma jak własne pomysły. Bardzo dobrze wytłumaczone, profeska...
gollent - IP: xx.xxx.89.7, Data: 19.02.2016 13:22:09
bardzo fajny pomysł
(c)2007-2016 Waldemar Miotk - ostatnia aktualizacja silnika 06.02.2016 - Twój IP: 18.118.146.46. Ta strona, aby lepiej działać, używa plików cookie przechowywanych na komputerach użytkowników. Wszystkie prawa do tekstów zamieszczonych na stronie są zastrzeżone, chyba że przy konkretnym tekście znajduje się inna informacja.
go up