Post on 15-Jan-2015
description
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 definicja
Jest 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 serwer
3. Podajemy zestaw danych do polecenia
4. Wykonujemy polecenie
5. 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 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 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ść zmiennych
CREATE 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 - 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 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