21-08-2019 / Od 0 do pentestera

SQL Truncation

Kiedy korzystamy z bazy danych zakładamy, że działa ona prawidłowo - czyli, ze wyniki przez nią zwracane są zgodne z zapytaniem, którego użyliśmy.

Dzisiaj w "od 0 do pentestera" o tym, jak pewna specyfika działania i porównywania danych w bazie MySQL może doprowadzić do błędów SQL Truncation.

SQL Truncation

Popatrzmy na przykładowe tabele prostego serwisu internetowego.

Tabela users

W tabeli users przetrzymujemy informacje na temat loginu i hasła użytkownika.

Tabela users

W tabeli privs natomiast znajdują się dane na temat uprawnień danego użytkownika.

W zależności od tych uprawnień możliwy jest dostęp do panelu administratora.

Każde pole w tabeli MySQL ma określony typ.

Najpopularniejszymi są liczby całkowite a także ciągi znaków.

Do przechowywania tekstu służy kilka typów.

Pole typu varchar

Jednym z popularniejszych jest varchar.

Liczba po nawiasie definiuje maksymalną długość danego pola.

Informacje te służą do optymalizacji struktury danej bazy.

Jeżeli bowiem baza z wyprzedzeniem wile ile maksymalnie treści może być przetrzymywane w danym polu, może odpowiednio zoptymalizować całą strukturę pliku, tak aby wyszukiwanie i zapisywanie było jak najszybsze.

Ograniczenie wielkości danych

W naszym przypadku username i password mogą składać się maksymalnie z 10 znaków.

A to jest bardzo istotna informacja.

Dlaczego? O tym za chwile.

Najpierw musimy zastanowić się jak działa pobieranie informacji z bazy.

Na potrzeby demonstracji stworzyłem tu już wcześniej konto admina, który posiada uprawnienia super użytkownika.

Mogę pobrać dane na temat użytkownika korzystając ze składni SELECT oraz konstrukcji WHERE, gdzie podaję nazwę kolumny oraz treść, której poszukuje.

W naszym wypadku jest to użytkownik o loginie admin.

SELECT * FROM privs WHERE username = "admin"

I rzeczywiście baza zwróciła odpowiedni rekord.

Ale niewiele osób wie, że ze względów optymalizacyjnych w standardowej konfiguracji ten sam rekord zostanie zwrócony nawet, jeżeli do ciągu admin dodamy na końcu kilka spacji.

SELECT * FROM users WHERE username = "admin     "

Baza po prostu ignoruje te spacje. A to jest ważne.

Dodatkowe spacje w zapytaniu

Dlaczego?

Popatrzmy na przykładowy mechanizm rejestracji do naszej strony.

Mechanizm rejestracji

Najpierw sprawdzamy, czy w bazie istnieje rekord o danym loginie.

Jeżeli nie, tworzymy nowy w wpis w tabeli users oraz odpowiadający mu wpis w tabeli privs gdzie ustalamy, uprawnienia danego użytkownika.

Mechanizm logowania

Teraz, możemy się już zalogować.

Sprawdzamy, czy w bazie istnieje rekord z danym loginem oraz hasłem.

Jeżeli tak, na podstawie zwróconego loginu pobieramy informacje na temat danego użytkownika aby sprawdzić, czy jest administratorem.

Na razie wszystko wygląda OK. Nasz mechanizm działa.

Gdzie zatem tkwi dzisiejszy błąd?

Wspominałem wcześniej, że podczas wyszukiwania danych baza ignoruje dodatkowe spacje na końcu tekstu.

Mówiłem także, że każde pole ma określoną długość.

Co zatem w przypadku, gdy podana zostanie zbyt długa nazwę użytkownika, która nie mieści się w danym polu?

Zbyt długa nazwa użytkownika

To zależy od rodzaju zapytania.

W przypadku wyszukiwania - nic się nie zmienia.

Baza po prostu nie odnajdzie danego rekordu - ponieważ po prostu tak długi rekord nie istnieje w tej bazie.

W przypadku dodawania danych przy pomocy konstrukcji INSERT dane zostaną automatycznie przycięte do maksymalnej długości danego pola.

Spróbujmy wykorzystać te dwie informacje i zalogować się do tej bazy jako administrator.

Nie możemy użyć jako loginu tekstu admin.

Baza zwróci bowiem dany rekord i rejestracja nowego użytkownika się nie powiedzie.

Nie możemy również zakończyć go spacjami, ponieważ są one ignorowane.

Ale możemy po tych spacjach dopisać inny, nieistotny ciąg danych.

Działanie SQL Truncation

Wtedy taki rekord nie zostanie znaleziony w bazie ponieważ jest za długi.

Jeżeli rekordu nie ma w bazie skrypt stworzy nowy rekord przy pomocy instrukcji INSERT.

Tylko że ciąg jest za długi.

Baza więc automatycznie skróci takowy ciąg do 10 znaków tak, aby zmieścił się w danym polu.

To powoduje, że nagle w bazie posiadamy dwa rekordy o loginie admin z dwoma różnymi hasłami.

Konto admina z dodatkowymi spacjami

Jeden nie posiada białych znaków na końcu drugi natomiast je posiada.

Nie mają one jednak większego znaczenia, ponieważ w standardowej konfiguracji są ignorowane.

Teraz możemy się zalogować jako administrator używając naszego hasła.

Taki rekord znajduje się w bazie (jest tam bowiem wpis z naszymi dodatkowymi spacjami na końcu, które są ignorowane) - wiec skrypt pozwoli na zalogowanie się, równocześnie wyszuka informacje na temat uprawnień użytkownika.

Dane w tabeli privs również wyszukiwane są na podstawie loginu.

A ponieważ rekord oryginalnego administratora został stworzony wcześniej, to właśnie ten wpis zostanie zwrócony jako pierwszy.

Tym sposobem właśnie zalogowaliśmy się jako super użytkownik nie znając loginu i hasła oficjalnego admina.

Dodanie indeksu do danych

Oczywiście błąd ten występuje tylko w specyficznych przypadkach.

Jak zatem się chronić przed tego rodzaju atakami?

Najprościej dodać do tabeli unikalny indeks.

Wtedy to tabela nie pozwoli na stworzenie kolejnego użytkownika.