Jump to content

Safe input using parameterized binding and escaping / other methods?


greenace92

Recommended Posts

This is always bothering me as I'm not sure if I am using safe/secure methods of accepting input.

 

All of my php-inserts are parameterized but I am wondering if I still should escape / use filter function.

 

Can someone clear this up?

 

I've looked at a few sites and a couple posts have said "Prepared statements only way to guarantee against SQL Injection." So if I use prepared statements, is that it then, am I okay? Or should I still perform the escaping/filtering?

 

Thanks for any help

 

http://stackoverflow.com/questions/2009910/basic-mysql-php-filtering

http://stackoverflow.com/questions/1290975/how-to-create-a-secure-mysql-prepared-statement-in-php

http://php.net/manual/de/ref.filter.php

http://stackoverflow.com/questions/129677/whats-the-best-method-for-sanitizing-user-input-with-php

 

Link to comment
Share on other sites

Hi,

 

prepared statements are the primary mechanism for making sure that input data cannot change the structure of a query. If used correctly, they reliably prevent SQL injection attacks as well as accidental syntax conflicts (like when you try to insert a single quote into a single-quoted string literal).Note that some database interfaces like PDO use “fake” prepared statements which do not provide this level of robustness. By default, calling PDO::prepare() and PDOStatement::execute() merely auto-escapes the input, inserts it into the query string and then sends the whole string to the database system. This is much less secure and has the exact same issues as manual escaping (see below). An actual prepared statement always consists of two separate steps: You send a query template to the database system, and then you pass specific data to this template and execute it. To enable this two-step process in PDO, you need to explicitly turn PDO::ATTR_EMULATE_PREPARES off.

 

As a secondary line of defense, you should validate the input data whenever possible. For example, use ctype_digit() to make sure that a variable only contains decimal digits. If it doesn't, reject the input and generate an appropriate error message.

 

Do not try to “fix” the input with the FILTER_SANITIZE_* constants or through type casting. This is extremely confusing and can cause severe problems. For example, let's say I ask you to delete the row with the ID “12a3”, which is clearly an incorrect request. It would be insane if you picked a different ID (like “12”) and deleted that row instead, because I never asked you to. Type casting can also lead to truncation bugs.

 

Escaping is a less secure alternative to prepared statements. They cannot be used together, because all the escape characters would literally be inserted into your database. The input doesn't run through any SQL parser (that's the whole point of prepared statements).

 

There are many problems associated with escaping, so it should generally we avoided:

  • Even the best developers forget to escape a value from time to time, or maybe they think it's safe when it isn't. This immediately leads to a potential SQL injection vulnerability.
  • Escaping isn't trivial. There are dozens of incorrect functions, and even if you've picked the right one, you may still screw up (e. g. mysql_real_escape_string() is useless without surrounding quotes).
  • Since escaping happens in the application rather than in the database system itself, there's always a certain risk that a seemingly safe query is misinterpreted and turned into an SQL injection by the database system.

The last problem is particularly nasty, because it can happen even when you think you've done everything correctly. For example: If the application uses backslash-escaping, it will produce queries like

SELECT username FROM users WHERE id = '123\'UNION SELECT password FROM users-- '

From the application's perspective, this is perfectly safe, because it has the following structure:

SELECT username FROM users WHERE id = <some string>

However, the database system may have disabled backslash-escaping, so it interprets the query like this:

SELECT username FROM users WHERE id = <some string> UNION SELECT password FROM users

That's a full-blown SQL injection which yields all password hashes stored in the database. The same can happen if the application doesn't use the same character encoding as the database.

 

If you do need to use escaping (e. g. on legacy systems), you have to be super-careful:

  • Escape all values, even if you think they're safe.
  • Only use mysql_real_escape_string(). Forget about addslashes() or anything like that.
  • The escaped string must be enclosed in quotes. Otherwise escaping is useless.
  • If you need to change the character encoding at runtime, use mysql_set_charset(). Do not run a SET NAMES query, because this can lead to an encoding mismatch between the database system and the application.

Long story short: Prepared statements are by far the most robust solution as long as you make sure they're not “emulated” in the application.

Edited by Jacques1
Link to comment
Share on other sites

  • 3 weeks later...

Oh man, Thank you very much Jacques1, bookmarked!

 

Going to implement these standards you mentioned for future projects.

 

How would I know if an input is not emulated?

 

I see this

 

 

PDO::ATTR_EMULATE_PREPARES off.

 

Will have to re-read thoroughly

Edited by greenace92
Link to comment
Share on other sites

MySQLi doesn't use emulation at all, PDO must be configured with PDO::ATTR_EMULATE_PREPARES set to false:

$databaseConnection = new PDO($dSN, DB_USER, DB_PASSWORD, [
    PDO::ATTR_EMULATE_PREPARES => false,    // disable emulation so that actual prepared statements are used
    ...
]);

A quick test is to prepare an invalid query. If emulation is used, nothing will happen, because the query template doesn't get processed at that point. If an actual prepared statement is used, then you'll get an error, because the query template is sent to the database system where it cannot be processed:

<?php

const DB_HOST = 'localhost';
const DB_USER = '...';
const DB_PASSWORD = '...';
const DB_NAME = '...';
const DB_CHARSET = 'UTF8';



$dSN = 'mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset='.DB_CHARSET;
$databaseConnection = new PDO($dSN, DB_USER, DB_PASSWORD, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,    // activate exceptions
]);

// preparing an invalid query *with* emulation: nothing happens
$databaseConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
$databaseConnection->prepare('THIS IS NO VALID SQL');

// preparing an invalid query *without* emulation: an exception is triggered
$databaseConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$databaseConnection->prepare('THIS IS NO VALID SQL');
Link to comment
Share on other sites

  • 3 weeks later...

No I agree, I will opt to learn PDO. I'm pretty early in the game and haven't done too much so it's good to switch to the more common practice.

 

I like the idea of switching databases I have read that Google for example is leaving SQL if I'm not mistaken.

 

Thanks for the great points.

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.