Thursday, May 24, 2012

Best way to stop SQL Injection in PHP


So specifically in a mysql database, I understand that this is vulnerable to SQL injection . Take the following code and tell me what to do.




// connect to the mysql database

$unsafe_variable = $_POST["user-input"];

mysql_query("INSERT INTO table (column) VALUES ('" . $unsafe_variable . "')");

// disconnect from the mysql database


Source: Tips4all

15 comments:

  1. Use prepared statements and parameterized queries. These are SQL statements that are sent to and parsed by the database server separately from any parameters.

    If you use PHP Data Objects you can work with prepared statements like this:

    $preparedStatement = $db->prepare('SELECT * FROM employees WHERE name = :name');

    $preparedStatement->execute(array(':name' => $name));

    $rows = $preparedStatement->fetchAll();


    where $db is a PDO object. The mysqli class also provides parameterized queries.

    What happens is that the SQL statement you pass to prepare is parsed and compiled by the database server. By specifying parameters (either a ? or a named parameter like :name in the example above) you tell the database engine where you want to filter on. Then when you call execute the prepared statement is combined with the parameter values you specify.

    The important thing here is that the parameter values are combined with the compiled statement, not a SQL string. SQL injection works by tricking the script into including malicious strings when it creates SQL to send to the database. So by sending the actual SQL separately from the parameters you limit the risk of ending up with something you didn't intend. Any parameters you send when using a prepared statement will just be treated as strings (although the database engine may do some optimization so parameters may end up as numbers too, of course). In the example above, if the $name variable contains 'Sarah'; DELETE * FROM employees the result would simply be a search for the string "'Sarah'; DELETE * FROM employees", and you will not end up with an empty table.

    Another benefit with using prepared statements is that if you execute the same statement many times in the same session it will only be parsed and compiled once, giving you some speed gains.

    Oh, and since you asked about how to do it for an insert, here's an example:

    $preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

    $preparedStatement->execute(array(':column' => $unsafeValue));

    ReplyDelete
  2. I'd recommend using PDO (PHP Data Objects) to run parameterized SQL queries. Not only does this protect against SQL injection, it also speeds up queries. And by using PDO rather than mysql_, mysqli_, and pgsql_ functions, you make your app a little more abstracted from the database, in the rare occurence that you have to switch database providers.

    ReplyDelete
  3. Always use parameterized queries. Always. There is no way to get the bad guy's code into your SQL if you don't put it into the source code of the query.

    It may take some learning, but it is the only way to go.

    ReplyDelete
  4. Use PDO and prepared queries.

    ($conn is a PDO object)

    $stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
    $stmt->bindValue(':id', $id);
    $stmt->bindValue(':name', $name);
    $stmt->execute();

    ReplyDelete
  5. Whatever you do end up using, make sure that you check your input hasn't already been mangled by magic_quotes or some other well-meaning rubbish, and if necessary, run it through stripslashes or whatever to sanitise it.

    ReplyDelete
  6. Parametrized sql (check your sql provider) plus htmlentities

    ReplyDelete
  7. Every answer here covers only part of the problem.
    In fact, there are four different query parts which we can add to it dynamically:


    a string
    a number
    an identifier
    a syntax keyword.


    and prepared statements covers only 2 of them

    But sometimes we have to make our query even more dynamic, adding operators or identifiers as well.
    So, we will need different protection techniques.

    In general, such a protection approach is based on whitelisting.
    In this case every dynamic parameter should be hardcoded in your script and chosen from that set.
    For example, to do dynamic ordering:

    $orders = array("name","price","qty"); //field names
    $key = array_search($_GET['sort'],$orders)); // see if we have such a name
    $orderby = $orders[$key]; //if not, first one will be set automatically. smart enuf :)
    $query = "SELECT * FROM `table` ORDER BY $orderby"; //value is safe


    However, there is another way to secure identifiers - escaping. As long as you have an identifier quoted, you can escape backticks inside by doubling them.

    As a further step we can borrow a truly brilliant idea of using some placeholder (a proxy to represent the actual value in the query) from the prepared statements and invent a placeholder of another type - an identifier placeholder.

    So, to make long story short: it's a placeholder, not prepared statement can be considered as a silver bullet.

    So, a general recommendation may be phrased as
    As long as you are adding dynamic parts to the query using placeholders (and these placeholders properly processed of course), you can be sure that your query is safe.

    Still there is an issue with SQL syntax keywords (such as AND, DESC and such) but whitelisting seems the only approach in this case.

    ReplyDelete
  8. you could do something basic like this.

    $safe_variable = mysql_real_escape_string($_POST["user-input"]);
    mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");


    this won't solve every problem but its a very good stepping stone. i left out obvious items such as checking the variable's existance, format (numbers, letters, etc)

    ReplyDelete
  9. Injection Prevention - mysql_real_escape_string()

    PHP has a specially-made function to prevent these attacks. All you need to do is use the mouthful of a function mysql_real_escape_string.

    What mysql_real_escape_string does is take a string that is going to be used in a MySQL query and return the same string with all SQL Injection attempts safely escaped. Basically, it will replace those troublesome quotes(') a user might enter with a MySQL-safe substitute, an escaped quote \'.

    /NOTE: you must be connected to the database to use this function!
    // connect to MySQL

    $name_bad = "' OR 1'";

    $name_bad = mysql_real_escape_string($name_bad);

    $query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
    echo "Escaped Bad Injection: <br />" . $query_bad . "<br />";


    $name_evil = "'; DELETE FROM customers WHERE 1 or username = '";

    $name_evil = mysql_real_escape_string($name_evil);

    $query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
    echo "Escaped Evil Injection: <br />" . $query_evil;


    you can find more detail here

    http://www.tizag.com/mysqlTutorial/mysql-php-sql-injection.php

    ReplyDelete
  10. Parameterized query AND input validation is the way to go. There is many scenarios under which SQL injection may occur, even though mysql_real_escape_string() has been used.

    Those examples are vulnerable to SQL injection :

    $offset = isset($_GET['o']) ? $_GET['o'] : 0;
    $offset = mysql_real_escape_string($offset);
    RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");


    or

    $order = isset($_GET['o']) ? $_GET['o'] : 'userid';
    $order = mysql_real_escape_string($order);
    RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");


    In both case you can't use ' to protect the encapsulation.

    source : The Unexpected SQL Injection (When Escaping Is Not Enough)

    ReplyDelete
  11. Anyone suggesting anything besides prepared statements or parametrized queries deserves a downvote:

    Parametrized queries in mySQL:

    http://us.php.net/manual/en/mysqli.prepare.php

    ReplyDelete
  12. I favor stored procedures (mySQL has sp support since 5.0) from a security point of view - the advantages are -


    Most databases (including mySQL) enable user access to be restricted to executing stored procedures. The fine grained security access control is useful to prevent escalation of privileges attacks. This prevents compromised applications from being able to run SQL directly against the database.
    They abstract the raw SQL query from the application so less information of the db structure is available to the application. This makes it harder or people to understand the underlying structure of the database and design suitable attacks.
    They accept only parameters so the advantages of parameterized queries are there. of course - IMO you still need to sanitize your input - especially if you are using dynamic SQL inside the stored procedure.


    The disadvantages are -


    They (stored procedures) are tough to maintain and tend to multiply very quickly. This makes managing them an issue.
    They are not very suitable for dynamic queries - if they are built to accept dynamic code as parameters then a lot of the advantages are negated.

    ReplyDelete
  13. interger

    $num = (int)$num;


    character

    $str = htmlspecialchars($str, ENT_QUOTES, 'UTF-8'); // escape 5 chars ' " < > &
    if (!get_magic_quotes_gpc()) {
    $str = addslashes($str); // escape 4 chars ' '' \ \0
    }
    if ($type == 'sql') {
    $arr = array('_' => "\_", '%' => "\%");
    $str = strtr($str, $arr);
    }

    ReplyDelete
  14. I use a combination of sprintf and mysql_real_escape_string functions...

    For example, if I want to select a row using its id value:

    $query = "SELECT `name` FROM `names` WHERE `id` = %u";
    $query = sprintf($query, $id_val);


    that way I make sure only positive integers get through the query...

    And when I want to insert a string into the query I use "%s" instead of "%u" (you can find more about it on the PHP documentation I linked above) with a combination of mysql_real_escape_string.

    Although I'm sure it's not 100% safe, there is always a way to bypass checks... One thing that comes to mind is maybe using hex values?

    ReplyDelete
  15. I would say something like this in addition to mysql_real_escape_string():

    <?php
    // Quote variable to make safe
    function quote_smart($value)
    {
    // Stripslashes
    if (get_magic_quotes_gpc()) {
    $value = stripslashes($value);
    }
    // Quote if not integer
    if (!is_numeric($value)) {
    $value = "'" . mysql_real_escape_string($value) . "'";
    }
    return $value;
    }
    ?>

    ReplyDelete