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ść.
Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...
Zwiń
Czytaj na blogu autora...