Kompletny przewodnik po SQL injection dla developerów PHP (i nie ...

Post on 11-Jan-2017

231 views 4 download

Transcript of Kompletny przewodnik po SQL injection dla developerów PHP (i nie ...

Copyright © The OWASP FoundationPermission is granted to copy, distribute and/or modify this document under the terms of the OWASP License.

The OWASP Foundation

OWASP

http://www.owasp.org

Kompletny przewodnik po SQL injection dla developerów PHP (i nie tylko)

Krzysztof KotowiczPHP Developer

http://web.eskot.plMedycyna Praktycznakrzysztof@kotowicz.net10.03.2010

OWASP 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

OWASP 3

Omawiane bazy danych (RDBMS)

MySQL Oracle MS SQL Server W mniejszym stopniu:

• PostgreSQL• SQLite

OWASP 4

Omawiane projekty PHP PDO – PHP data objects

• 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

MDB2 2.4.1• Warstwa abstrakcji bazy danych (DBAL)• Dystrybuowany przez PEAR

OWASP 5

Co to jest SQL injection?

OWASP 6

SQL injection – krótka definicjaJest to rodzaj ataku na aplikacje internetowe.Polega na tym, że dane od użytkownika pochodzące z:

URL: www.example.com?id=1Formularzy: email=a@example.comInnych elementów: np. cookie, nagłówki HTTP

zostają zmanipulowane tak, że w podatnej aplikacji zostaje wykonane „wstrzyknięte” przez atakującego polecenie SQL.

OWASP 7

Przykład – formularz logowania

Użytkownik jest zalogowany bez znajomości loginu ani hasła

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 takSELECT * 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')

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}')

OWASP 8

Dlaczego jest groźne? DEMO

OWASP 9

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

OWASP 10

Kilka faktów

Podatności na injection na pierwszym miejscu OWASP Top 10 2010 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

OWASP 11

Jak się bronić?

OWASP 12

Jak się bronić przed SQL injection?

Źródło podatności - łączenie kodu z danymi

Metody obronyOddzielenie kodu od danych

prepared statementsstored procedures

Escape'owanie danych

SELECT * FROM users WHERE login = 'login'

OWASP 13

Jak się bronić?Prepared statements

OWASP 14

Prepared statements – zasada działania

1. Przygotowujemy polecenie SQL (string) W miejsce danych wstawiamy znaczniki

2. Przesyłamy polecenie na serwer3. Podajemy zestaw danych do polecenia4. Wykonujemy polecenie5. Odbieramy rezultat

3, 4, 5 można powtarzać...6. Czyścimy polecenie

WHERE a = ? ... WHERE a = :col

PREPARE

EXECUTE

OWASP 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

OWASP 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();}

OWASP 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!)

-- bladSELECT * FROM :tabelaSELECT :funkcja(:kolumna) FROM :widok

-- nie tego się spodziewacieSELECT * FROM tabela WHERE :kolumna = 1SELECT * FROM tabela GROUP BY :kolumna

OWASP 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();

OWASP 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']);

OWASP 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!');

OWASP 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'];

OWASP 22

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 numbersVALUES (:id, :name, :lang)', $types);

$data = array('id' => 1, 'name' => 'one', 'lang' => 'en');

$affectedRows = $stmt->execute($data);$stmt->free();

OWASP 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ń

OWASP 24

Jak się bronić?Escape'owanie danych

OWASP 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 ...

OWASP 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);

OWASP

NIE

OWASP 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 MySQLRBDMS 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 mam \'apostrofy\'

Oracle (oci8) n/d - str_replace() mam ''apostrofy''

SQLite sqlite_escape_string mam ''apostrofy''

MS SQL (mssql) n/d - str_replace() mam ''apostrofy''

PostgreSQL pg_escape_string() mam ''apostrofy''

OWASP 29

Escape'owanie – kontekst cd.

Nie używaj addslashes(), używaj funkcji konkretnej bazy

Czy teraz jesteś bezpieczny?

// 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:SELECT * FROM users WHERE username = 'cokolwiek\'' AND password = ' or 1=1 -- '

OWASP

PRAWIE

OWASP 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 ¿'

OWASP 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

OWASP 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)

OWASP 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()) { ...}

OWASP 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);

OWASP

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

36

$quoted = $pdo->quote($input, PDO::PARAM_STR, 40);

OWASP 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 -- '

OWASP 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);

OWASP 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');

OWASP 40

Escape'owanie w MDB

// 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);

Funkcja quote()

OWASP 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

OWASP 42

Jak się bronić?Procedury składowane

OWASP 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

OWASP 44

Procedury składowane

Przykład w MS SQL – fragment podatnej procedury

To eval() w kolejnym wcieleniu!

CREATE PROCEDURE SP_ProductSearch@prodname varchar(400)

AS DECLARE @sql nvarchar(4000) SELECT @sql = 'SELECT ProductID, ProductName, Category, Price FROM Product Where ProductName LIKE ''' + @prodname + '''' EXEC (@sql) ...

OWASP 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;

OWASP 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

OWASP 47

Procedury składowane w MS SQL

Oddzielenie danych od kodu• użyj sp_executesql razem z listą

parametrówCREATE PROCEDURE SP_ProductSearch @prodname varchar(400) = NULL ASDECLARE @sql nvarchar(4000)SELECT @sql = N'SELECT ProductID, ProductName, Category, Price FROM Product Where ProductName LIKE @p'EXEC sp_executesql @sql, N'@p varchar(400)', @prodname

OWASP 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 QUOTENAME(@v)

Tekst <= 128 znaków QUOTENAME(@v,'''')

Tekst > 128 znaków REPLACE(@v,'''','''''')

SET @cmd = N'select * from authors where lname=''' +REPLACE(@lname, '''', '''''') + N''''

OWASP 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;

OWASP 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

OWASP 51

Procedury składowane w MySQL cd.

PREPARE / EXECUTE USING / DEALLOCATE PREPARE

DELIMITER $$CREATE PROCEDURE get_users_like ( IN contains VARCHAR(40)) BEGIN SET @like = CONCAT("%", contains, "%"); SET @sql = "SELECT * FROM users WHERE uname LIKE ?"; PREPARE get_users_stmt from @sql; EXECUTE get_users_stmt USING @like; DEALLOCATE PREPARE get_users_stmt;END$$DELIMITER ;

OWASP 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 SET @like = CONCAT("%", contains, "%"); SELECT * FROM users WHERE uname LIKE @like;END$$DELIMITER ;

OWASP 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

OWASP 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);

OWASP 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();

OWASP 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());

}

OWASP 57

Procedury składowane - pułapki

Długość zmiennychCREATE PROCEDURE change_password

@loginname varchar(50),@old varchar(50),@new varchar(50)

AS DECLARE @command varchar(120) SET @command= 'UPDATE users SET password=' + QUOTENAME(@new, '''') + ' WHERE loginname=' + QUOTENAME(@loginname, '''') + ' AND password=' + QUOTENAME(@old, '''') EXEC (@command)GO

OWASP 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

OWASP 59

Procedury składowane - podsumowanieMają 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

SQLinjection, ale przy dużych kosztach.

Niezbędne jest zabezpieczanie samego kodu procedur przed SQL injection.

OWASP 60

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

OWASP 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]

OWASP 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

OWASP 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

OWASP 64

Linki Omawiane projekty

• sqlmap.sourceforge.net• php.net/manual/en/book.pdo.php• www.doctrine-project.org• propel.phpdb.org/trac• framework.zend.com• pear.php.net/package/MDB2

O SQL injection• www.owasp.org/index.php/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

krzysztof@kotowicz.net http://blog.kotowicz.net