Autor wpisu: Michał Środek, dodany: 13.02.2010 22:33, tagi: php, sql
Ostatnio pracowałem nad pewnym projektem wraz z innym(„troszkę“ mniej doświadczonym) programistą. Starałem się przymykać oko na wiele jego błędów(niepotrzebne zmienne, brak obiektowości itp.) jednak jeden był niewybaczalny — brak filtracji danych przychodzących w zapytaniach SQL. Dzisiaj postaram się wytłumaczyć dlaczego to jest tak bardzo ważne pokazując jak haker w prosty sposób może wykraść loginy i hasła użytkowników ze słabo zabezpieczonej witryny.
Stwórzmy przykładową bazę danych „hack“ aby pokazać jak to wygląda od drugiej strony.
CREATE TABLE articles( id int(10) AUTO_INCREMENT, title varchar(80) NOT NULL, content text NOT NULL, author int(10), category int(10), PRIMARY KEY(id) ); CREATE TABLE users( id int(10) AUTO_INCREMENT, login varchar(40) NOT NULL, email varchar(60) NOT NULL, password varchar(32) NOT NULL, PRIMARY KEY(id) );
Wypełnijmy je danymi:
mysql> select * from articles; +----+--------------+----------+--------+----------+ | id | title | content | author | category | +----+--------------+----------+--------+----------+ | 1 | Artykul nr 1 | tresc... | 1 | 2 | | 2 | Artykul nr 2 | tresc... | 3 | 1 | | 3 | Artykul nr 3 | tresc... | 2 | 1 | | 4 | Artykul nr 4 | tresc... | 3 | 2 | +----+--------------+----------+--------+----------+ mysql> select * from users; +----+--------+--------------------+----------------------------------+ | id | login | email | password | +----+--------+--------------------+----------------------------------+ | 1 | Michal | michal@example.com | 1660fe5c81c4ce64a2611494c439e1ba | | 2 | Magda | romek@example.com | 9d0250b24620c2056516e5d2d79eed4a | | 3 | Romek | romek@example.com | 944278ab01f435bfc369fa038130f25b | +----+--------+--------------------+----------------------------------+
Czas na kod PHP odpowiedzialny za pobieranie artykułów. Pomijam filtrowanie zmiennych przychodzących. Wygląda to mniej więcej tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php $mysqli = new mysqli('localhost', 'root', '', 'hack'); if(isset($_GET['id'])) $result = $mysqli->query('SELECT a.*, u.login FROM articles a, users u WHERE a.author=u.id AND a.id='.$_GET['id']); else $result = $mysqli->query('SELECT a.*, u.login FROM articles a, users u WHERE a.author=u.id'); while($row = $result->fetch_array()) { echo '<h2>'.$row['title'].'</h2>'; echo '<strong>Author: '.$row['author'].'</strong>'; echo '<p>'.$row['content'].'</p>'; } ?> |
Wystarczy mała nieuwaga aby skrypt był dziurawy jak ser szwajcarski. Co więcej, dziurę taką da się bardzo łatwo wykryć — wystarczy apostrof lub cudzysłów.
http://localhost/hack/sql/?id=1'
Naszym oczom ukaże się błąd:
Fatal error: Call to a member function fetch_array() on a non-object in /home/hellson/public_html/hack/sql/index.php on line 27
Jest to informacja, że użytkownik może wpływać na treść zapytania SQL. Dlaczego tak się dzieje? PHP bezmyslnie umieszcza zmienną $_GET[’id’] do zapytania SQL które teraz wygląda tak:
SELECT a.*, u.login FROM articles a, users u WHERE a.author=u.id AND a.id=1'
Jest to niepoprawne zapytanie więc php zwraca błąd metody fetch_array(). Tym samym jest to informacja, że haker może spreparować własne zapytania, które zwrócą mu loginy i hasła. Oczywiście w sytuacji gdy nie zna on struktury bazy danych jest to troszkę utrudnione lecz wciąż możliwe. Aby uświadomić o jakie aspekty należy zadbać warto dowiedzieć się jak to robi przeciętny włamywacz.
Od strony hakera
Pierwszym krokiem jest sprawdzenie czy rzeczywiście możemy wpływać na zapytanie. Najlepiej jest dopisać nic nie znaczące „OR 1=1″
http://localhost/hack/sql/?id=1 OR 1=1
SELECT a.*, u.login FROM articles a, users u WHERE a.author=u.id AND a.id=1 OR 1=1
Naszym oczom powinna ukazać się pełna lista artykułów zamiast tylko jednego lub jeśli na stronie moduł pobiera tylko pierwszy rekord otrzymamy inny niż jest sprecyzowany w zmiennej id(oczywiście należy wziąć pod uwagę sortowanie). Kolejnym krokiem jest wprowadzenie własnego zapytania. Najczęściej używa się UNION ponieważ jest to najprostsza technika. UNION połączy 2 zapytania tylko wtedy gdy będą one posiadać taką samą ilość kolumn. Warto więc najpierw to sprawdzić metodą prób i błędów pobierając kolejno 2 NULL-e, 3 NULL-e itd. W naszym przykładzie wystarczy sześć.
Kanał ATOM

Nadszedł czas na pierwszą implementację tej struktury w sql, oczywiście nie jest to czego byśmy oczekiwali ponieważ ma pewne braki które mogły by się wydawać dla większego użytku wprost odwrotnie proporcjonalne do zastosowania role based access control. Otóż jest trochę uproszczona struktura, dlaczego? Mieliśmy w pracy z kolegą burzę mózgów na ten temat gdyż go bardzo zainteresował i doszedł do wniosku iż konstruowanie kolosa może mieć negatywny wpływ na jego interfejs, i prawdę powiedziawszy po głębszym zastanowieniu się oraz doświadczeniu wyniesionym z używania phpbb (taki skrypt forum) doszedłem do wniosku iż ma chłop rację. Więc na pierwszy ogień skonstruowaliśmy taką oto strukturę rbac’a, ale moje ego nie pozwala mi na zakończenie prac nad tym i będę kontynuował rozwój do takiego stopnia że stwierdzę iż większego kolosa się zbudować nie da ponieważ zawiera już wszystko.
Tym razem nie będzie nic o implementacji pewnej struktury drzewiastej w php. Natomiast zajmiemy się dziś sql. A dokładniej PostgreSQL, od dłuższego czasu nie mogłem ogarnąć modułu w postgresql, który nazywa się ltree, jak sama cząstkowa nazwy wskazuje i łatwo się domyślić że chodzi tutaj o reprezentację i strukturę drzewa. Co najlepsze, przeprowadzone benchmarki przez jego twórców wykazują na największy w śród tymczasowych ogólnie dostępnych wzorców tego typu struktury czas dostępu do danych.