luq techblog

o tworzeniu słów kilka…

(nie)Captcha – jak złamać captche a jak napisać swoją. 28 stycznia 2010

Filed under: PHP,Programowanie,Security,Web — Łukasz @ 00:56
Tags: , ,

Dziś tematem będą, ponownie już na moim blogu, – captche, czyli obrazki z kodem, który ma nas zabezpieczyć przed wypełnianiem formularza przez boty przeglądarek (rejestracją konta na serwisie, wysłaniem SMS`a z darmowej bramki etc.) ale również jest to zabezpieczenie przed automatyzacją pewnych operacji (np. wspomniane już właśnie wysyłanie SMS`ów, zresztą pisałem nt. Orange captcha). Pomysł wyprowadzano w życie już dawno i bardzo dużo serwisów z takiego rozwiązania korzysta. Również stronki domowe pisane przez zapaleńców często taki cuda mają (przy skomentowaniu artykułu np.). Jest to naturalne, bo pomysł jest ciekawy i fajny, są jeszcze inne sposoby ale nie o tym chciałem tu pisać.

 

Bardzo ważnym, nie tylko w dziedzinie informatyki/programowania, jest robienie wszystkiego z głową, a niektórzy zapominają o tym. Generowana captcha z zasady ma bronić nas przed zapycham korzystania z usług naszego serwisu przez boty przeglądarkowe, jest to jedyna ich funkcja. Natomiast bardzo często można się spotkać z „niby captchami” które właściwie nic z captcha nie mają wspólnego.

 

Ostatnio spotkałem się na pewnym serwisie, stronie właściwie, z capatchą generowaną jako .gif na osobnym linku

 
http://www.example.com/pic.php
 

i jeśli wklepać ten link to pojawi się nam prosta captcha o różnym napisie na obrazku (oraz różnym tle), zapewne rand() indeksu z tablicy zawierającej kody (stringi) captchy.
Natomiast poprzez wpisanie adresu

 
http://www.example.com/pic.php?id=X
 

Generowana była captcha z zawsze takim samym kodem ale różnym tłem, czyli zapewne $_GET[‚id’] było indeksem tablicy kodów captchy.Oczywiście na stronie gdzie captcha była niezbędna do napisania komentarza, link z generatorem tokena był wrzucony z atrybut src HTML`owego tagu <img/> tak jak, zresztą, często ma to miejsce. Natomiast, prawdopodobnie przez pomyłkę czy niedopatrzenie link podany był w formie:

 
http://www.example.com/pic.php?id=X
 

X pewnie był generowany na tej właśnie stronie z formularzem do dodania komentarza, a więc to tak naprawdę to nie była captcha. Wystarczy przelecieć $_GET[‚id’] od 1 do pierwszego który nie wyświetli obrazka i zapisać sobie do tablicy asocjacyjnej

 
'id' => 'kod captchy'
 

i cała captcha leży. Napisanie skryptu który korzystając z powyżej stworzonej tablicy, pisze spamowe komentarze nie jest wcale trudne. Następnym przykładem, który mogę podać, jest captcha mająca zawsze ten sam kod na obrazku, a jedynie co jest zmieniane to tło. Gdzie sens takiej captchy? Przecież programista chcący napisać automat wypełniający formularz i przechodzący przez to zabezpieczenie ma właściwie jedną linijkę więcej do napisania, więc gdzie sens?

 

Łamanie prostych captch

 

Prosta captcha zbudowana jest w ten sposób:
Prostokąt wypełniany jest tłem zawierającym jakieś tam plamki/linie/kwadraciki w tle o kolorze bardzo różnym od koloru czcionki, natomiast sam napis jest pisany prosto (bez żadnych uskoków i pochyleń) zawsze tą samą czcionką i kolorem bardzo różniącym się od tła. Właśnie takie captche są bardzo proste do złamania. Do takich możemy zaliczyć, właśnie obie z dwóch opisanych powyżej. Skupimy się na pierwszej z nich:

captcha n.1

captcha n.2

captcha n.3

We wpisie o captchy Orange było trochę na temat, jak się captche łamie. Tutaj jednak wystarczy zastosować filtr progowania z odpowiednio dobranym progiem aby na obrazku pozostawić sam czysty kod. Próg 100 daje radę i wychodzi z tego odpowiednio:

captcha n.1 after threshold

captcha n.2 after threshold

captcha n.3 after threshold

Ogólnie rzecz biorąc zbudowałem klasę do celu łamania takich prostych captch więc skupmy się na pomysłach zastosowanych w niej oraz na tym co można by robić inaczej. Po wspomnianym progowaniu, tworzona jest boolowska tablica 2D z informacjami czy dany piksel obrazka jest napisem czy pustym nieistotnym polem (wymazanym tłem). Taką tablicę można wyprintować sobie (metoda draw()):

------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
--------------------XX--------------------------------------
--------------------XX-----------------XX-------------------
---------------------------------------XX-------------------
-------XX----XX----XXX-------XXXXX---XXXXXX------XXXXX------
-------XX----XX-----XX------XX---XX----XX-------XX---XX-----
-------XX-XX-XX-----XX-----------XX----XX------------XX-----
-------XX-XX-XX-----XX------XXXXXXX----XX-------XXXXXXX-----
-------XX-XX-XX-----XX-----XX----XX----XX------XX----XX-----
-------XXXXXXXX-----XX-----XX---XXX----XX--XX--XX---XXX-----
--------XX--XX----XXXXXX----XXXX-XX-----XXXX----XXXX-XX-----
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------

Jak widać na obrazku literki mają między sobą odstęp, co bardzo ale to bardzo ułatwia sprawę. Nie ma problemu odseparować od siebie literki. Wystarczy przelecieć kolumna po kolumnie, od lewej do prawej i sprawdzać czy aby jeden z pikseli w danej kolumnie nie jest częścią kodu captchy. Jeśli tak, to znaczy, że od tej kolumny zaczyna się litera (jeśli nie to oczywiście jedziemy dalej aż aby uzyskać dany warunek lub do końca obrazka), następnie, począwszy od tej kolumny szukamy kolumny całkowicie pustej, wtedy to znak, że w tym miejscu jest koniec litery. Kolejną litery są szukane na prawo od ostatnio „zbadanej” kolumny. Jeszcze dodatkowo ucinamy przestrzeń pustą nad i pod literą i otrzymujemy literki (wyprintować je możemy metodą drawLetters())


XX----XX
XX----XX
XX-XX-XX
XX-XX-XX
XX-XX-XX
XXXXXXXX
-XX--XX-


--XX--
--XX--
------
-XXX--
--XX--
--XX--
--XX--
--XX--
--XX--
XXXXXX

(…)

Następnie można by zrobić rozpoznawanie liter na podstawie pewnych stałych dla danej litery, tzn. wysokości i szerokości oraz liczby pixeli znaczących (czarnych) w poszczególnych kolumnach i wierszach. Sposób dobry ale nie jest przenośny, tzn. dla innej czcionki trzeba by wszystko ponownie zbadać i policzyć, natomiast mnie zależało, żeby klasa działała na maksymalnej ilości captch, dlatego skupiłem się na rozpoznawaniu liter na podstawie podanych modeli. Po prostu w klasie podane jest jak wygląda literka ‚a’, ‚b’, ‚c’ (…) i podczas rozpoznawania tej narysowanej na obrazku, jest porównywana z kolejnym modelem. Jeśli występuje 100% zgodność, literka jest traktowana tak jak ta oznaczona w modelu. Oczywiście można dodawać i podmieniać defaultowe modele (metoda setLetterModel()).

Dla ułatwienia i czytelności konkretne już modele są podawane w jednowymiarowej tablicy stringów. Bez sensu żeby wygladało to tak:

array(
array( true, false, false, true ),
array( true, false, false, true ),
array( true, true, true, true )
);

No ja nie widzę w tym nic fajnego, natomiast wpisując to w ten sposób:

array(
'--XXXXX-',
'-XX---XX',
'------XX',
'-XXXXXXX',
'XX----XX',
'XX---XXX',
'-XXXX-XX'
)

widać wszystko na pierwszy rzut oka. Oczywiście znaki „X” (true) oraz „-” (false) można zmienić dla całej klasy, dzięki temu w innej konwencji podajemy modele oraz otrzymujemy wyniki rysowań (metoda setChar()). Wydaje mi się, że fajnym jest też to, że nie mając wygenerowanych modeli do danej czcionki możemy to zrobić w naprawdę kilka chwil. Jedyne co wystarczy to wrzucić obrazek do konstruktora klasy i skorzystać z metody drawLettersModel(), która printuje dla nas:


'?' => array(
'--XX----',
'--XX----',
'XXXXXX--',
'--XX----',
'--XX----',
'--XX----',
'--XX----',
'--XX--XX',
'---XXXX-',
)

(…)

Wystarczy skopiować, zmienić znak „?” na literę odpowiadającą a następnie dokładnie taką tablice wrzucić jako model metodą setLetterModel(). Proste, szybkie, łatwe… ;)
Ostatnią zaprezentowaną tutaj metodą klasy, jest zarazem podstawową metodą tej klasy do której wszystko się sprowadzą, metoda ta została nazwana getText() i oczywiście zwraca stringa z kodem podanej w konstruktorze captchy. Jeśli nie rozpozna podanej literki (nie ma modelu pasującego do litery) to zwróci * w tym miejscy kodu captchy.

 

Na końcu wpisu link do rara z klasą.

 

Pisanie własnej captchy

 

Nie będę tu pisał jak wygenerować obrazek, chodzi mi bardziej o rady co zwiększa trudność złamania, lub wręcz przeciwnie – ułatwia, a co jest nieistotne.

 

– kolor tła – kolor tła nie ma znaczenia, bo zawszę musi być kolor dość różny od czcionki w celu umożliwienia przeczytania kodu.
– wzorki/ linie/itp. na tle – tutaj dobrym pomysłem jest dodać takie elementu w dokładnie takim samum kolorze jak czcionka (ewentualnie ciut innym), dbając przy okazji o to żeby jednak kod był widoczny. Chodzi o to żeby po progowaniu został nam kod captchy oraz jakiś syf z tych wzorków.

 

– pochylenie liter – nie widzę sensu. W podanym przykładnie łamanej captchy takie pochylenie niczego by nie zmieniło.
– obrócenie liter – dobry pomysł. Proponowałbym zrobić losowe obrócenie literki o kąt z danego przedziału (tak żebyśmi nie obrócili o 90* bo to znacznie utrudni człowiekowi odczytanie kodu), w losową stronę.
– uskoki literek – chodzi mi o to, żeby rysować literki jedna wyżej druga niżej. Nie widzę sensu. W podanym przykładnie nic by to nie zmieniło.
– łączenie liter – bardzo utrudnia zadanie łamanie captchy ponieważ sam skrypt musi zdecydować gdzie jest koniec litery, na podstawie ilości istotnych pixeli w kolumnie (?)
– różne czcionki – jak najbardziej tak. Wtedy łamacz musiałby obsługiwać kilka naraz, więc myślę, że to dobry pomysł.

 

No więc tyle na dziś ;)
Pod spodem obiecany link. Jasne, że klasę należałoby jeszcze podrasować (multi modele, obsługa dużych liter, cyfr) ale myślę, że w obecnej formie prezentuje się nieźle.

 

CaptchaBreak v.0.1 (paczka z kodem i przykładem. Klasa działa w oparciu o inną moją klasę – Filter)

Reklamy
 

11 Responses to “(nie)Captcha – jak złamać captche a jak napisać swoją.”

  1. D.F. Says:

    Przydatna rzecz :)

  2. Misiur Says:

    Dzięki, właśnie tego szukałem – jak złamać captchę :D A tak na serio – stosuję recaptche, bo własnej roboty są po prostu ciężkie do wyważenia. Funkcjinalność wtedy leży i kwiczy.

  3. luq Says:

    @D.F.
    Dzięki ;)

    @Misiur
    Tak masz racje. Babranie się z napisaniem swojej captchy jest wydaje mi się dość trudne. reCAPTCHA to świetne rozwiązanie, stosowane na wielu dużych stronach. Zresztą poza zabezpieczeniem naszej strony pomagamy także w digitalizacji bibliotecznych zbiorów ;)

  4. brulionman Says:

    a ja sie dluuugo wpatruje w ten kod http://www.google.com/logos/olympics10-freestyleski-hp.png i nie mam pojecia jak ktos tam moze zobaczyc „Google” inaczej niz na sile, az mi sie skojarzylo z kodem Captcha :)

  5. ciesiel Says:

    fajne, fajne tylko te linki mi nie śmigają :(

  6. luq Says:

    @ciesiel zawiodłem się na Tobie! Ale dzięki za doprowadzeniu mnie do stanu „leże na podłodze i nie mogę wstać ze śmiechu” ;))

  7. Misiur Says:

    Teraz się bawię z różnymi captchami (no ba, cudzymi). I np. mój bot się myli – myli „x” z „o” i „v”, „X” z „O”. Dlaczego? Bo ich „kod” jest identyczny, tylko układ pikseli się różni w poziomie. Rozkminiłem już to, ale zwróć uwagę na to.

  8. luq Says:

    @Misiur, no właśnie moja klasa nie opiera się o zliczanie pikseli liter w kolumnach, a na ocr`rze polegającym na porównaniu liter względem modeli. To czyni ją „odporną” na tego typu „wpadki” ;)
    Dzięki za @ który pomógł w małym rozwoju klasy (do wersji 0.1.1), nowością jest możliwość ustawienia czy tekst (literki) są czarne czy białe, oraz obsługa znaków od ASCII 48 do z ASCII 122. Teraz wszystkie znaki z tego przedziału mogą być rozpoznane ;)

    Jutro (najprawdopodobniej) pojawi się wpis z nową wersją klasy. Jeśli ktoś chcę mieć ją wcześniej, zapraszam do kontaktu via. @.

  9. […] nie piszesz nic ciekawego. W końcu od zawsze lubiłem problemy które były wyzwaniem, tak jak np. CaptchaBreak. A więc stałem się frontend developerem. Natomiast nie zajmuję się pisaniem […]


Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj / Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj / Zmień )

Zdjęcie na Google+

Komentujesz korzystając z konta Google+. Wyloguj / Zmień )

Connecting to %s