SQL Injection: complete walkthrough (not only) for PHP developers
Upcoming SlideShare
Loading in...5
×

Like this? Share it with your network

Share

SQL Injection: complete walkthrough (not only) for PHP developers

  • 25,149 views
Uploaded on

Learn what is SQL injection, how to use prepared statements, how to escape and write secure stored procedures. Many PHP projects are covered - PDO, Propel, Doctrine, Zend Framework and MDB2.......

Learn what is SQL injection, how to use prepared statements, how to escape and write secure stored procedures. Many PHP projects are covered - PDO, Propel, Doctrine, Zend Framework and MDB2. Multiple gotchas included.

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
  • good one
    Are you sure you want to
    Your message goes here
  • thank u
    Are you sure you want to
    Your message goes here
  • good
    Are you sure you want to
    Your message goes here
No Downloads

Views

Total Views
25,149
On Slideshare
20,418
From Embeds
4,731
Number of Embeds
27

Actions

Shares
Downloads
666
Comments
3
Likes
22

Embeds 4,731

http://php.dzone.com 2,089
http://blog.kotowicz.net 1,120
http://scipion.es 662
http://it-republik.de 362
http://entwickler.com 122
http://www.slideshare.net 90
http://www.sfexception.com 78
http://entwickler.de 54
http://www.wilsolutions.com.br 40
http://wissen 19
http://css.dzone.com 18
http://www.scoop.it 17
http://phpmagazin.de 9
http://localhost 8
https://www.linkedin.com 8
http://translate.googleusercontent.com 8
http://www.dinkeskaltim.com 7
http://sql.dzone.com 4
http://architects.dzone.com 4
http://wissen:8686 3
http://www.linkedin.com 3
http://twitter.com 1
https://twitter.com 1
http://sqlcon.net 1
http://www.developpez.net 1
http://translate.yandex.net 1
http://webcache.googleusercontent.com 1

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. SQL injection: complete walktrough (not only) for PHP developers Krzysztof Kotowicz PHP Developer http://web.eskot.pl OWASP Medycyna Praktyczna krzysztof@kotowicz.net 10.03.2010 Copyright © The OWASP Foundation Permission is granted to copy, distribute and/or modify this document under the terms of the OWASP License. The OWASP Foundation http://www.owasp.org
  • 2. Plan  What is SQL injection?  Why is it so dangerous (demo)?  How to defend? • Prepared statements • Escaping • Stored procedures • Additional methods  Summary OWASP 2
  • 3. Discussed databases (RDBMS)  MySQL  Oracle  MS SQL Server  To some extent: • PostgreSQL • SQLite OWASP 3
  • 4. Discussed PHP projects  PDO – PHP data objects • Common interface for various RDBMS  Doctrine 1.2 • ORM (Object Relational Mapper) used e.g. in Symfony framework  Propel 1.4 • ORM, like Doctrine • Used in Symfony  Zend Framework 1.10 • Popular framework MVC for PHP  MDB2 2.4.1 • Database abstraction layer (DBAL) • Distributed through PEAR OWASP 4
  • 5. What is SQL injection? OWASP 5
  • 6. SQL injection – short definition It is a kind of web application attack, where user- supplied input coming from: URL: www.example.com?id=1 Forms: email=a@example.com Other elements: e.g. cookie, HTTP headers is manipulated so that vulnerable application executes SQL commands injected by attacker. OWASP 6
  • 7. Example – login form SELECT * FROM users WHERE login = '{$login}' and password_hash = MD5('{$password}') $login = "' or 1=1 -- "; "anything"; $password = "dowolne"; // zamierzalismy osiagnacthis(kod dane) you wanted to achieve to (code data) SELECT * FROM users WHERE login = '' or 1=1 -- ' and password_hash = MD5('dowolne') MD5('anything') // but server interprets it as SELECT * FROM users WHERE login = '' or 1=1 -- ' and password_hash = MD5('anything') User logs in without knowing the login nor password OWASP 7
  • 8. Why is it so dangerous? DEMO OWASP 8
  • 9. What are the possible threats?  Unauthorized access to application  Access to whole database / databases on the server  Denial of service  Database modification  Read / write files on server's filesystem  Code execution OWASP 9
  • 10. A few facts  Injection vulnerabilities are the 1st on OWASP Top 10 2010 RC  SQLi is responsible for 40–60% cases of data breach [1] [2]  Modern attack techniques are advanced and automated • Vulnerability is not only in WHERE part • Sometimes it is enough to break a query  Vulnerabilities are found on a daily basis, even in new applications OWASP 10
  • 11. How to defend? OWASP 11
  • 12. How to defend against SQL injection?  Source of vulnerability is mixing code with data SELECT * FROM users WHERE login = 'login' Defense methods Separating code from data prepared statements stored procedures Escaping OWASP 12
  • 13. How to defend? Prepared statements OWASP 13
  • 14. Prepared statements – how to use? 1. Preparing SQL command (string) Put placeholders where data should be WHERE a = ? ... WHERE a = :col 2. Send command to server PREPARE 3. Attach data to command 4. Execute command EXECUTE 5. Fetch results 3, 4, 5 could be repeated... 6. Clear the command OWASP 14
  • 15. Prepared statements - example  PDO // prepare command $stmt = $dbh->prepare("INSERT INTO SUMMARIES (name, sum) VALUES (:name, :sum)"); // attach data variables - WITH ITS TYPES! $stmt->bindParam(':name', $name, PDO::PARAM_STR); $stmt->bindParam(':sum', $sum, PDO::PARAM_INT); // bind values $name = 'something'; $value = 1234; // execute command $stmt->execute(); $stmt = null; //free memory OWASP 15
  • 16. Prepared statements - advantages  Commands are completely separated from data they operate on  Injection is not possible  Command is compiled only once - potential speedup $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 16
  • 17. Prepared statements - caveats  Not all commands may be parametrised  You cannot put parameters everywhere -- error SELECT * FROM :table SELECT :function(:column) FROM :view -- not what you expect SELECT * FROM table WHERE :column = 1 SELECT * FROM table GROUP BY :column  Just using PS does not enforce using parameters in them  Sometimes they're emulated (it's a good thing!) OWASP 17
  • 18. Prepared statements in Doctrine  Uses PDO (emulated for Oracle) and prepared statements  Uses own DQL language instead of SQL $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 18
  • 19. Prepared statements in Doctrine cont.  It can still bite you $q = Doctrine_Query::create() ->update('Account') ->set('amount', 'amount + 200') ->where("id > {$_GET['id']}");  Correct this to: ->where("id > ?", (int) $_GET['id']);  NEVER put input data directly into SQL commands OWASP 19
  • 20. Prepared statements in Propel  Uses PDO, like Doctrine // through Criteria $c = new Criteria(); $c->add(AuthorPeer::FIRST_NAME, "Karl"); $authors = AuthorPeer::doSelect($c); // through custom SQL (sometimes it's more convenient) $pdo = Propel::getConnection(BookPeer::DATABASE_NAME); $sql = "SELECT * FROM complicated_sql JOIN some_big_join USING something WHERE column = :col)”; $stmt = $pdo->prepare($sql); $stmt->execute(array('col' => 'Bye bye SQLi!'); OWASP 20
  • 21. Prepared statements in 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 in one step $stmt = $db->query('SELECT * FROM bugs WHERE reported_by = ? AND bug_status = ?', array('goofy', 'FIXED')); while ($row = $stmt->fetch()) echo $row['bug_description']; OWASP 21
  • 22. Prepared statements in MDB2  Based on different database drivers (mysql, oci8, mssql, ...)  Emulates PS, if database doesn't support them $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(); OWASP 22
  • 23. Prepared statements - summary  They offer very good protection (if used properly)  Easy to use, small changes in code  Good support in frameworks  They have their limits  Sometimes they have to be used with other defense methods OWASP 23
  • 24. How to defend? Escaping data OWASP 24
  • 25. Escaping – how does it work?  Data and commands are still kept in a single variable, but we try to separate them inline  Numbers • Cast to (int) / (float) – don't use is_numeric [1]!  Texts are surrounded with single quotes : ' .. WHERE col = 'TEXT DATA' AND ... • If quote is inside the text, you need a way to distinguish it from the ending quote • Prepend a special character e.g. "" to a quote • Escaping rules depend on context! OWASP 25
  • 26. Escaping – context 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). / Source: php.net manual / $user = addslashes($_GET['u']); $pass = addslashes($_GET['p']); $sql = "SELECT * FROM users WHERE username = '{$user}' AND password = '{$pass}'"; $ret = exec_sql($sql);  Are you safe? OWASP 26
  • 27. NO OWASP
  • 28. Escaping – context cont.  Different RDBMS have different ways of escaping data (it also depends on configuration)  addslashes() works just like MySQL only „by chance” RBDMS PHP function i've got quotes PDO $pdo->quote($val, $type) n/a (it depends) MySQL (mysql) mysql_real_escape_string i've got quotes MySQL (mysqli) mysqli_real_escape_string i've got quotes Oracle (oci8) n/d - str_replace() i''ve got quotes SQLite sqlite_escape_string i''ve got quotes MS SQL (mssql) n/d - str_replace() i''ve got quotes PostgreSQL pg_escape_string() i''ve got quotes OWASP 28
  • 29. Escaping – context cont. // SELECT * FROM users WHERE username = // '{$user}' AND password = '{$pass}' $_GET['u'] = "anything'"; $_GET['p'] = " or 1=1 -- "; // MySQL sees it as : SELECT * FROM users WHERE username = 'anything'' AND password = ' or 1=1 -- ' // SQLite / MS SQL / Oracle / PostgreSQL: SELECT * FROM users WHERE username = 'anything'' AND password = ' or 1=1 -- '  Don't use addslashes(), use PHP functions for your RBDMS  Are you safe now? OWASP 29
  • 30. ALMOST OWASP
  • 31. Escaping gotchas – charsets  Errors discovered in 2006 in PostgreSQL and MySQL [1] [2]  In some multibyte charsets despite escaping you can cause SQL injection  is „swallowed” by multibyte character  Example: • BF 27 [ ¬ ' ]  BF 5C 27 [ ¬ ' ] • First 2 bytes are character ¿ in GBK charset • Server will see ¿' OWASP 31
  • 32. Escaping gotchas – charsets  Some Asian charsets are vulnerable  Luckily - not UTF-8!  In PostgreSQL '' escaping was used (instead of ')  In mysql_real_escape_string() escaping is done with respect to current connection charset • Doesn't always work! [1] [2]  Charset also defines context OWASP 32
  • 33. Escaping gotchas – object names  Colum, table, database etc. names • No common good rule to escape them • Different reserved words, different maximum name lengths etc. If you need to get those names from the user - use whitelisting (blacklisting if you really can't do otherwise) OWASP 33
  • 34. Escaping gotchas – object names cont.  Example - sorting by column  There's a vuln. in $order, but you can't escape there $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 34
  • 35. Escaping gotchas – object names cont.  Whitelisting $columns = array( // list of allowed columns 'product_name','cid','price', ); if (!in_array($order, $columns, true)) $order = 'product_name'; // default column  Blacklisting // only a-z and _ $order = preg_replace('/[^a-z_]/', '', $order); // max 40 characters $order = substr($order, 0, 40); OWASP 35
  • 36. Escaping in PDO  PDO::quote($value, $type, $len)  Length and type are sometimes ignored! • Cast numbers to (int), (float) • Texts – cut them manually $quoted = $pdo->quote($input, PDO::PARAM_STR, 40); OWASP 36
  • 37. Escaping in Doctrine  Careful with Doctrine quote()! $q = Doctrine_Query::create(); // not like this!!! $quoted = $q->getConnection()->quote($input, 'text'); $q->update('User')->set('username', $quoted); // quote() only changes ' to '' - exploit (MySQL): $input = 'anything' where 1=1 -- '; // escape through PDO - getDbh(): $quoted = $q->getConnection() ->getDbh() ->quote($input, PDO::PARAM_STR); // 'anything ' where 1=1 -- ' OWASP 37
  • 38. Escaping in Propel  Through PDO::quote() $pdo = Propel::getConnection(UserPeer::DATABASE_NAME); $c = new Criteria(); $c->add(UserPeer::PASSWORD, "MD5(".UserPeer::PASSWORD.") " ." = " . $pdo->quote($password), Criteria::CUSTOM); OWASP 38
  • 39. Escaping in Zend Framework  Functions quote(), quoteInto() $name = $db->quote("O'Reilly"); // 'O'Reilly' // simplified escaping for a single value $sql = $db->quoteInto("SELECT * FROM products WHERE product_name = ?", 'any string'); OWASP 39
  • 40. Escaping in MDB  quote() // quote() function - give type $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); OWASP 40
  • 41. Escaping - summary  Looks easy - search and replace  Just looks • You need to know the context (database, charset) • There are invalid implementations  Encourages invalid practices • concatenating strings to form a SQL command • ignoring numeric parameters  Use only if • You program for a single RDBMS • There is no other way OWASP 41
  • 42. How to defend? Stored procedures OWASP 42
  • 43. Stored procedures  SQL command(s) is moved to database server and stored there under a name  Client executes a procedure with input and output parameters  In output parameters client receives results  Data is formally separated from code  It's NOT enough OWASP 43
  • 44. Stored procedures cont.  Example for MS SQL – a vulnerable procedure 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) ...  It's just like eval()! OWASP 44
  • 45. Stored procedures cont.  Same vulnerability in 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 45
  • 46. Stored procedures – Dynamic SQL  Vulnerability lies in Dynamic SQL • Data is again mixed with code in one variable  How to defend? • Separate the code from data • Escape OWASP 46
  • 47. Stored procedures in MS SQL  Separating code from data • use sp_executesql with parameter list CREATE PROCEDURE SP_ProductSearch @prodname varchar(400) = NULL AS DECLARE @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 47
  • 48. Stored procedures in MS SQL cont.  Escaping character data Object name QUOTENAME(@v) Text <= 128 chars QUOTENAME(@v,'''') Text > 128 chars REPLACE(@v,'''','''''')  Example: SET @cmd = N'select * from authors where lname=''' + REPLACE(@lname, '''', '''''') + N''''  Escape only when you must! (use sp_executesql with parameters) OWASP 48
  • 49. Stored procedures in Oracle  Oracle - use EXECUTE IMMEDIATE .. USING 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;  Escaping - DBMS_ASSERT package OWASP 49
  • 50. Stored procedures in MySQL  Support for Dynamic SQL only through prepared statements  It's actually harder to make vulnerable procedure  Just use placeholders OWASP 50
  • 51. Stored procedures in MySQL cont.  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 51
  • 52. Stored procedures in MySQL cont.  Or, even simpler DELIMITER $$ CREATE PROCEDURE get_users_like ( IN contains VARCHAR(40)) BEGIN SET @like = CONCAT("%", contains, "%"); SELECT * FROM users WHERE uname LIKE @like; END$$ DELIMITER ;  Escaping – QUOTE() function OWASP 52
  • 53. Stored procedures in PHP  Different support level, depending on RDBMS  Common API (e.g. PDO) only for simple calls • No return from procedure • Returns scalar value in OUT parameter  Different API (or none at all) for advanced calls • e.g. cursors, fetching records sets  Almost no support in frameworks  Still some errors... OWASP 53
  • 54. Stored procedures in PDO  Calling a procedure // 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 54
  • 55. Stored procedures in Doctrine/Propel/Zend Framework  Doctrine - no support (use PDO) $pdo = Doctrine_Manager::connection()->getDbh();  Propel – likewise $pdo = Propel::getConnection(UserPeer::DATABASE_NAME);  Zend Framework – likewise $pdo = $db::getConnection(); OWASP 55
  • 56. Stored procedures in MDB2  You need to manually escape all parameters! $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 56
  • 57. Stored procedures - gotchas  Data length 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 57
  • 58. Stored procedures - summary  Moving SQL logic to server takes time  Code is not easily ported to other RDBMS  You need to use prepared statements or escaping to write safe stored procedures anyway  If done poorly, you're even more vulnerable • Both SP code and statement calling SP could be vulnerable • SP usually has greater permissions than code calling it  Bad support in PHP and frameworks OWASP 58
  • 59. Stored procedures - summary SPs have many advantages outside our scope  Could be used with different clients (Java/.NET + PHP)  Could have better berformance  and many more... Conclusion: You can write secure stored procedures, but they usually increase the application cost considerably It is vital to write stored procedures protected against SQL injection OWASP 59
  • 60. How to defend? Additional methods OWASP 60
  • 61. Validation and filtering  Validate all external data  Validate before processing  Filter INPUT - escape OUTPUT  Different validation rules for each parameter - check e.g. • Type • Scalar / array • Min / max values • Character data length! [1] OWASP 61
  • 62. Additional methods Complementary to all previously mentioned!  Principle of least privilege when connecting to DB  Removing unused functions, accounts, packages shipped with database  Routinely updating the system and database software  Correct PHP and database configuration • magic_quotes_* = false • display_errors = false  Good database design OWASP 62
  • 63. Summary  Pay attention to SQL injection - even a single mistake could cost you!  Prefer complete solutions - e.g. frameworks  Filter and validate all input data  Remeber about data types and lengths  Prefer whitelisting to blacklisting - the latter will fail one day!  Use prepared statements whenever you can  Try to avoid escaping  In stored procedures double check your Dynamic SQL OWASP 63
  • 64. Links  Discussed projects • 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  About 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 OWASP 64