Pobieranie prezentacji. Proszę czekać

Pobieranie prezentacji. Proszę czekać

Kompletny przewodnik po SQL injection dla developerów PHP (i nie tylko) Krzysztof Kotowicz PHP Developer http://web.eskot.pl Medycyna Praktyczna krzysztof@kotowicz.net.

Podobne prezentacje


Prezentacja na temat: "Kompletny przewodnik po SQL injection dla developerów PHP (i nie tylko) Krzysztof Kotowicz PHP Developer http://web.eskot.pl Medycyna Praktyczna krzysztof@kotowicz.net."— Zapis prezentacji:

1 Kompletny przewodnik po SQL injection dla developerów PHP (i nie tylko)
Krzysztof Kotowicz PHP Developer Medycyna Praktyczna 1

2 Plan prezentacji Co to jest SQL injection?
Dlaczego SQL injection jest groźne (demo)? Jak się bronić? Prepared statements Escape'owanie Procedury składowane Metody uzupełniające Podsumowanie 2 2

3 Omawiane bazy danych (RDBMS)
MySQL Oracle MS SQL Server W mniejszym stopniu: PostgreSQL SQLite 3 3

4 Omawiane projekty PHP PDO – PHP data objects Doctrine 1.2 Propel 1.4
Wspólny interfejs dla różnych RDBMS Doctrine 1.2 ORM (Object Relational Mapper) używany m.in. we frameworku Symfony Propel 1.4 ORM konkurencyjny dla Doctrine Używany we frameworku Symfony Zend Framework 1.10 Popularny framework MVC dla PHP MDB Warstwa abstrakcji bazy danych (DBAL) Dystrybuowany przez PEAR 4 4

5 Co to jest SQL injection?
5 5

6 SQL injection – krótka definicja
Jest to rodzaj ataku na aplikacje internetowe. Polega na tym, że dane od użytkownika pochodzące z: URL: Formularzy: Innych elementów: np. cookie, nagłówki HTTP zostają zmanipulowane tak, że w podatnej aplikacji zostaje wykonane „wstrzyknięte” przez atakującego polecenie SQL. 6 6

7 Przykład – formularz logowania
SELECT * FROM users WHERE login = '{$login}' and password_hash = MD5('{$password}') $login = "' or 1=1 -- "; $password = "dowolne"; SELECT * FROM users WHERE login = '{$login}' and password_hash = MD5('{$password}') SELECT * FROM users WHERE login = '{$login}' and password_hash = MD5('{$password}') $login = "' or 1=1 -- "; $password = "dowolne"; // zamierzalismy osiagnac to (kod \ dane) SELECT * FROM users WHERE login = '' or 1=1 -- ' and password_hash = MD5('dowolne') SELECT * FROM users WHERE login = '{$login}' and password_hash = MD5('{$password}') $login = "' or 1=1 -- "; $password = "dowolne"; // zamierzalismy osiagnac to (kod \ dane) SELECT * FROM users WHERE login = '' or 1=1 -- ' and password_hash = MD5('dowolne') // serwer interpretuje to tak Użytkownik jest zalogowany bez znajomości loginu ani hasła 7 7

8 Dlaczego jest groźne? DEMO 8 8

9 Nieuprawniony dostęp do aplikacji
Czym grozi podatność na SQL injection? Nieuprawniony dostęp do aplikacji Dostęp do całej zawartości bazy / baz na serwerze Denial of service Możliwość modyfikacji danych w bazie Przeczytanie / zapisanie pliku na serwerze Wykonanie kodu na serwerze 9 9

10 Kilka faktów Podatności na injection na pierwszym miejscu OWASP Top RC Odpowiada za 40–60% przypadków wycieku danych [1] [2] Obecne techniki ataku są bardzo zaawansowane i często automatyzowane Podatność nie tylko w części WHERE Czasem celem jest zepsucie zapytania Codziennie znajdowane podatności, nawet w nowych aplikacjach 10 10 10

11 Jak się bronić? 11 11

12 Jak się bronić przed SQL injection?
Źródło podatności - łączenie kodu z danymi Metody obrony Oddzielenie kodu od danych prepared statements stored procedures Escape'owanie danych SELECT * FROM users WHERE login = 'login' 12 12

13 Jak się bronić? Prepared statements 13 13

14 Prepared statements – zasada działania
Przygotowujemy polecenie SQL (string) W miejsce danych wstawiamy znaczniki Przesyłamy polecenie na serwer Podajemy zestaw danych do polecenia Wykonujemy polecenie Odbieramy rezultat 3, 4, 5 można powtarzać... Czyścimy polecenie WHERE a = ? ... WHERE a = :col PREPARE EXECUTE 14 14

15 Prepared statements - przykład
Przykład działania (PDO) // przygotowujemy zapytanie $stmt = $dbh->prepare("INSERT INTO SUMMARIES (name, sum) VALUES (:name, :sum)"); // podajemy wartosci zmiennych – RAZEM Z TYPAMI! $stmt->bindParam(':name', $name, PDO::PARAM_STR); $stmt->bindParam(':sum', $sum, PDO::PARAM_INT); // podajemy wartości zmiennych $name = 'something'; $value = 1234; // wykonujemy zapytanie $stmt->execute(); $stmt = null; //zwalniamy pamiec 15 15

16 Prepared statements - zalety
Polecenia SQL są całkowicie oddzielone od przetwarzanych danych Brak możliwości wstrzyknięcia kodu SQL Polecenie SQL jest przez serwer kompilowane tylko raz – potencjalne zwiększenie wydajności zapytań $stmt->bindParam(':name', $name, PDO::PARAM_STR); $stmt->bindParam(':sum', $sum, PDO::PARAM_INT); // petla po danych... foreach ($do_bazy as $name => $value) { $stmt->execute(); } 16 16

17 Prepared statements - uwagi
Nie wszystkie typy poleceń można parametryzować Nie w każdym miejscu polecenia można wstawić parametr Samo ich użycie nie wymusza stosowania parametrów Czasem są emulowane (ale to dobrze!) -- blad SELECT * FROM :tabela SELECT :funkcja(:kolumna) FROM :widok -- nie tego się spodziewacie SELECT * FROM tabela WHERE :kolumna = 1 SELECT * FROM tabela GROUP BY :kolumna 17 17

18 Prepared statements w Doctrine
Używa PDO (emulacja dla Oracle) i prepared statements Zamiast SQL używa własnego języka – DQL $q = Doctrine_Query::create() ->select('u.id') ->from('User u') ->where('u.login = ?', ‘mylogin'); echo $q->getSqlQuery(); // SELECT u.id AS u__id FROM user u // WHERE (u.login = ?) $users = $q->execute(); 18 18

19 Prepared statements w Doctrine cd.
Wciąż można „wpaść” Trzeba poprawić na: NIGDY nie umieszczaj danych wejściowych bezpośrednio w treści zapytań $q = Doctrine_Query::create() ->update('Account') ->set('amount', 'amount + 200') ->where("id > {$_GET['id']}"); ->where("id > ?", (int) $_GET['id']); 19 19

20 Prepared statements w Propel
Podobnie jak Doctrine, oparty na PDO // poprzez Criteria $c = new Criteria(); $c->add(AuthorPeer::FIRST_NAME, "Karl"); $authors = AuthorPeer::doSelect($c); // poprzez customowy SQL (czasem jest latwiej) $pdo = Propel::getConnection(BookPeer::DATABASE_NAME); $sql = "SELECT * FROM skomplikowany_sql JOIN cos_jeszcze_gorszego USING cos_tam WHERE kolumna = :col)”; $stmt = $pdo->prepare($sql); $stmt->execute(array('col' => 'Bye bye SQLi!'); 20 20

21 Prepared statements w Zend Framework
PDO (+ mysqli + oci8 + sqlsrv) // prepare + execute $stmt = $db->prepare('INSERT INTO server (key, value) VALUES (:key,:value)'); $stmt->bindParam('key', $k); $stmt->bindParam('value', $v); foreach ($_SERVER as $k => $v) $stmt->execute(); // prepare + execute w jednym kroku $stmt = $db->query('SELECT * FROM bugs WHERE reported_by = ? AND bug_status = ?', array('goofy', 'FIXED')); while ($row = $stmt->fetch()) echo $row['bug_description']; 21 21

22 Emuluje PS, jeśli baza ich nie wspiera
MDB2 Oparty na konkretnych sterownikach baz danych (mysql, oci8, mssql, ...) Emuluje PS, jeśli baza ich nie wspiera $types = array('integer', 'text', 'text'); $stmt = $mdb2->prepare('INSERT INTO numbers VALUES (:id, :name, :lang)', $types); $data = array('id' => 1, 'name' => 'one', 'lang' => 'en'); $affectedRows = $stmt->execute($data); $stmt->free(); 22 22

23 Prepared statements - podsumowanie
Oferują bardzo dobre zabezpieczenie (jeśli użyte poprawnie) Łatwe w użyciu, niewielkie zmiany w kodzie Dobre wsparcie we frameworkach Mają swoje ograniczenia Czasem muszą być uzupełniane innymi metodami zabezpieczeń 23 23

24 Jak się bronić? Escape'owanie danych 24 24

25 Escape'owanie – zasada działania
Dane i polecenia wciąż trzymamy w jednej zmiennej, ale zabezpieczamy je Liczby Rzutowanie na (int) / (float) – nie is_numeric [1]! Teksty - zwykle otoczone apostrofami: ' Jeśli w tekście również są apostrofy, trzeba je odróżnić od apostrofu „kończącego” Apostrof wewnątrz danych jest poprzedzany znakiem specjalnym, np. "\" Reguły escape'owania zależą od kontekstu! .. WHERE pole = 'DANE TEKSTOWE' AND ... 25 25

26 Escape'owanie – kontekst
addslashes() Returns a string with backslashes before characters that need to be quoted in database queries etc. These characters are single quote ('), double quote ("), backslash (\) and NUL (the NULL byte). / Źródło: php.net manual / Czy jesteś bezpieczny? $user = addslashes($_GET['u']); $pass = addslashes($_GET['p']); $sql = "SELECT * FROM users WHERE username = '{$user}' AND password = '{$pass}'"; $ret = exec_sql($sql); 26 26

27 NIE

28 Escape'owanie – kontekst cd.
Różne RDBMS mają różne sposoby escape'owania danych (zależy to też od konfiguracji bazy) addslashes() tylko „przypadkiem” działa dla MySQL RBDMS Funkcja mam 'apostrofy' PDO $pdo->quote($val, $type) n/d (różnie) MySQL (mysql) mysql_real_escape_string mam \'apostrofy\' MySQL (mysqli) mysqli_real_escape_string Oracle (oci8) n/d - str_replace() mam ''apostrofy'' SQLite sqlite_escape_string MS SQL (mssql) PostgreSQL pg_escape_string() Sprawdzic ansi mode 28 28

29 Escape'owanie – kontekst cd.
// SELECT * FROM users WHERE username = // '{$user}' AND password = '{$pass}' $_GET['u'] = "cokolwiek'"; $_GET['p'] = " or 1=1 -- "; // MySQL widzi to tak: SELECT * FROM users WHERE username = 'cokolwiek\'' AND password = ' or 1=1 -- ' // SQLite / MS SQL / Oracle / PostgreSQL - tak: Nie używaj addslashes(), używaj funkcji konkretnej bazy Czy teraz jesteś bezpieczny? 29 29

30 PRAWIE

31 Pułapki escape'owania – zestawy znaków
Błędy wykryte w 2006 r. w PostgreSQL i MySQL [1] [2] W niektórych wielobajtowych zestawach znaków pomimo escape’owania można doprowadzić do SQL injection \ zostaje „połknięty” przez wielobajtowy znak Przykład: BF 27 [ ¬ ' ]  BF 5C 27 [ ¬ \ ' ] Pierwsze dwa bajty to w charsecie GBK znak ¿ Serwer „zobaczy” ciąg ¿' 31 31

32 Pułapki escape'owania – zestawy znaków
Podatne są różne azjatyckie zestawy znaków Na szczęście nie UTF-8! W PostgreSQL zastosowano escape'owanie poprzez '' (zamiast \') W mysql_real_escape_string() zastosowano uwzględnianie bieżącego zestawu znaków Nie zawsze zadziała! [1] [2] Kontekst to również zestaw znaków 32 32

33 Escape'owanie – nazwy obiektów
Nazwy kolumn, tabel, baz Nie ma dobrej ogólnej metody na ich escape'owanie W różnych bazach różne listy słów zarezerwowanych, różne długości nazw itp. Jeśli musisz pobierać te nazwy od użytkownika, zastosuj whitelisting (blacklisting w ostateczności) 33 33

34 Escape'owanie – nazwy obiektów cd.
Przykład – sortowanie po kolumnie Jest podatność w $order, ale nie możesz użyć escape'owania $cat_id = (int) $_GET['cid']; $order = $_GET['column']; $stmt = $pdo->prepare("SELECT * FROM products WHERE cid = :cid ORDER BY $order"); $stmt->bindParam(':cid', $cat_id, PDO::PARAM_INT); if ($stmt->execute()) { ... } 34 34

35 Escape'owanie – nazwy obiektów cd.
Whitelisting Blacklisting $columns = array( // lista dozwolonych kolumn 'product_name','cid','price', ); if (!in_array($order, $columns, true)) $order = 'product_name'; // wartosc domyslna // tylko znaki a-z i _ $order = preg_replace('/[^a-z_]/', '', $order); // max 40 znakow $order = substr($order, 0, 40); 35 35

36 PDO::quote($value, $type, $len) Długość i typ bywają ignorowane!
Escape'owanie w PDO PDO::quote($value, $type, $len) Długość i typ bywają ignorowane! Liczby najlepiej rzutuj na (int), (float) Teksty – obcinaj ręcznie $quoted = $pdo->quote($input, PDO::PARAM_STR, 40); 36 36

37 Escape'owanie w Doctrine
Uwaga na Doctrine'owe quote()! $q = Doctrine_Query::create(); // nie tak!!! $quoted = $q->getConnection()->quote($input, 'text'); $q->update('User')->set('username', $quoted); // quote() zamienia ' na '' - exploit (MySQL): $input = 'anything\\\' where 1=1 -- '; // trzeba escape'owac poprzez PDO - getDbh(): $quoted = $q->getConnection() ->getDbh() ->quote($input, PDO::PARAM_STR); // 'anything \\\\\\\' where 1=1 -- ' 37 37

38 Escape'owanie w Propel Poprzez PDO::quote()
$pdo = Propel::getConnection(UserPeer::DATABASE_NAME); $c = new Criteria(); $c->add(UserPeer::PASSWORD, "MD5(".UserPeer::PASSWORD.") " ." = " . $pdo->quote($password), Criteria::CUSTOM); 38 38

39 Escape'owanie w Zend Framework
Funkcje quote(), quoteInto() $name = $db->quote("O'Reilly"); // 'O\'Reilly' // uproszczone escape'owanie dla jednej zmiennej $sql = $db->quoteInto("SELECT * FROM products WHERE product_name = ?", 'any string'); 39 39

40 Escape'owanie w MDB Funkcja quote()
// funkcja quote()- trzeba określić typ $query = 'INSERT INTO table (id, itemname, saved_time) VALUES (' . $mdb2->quote($id, 'integer') .', ' . $mdb2->quote($name, 'text') .', ' . $mdb2->quote($time, 'timestamp') .')'; $res = $mdb2->exec($query); 40 40

41 Escape'owanie danych - podsumowanie
Wydaje się proste – zastępowanie tekstu Niestety, tylko się wydaje Musimy znać kontekst (baza danych, charset) Istnieją błędne implementacje Skłania do stosowania niebezpiecznych konstrukcji sklejanie poleceń ignorowanie zmiennych numerycznych Stosowanie dopuszczalne tylko, jeśli Programujemy pod konkretną bazę Nie ma innej możliwości 41 41

42 Jak się bronić? Procedury składowane 42 42

43 Procedury składowane Polecenie SQL (lub seria poleceń) zostaje przeniesione na serwer bazy danych i zapisane jako procedura Po stronie klienta procedura zostaje wywołana z określonymi parametrami (danymi) wejściowymi i wyjściowymi W parametrach wyjściowych klient otrzymuje wyniki procedury Dane są formalnie oddzielone od kodu To NIE wystarcza 43 43

44 Przykład w MS SQL – fragment podatnej procedury
Procedury składowane Przykład w MS SQL – fragment podatnej procedury To eval() w kolejnym wcieleniu! CREATE PROCEDURE SP_ProductSearch @prodname varchar(400) AS nvarchar(4000) = 'SELECT ProductID, ProductName, Category, Price FROM Product Where ProductName LIKE ''' + '''' EXEC ... 44 44

45 Procedury składowane cd.
Przykład tej samej podatności w Oracle CREATE OR REPLACE PROCEDURE SP_ProductSearch(Prodname IN VARCHAR2) AS sqltext VARCHAR2(80); BEGIN sqltext := 'SELECT ProductID, ProductName, Category, Price FROM Product WHERE ProductName LIKE ''' || Prodname || ''''; EXECUTE IMMEDIATE sqltext; ... END; 45 45

46 Procedury składowane – Dynamic SQL
Źródło podatności – Dynamic SQL Dane znów „przemieszane” z kodem w jednej zmiennej, która zostaje wykonana jako polecenie SQL Jak się obronić? Oddziel kod od danych Escape'uj 46 46

47 Procedury składowane w MS SQL
Oddzielenie danych od kodu użyj sp_executesql razem z listą parametrów CREATE PROCEDURE varchar(400) = NULL AS nvarchar(4000) = N'SELECT ProductID, ProductName, Category, Price FROM Product Where ProductName EXEC varchar(400)', @prodname 47 47

48 Procedury składowane w MS SQL cd.
Escape'owanie zmiennych tekstowych Przykład: Escape'uj tylko wtedy, kiedy musisz! (używaj sp_executesql z parametrami) Nazwa obiektu Tekst <= 128 znaków Tekst > 128 znaków = N'select * from authors where lname=''' + '''', '''''') + N'''' 48 48

49 Procedury składowane w Oracle
Oracle - użyj EXECUTE IMMEDIATE .. USING Escape'owanie - pakiet DBMS_ASSERT CREATE OR REPLACE PROCEDURE SP_ProductSearch(Prodname IN VARCHAR2) AS sqltext VARCHAR2(80); BEGIN sqltext := 'SELECT ProductID, ProductName, Category, Price WHERE ProductName=:p'; EXECUTE IMMEDIATE sqltext USING Prodname; ... END; 49 49 CREATE PROCEDURE varchar(400) = NULL AS nvarchar(4000) = 'SELECT ProductID, ProductName, Category, Price FROM Product Where ProductName EXEC varchar(400)', @prodname

50 Procedury składowane w MySQL
Wsparcie dla Dynamic SQL tylko poprzez prepared statements Napisanie podatnych procedur jest trudniejsze niż procedur zabezpieczonych! Wystarczy używać placeholderów zamiast doklejać wartości zmiennych pO tym dodac slajd, jak wywolac procedure w SQL (call / exec) 50 50

51 Procedury składowane w MySQL cd.
PREPARE / EXECUTE USING / DEALLOCATE PREPARE DELIMITER $$ CREATE PROCEDURE get_users_like ( IN contains VARCHAR(40)) BEGIN = CONCAT("%", contains, "%"); = "SELECT * FROM users WHERE uname LIKE ?"; PREPARE get_users_stmt EXECUTE get_users_stmt DEALLOCATE PREPARE get_users_stmt; END$$ DELIMITER ; 51 51

52 Procedury składowane w MySQL cd.
Lub jeszcze prościej (bezpośrednio) Escape'owanie – funkcja QUOTE() DELIMITER $$ CREATE PROCEDURE get_users_like ( IN contains VARCHAR(40)) BEGIN = CONCAT("%", contains, "%"); SELECT * FROM users WHERE uname END$$ DELIMITER ; 52 52

53 Procedury składowane w PHP
Różne wsparcie w zależności od RDBMS Wsparcie zależy od konkretnego sterownika Wspólne API (np. PDO) obsługuje tylko najprostsze wywołania Procedura nic nie zwraca Procedura zwraca prosty rezultat w parametrze OUT Różna obsługa (lub brak) bardziej zaawansowanych wywołań np. pobieranie rekordów z procedur, kursory Wsparcie we frameworkach śladowe Wciąż występują błędy 53 53

54 Procedury składowane w PDO
Wywołanie procedury // MySQL $sql = "CALL get_users_like(:contains)"; // MS SQL – EXEC get_users_like :contains $stmt = $pdo->prepare($sql); $ret = $stmt->execute(array('contains' => $input)); foreach($stmt->fetchAll() as $users) { var_dump($users); } unset($s); 54 54

55 Procedury składowane w Doctrine/Propel/Zend Framework
Doctrine - Brak wsparcia (użyj PDO) Propel – jw. Zend Framework – jw. $pdo = Doctrine_Manager::connection()->getDbh(); $pdo = Propel::getConnection(UserPeer::DATABASE_NAME); $pdo = $db::getConnection(); 55 55

56 Procedury składowane w MDB2
Trzeba własnoręcznie escape'ować wszystkie parametry $mdb2->loadModule('Function'); $multi_query = $mdb2->setOption('multi_query', true); if (!PEAR::isError($multi_query)) { $result = $mdb2->executeStoredProc('get_users_like', array($mdb2->quote($contains, 'text'))); do { while ($row = $result->fetchRow()) { var_dump($row); } } while ($result->nextResult()); 56 56

57 Procedury składowane - pułapki
Długość zmiennych CREATE PROCEDURE change_password @loginname varchar(50), @old varchar(50), @new varchar(50) AS varchar(120) 'UPDATE users SET password=' + '''') + ' WHERE loginname=' + '''') + ' AND password=' + '''') EXEC GO 57 57

58 Procedury składowane - podsumowanie
Czasochłonne przenoszenie logiki SQL z aplikacji na serwer Nie są łatwo przenośne pomiędzy RDBMS Napisane bezpiecznych procedur i tak wymaga użycia prepared statements lub escape'owania danych Źle zaimplementowane mogą zwiększyć podatność Zarówno wywołanie procedury, jak i jej kod jest podatny Procedura może mieć większe uprawnienia niż kod ją wywołujący Złe wsparcie w PHP i we frameworkach 58 58

59 Procedury składowane - podsumowanie
Mają dużo zalet poza naszym obszarem zainteresowania Można precyzyjnie zarządzać uprawnieniami do procedur Przydatne w wypadku stosowania różnych klientów (Java/.NET + PHP) Mogą zwiększyć wydajność I wiele innych... Wnioski: Pozwalają osiągnąć dobre zabezpieczenie przed SQL injection, ale przy dużych kosztach. Niezbędne jest zabezpieczanie samego kodu procedur przed SQL injection. 59 59

60 Jak się bronić? Metody uzupełniające 60 60

61 Walidacja i filtrowanie danych
Kontrola poprawności danych zewnętrznych Odbywa się przed przetwarzaniem tych danych Nie myl z escape'owaniem! Filter INPUT - escape OUTPUT Osobne reguły walidacji dla każdego parametru - sprawdzaj m.in. Typ zmiennej Skalar / tablica Wartości min / max Długość danych tekstowych! [1] 61 61

62 Uzupełniające metody obrony
Komplementarne do poprzednich! Zasada najmniejszych uprawnień przy łączeniu się do bazy danych Wyłączenie nieużywanych funkcji, kont, pakietów dostarczanych z bazą danych Regularne aktualizowanie serwera bazy danych Dobra konfiguracja PHP i bazy magic_quotes_* = false display_errors = false Dobrze zaprojektowana baza danych 62 62

63 Podsumowanie Zwracaj uwagę na SQL injection - pojedynczy błąd może wiele kosztować! Preferuj rozwiązania kompleksowe - np. frameworki Filtruj wszystkie dane wejściowe Pamiętaj o typach i długościach zmiennych Stosuj whitelisting zamiast blacklistingu - to drugie kiedyś zawiedzie! Stosuj prepared statements wszędzie, gdzie możesz Unikaj escape'owania W procedurach składowanych uważaj na Dynamic SQL 63 63

64 Linki Omawiane projekty sqlmap.sourceforge.net
php.net/manual/en/book.pdo.php propel.phpdb.org/trac framework.zend.com pear.php.net/package/MDB2 O SQL injection unixwiz.net/techtips/sql-injection.html delicious.com/koto/sql+injection Hack me threats.pl/bezpieczenstwo-aplikacji-internetowych tinyurl.com/webgoat mavensecurity.com/dojo.php 64 64


Pobierz ppt "Kompletny przewodnik po SQL injection dla developerów PHP (i nie tylko) Krzysztof Kotowicz PHP Developer http://web.eskot.pl Medycyna Praktyczna krzysztof@kotowicz.net."

Podobne prezentacje


Reklamy Google