what is sql injection
SQL injection refers to the act of someone inserting a MySQL statement to be run on your database without your knowledge. Injection usually occurs when you ask a user for input, like their name, and instead of a name they give you a MySQL statement that you will unknowingly run on your database.
sql injection example
Below is a sample string that has been gathered from a normal user and a bad user trying to use SQL Injection. We asked the users for their login, which will be used to run a SELECT statement to get their information.
MySQL & PHP Code:
// a good user's name
$name = "timmy";
$query = "SELECT * FROM customers WHERE username = '$name'";
echo "Normal: " . $query . "
";
// user input that uses SQL Injection
$name_bad = "' OR 1'";
// our MySQL query builder, however, not a very safe one
$query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
// display what the new query will look like, with injection
echo "Injection: " . $query_bad;
Display:
Normal: SELECT * FROM customers WHERE username = 'timmy'
Injection: SELECT * FROM customers WHERE username = '' OR 1''
Injection: SELECT * FROM customers WHERE username = '' OR 1''
The normal query is no problem, as our MySQL statement will just select everything from customers that has a username equal to timmy.
However, the injection attack has actually made our query behave differently than we intended. By using a single quote (') they have ended the string part of our MySQL query
- username = ' '
and then added on to our WHERE statement with an OR clause of 1 (always true).
- username = ' ' OR 1
This OR clause of 1 will always be true and so every single entry in the "customers" table would be selected by this statement!
more serious sql injection attacks
Although the above example displayed a situation where an attacker could possibly get access to a lot of information they shouldn't have, the attacks can be a lot worse. For example an attacker could empty out a table by executing aDELETE statement.
MySQL & PHP Code:
$name_evil = "'; DELETE FROM customers WHERE 1 or username = '";
// our MySQL query builder really should check for injection
$query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
// the new evil injection query would include a DELETE statement
echo "Injection: " . $query_evil;
Display:
SELECT * FROM customers WHERE username = ' '; DELETE FROM customers WHERE 1 or username = ' '
If you were run this query, then the injected DELETE statement would completely empty your "customers" table. Now that you know this is a problem, how can you prevent it?
injection prevention - mysql_real_escape_string()
Lucky for you, this problem has been known for a while and 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 \'.
Lets try out this function on our two previous injection attacks and see how it works.
MySQL & PHP Code:
//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:
" . $query_bad . "
";
$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:
" . $query_evil;
Display:
Escaped Bad Injection:
SELECT * FROM customers WHERE username = '\' OR 1\''
Escaped Evil Injection:
SELECT * FROM customers WHERE username = '\'; DELETE FROM customers WHERE 1 or username = \''
SELECT * FROM customers WHERE username = '\' OR 1\''
Escaped Evil Injection:
SELECT * FROM customers WHERE username = '\'; DELETE FROM customers WHERE 1 or username = \''
Notice that those evil quotes have been escaped with a backslash \, preventing the injection attack. Now all these queries will do is try to find a username that is just completely ridiculous:
- Bad: \' OR 1\'
- Evil: \'; DELETE FROM customers WHERE 1 or username = \'
And I don't think we have to worry about those silly usernames getting access to our MySQL database. So please do use the handy mysql_real_escape_string()function to help prevent SQL Injection attacks on your websites. You have no excuse not to use it after reading this lesson!
function cleanQuery($string)
{
if(get_magic_quotes_gpc()) // prevents duplicate backslashes
{
$string = stripslashes($string);
}
if (phpversion() >= '4.3.0')
{
$string = mysql_real_escape_string($string);
}
else
{
$string = mysql_escape_string($string);
}
return $string;
}
// if you are using form data, use the function like this:
if (isset($_POST['itemID'])) $itemID = cleanQuery($_POST['itemID']);
// you can also filter the data as part of your query:
SELECT * FROM items WHERE itemID = '". cleanQuery($itemID)."' "
PHP Code:
$username = mysql_real_escape_string( $username );$password = mysql_real_escape_string( $password );
?>
This is intended as a brief guide to protecting your MySQL database from SQL injection attacks. Unfortunately, a large amount of the code that I've seen written by people on forums, and in countless crappy PHP tutorials lurking around on the net, and in the many websites that display the "magic breeding slashed-quote" show that many people just do not understand what's going on and how to protect themselves against SQL injection attacks.
This is intended as a brief guide to protecting your MySQL database from SQL injection attacks. Unfortunately, a large amount of the code that I've seen written by people on forums, and in countless crappy PHP tutorials lurking around on the net, and in the many websites that display the magic breeding slashed-quote (\' - see below) show that many people just do not understand what's going on and how to protect themselves against SQL injection attacks.
In fact, the only reason that many websites are "protected" is due to magic quotes, and given that this is due to be disabled in the forthcoming PHP6, then there's going to be some major problems cropping up.
I'll talk about the problem of SQL injection, the half-hearted attempt to fix it with these "magic quotes", and what you should really be doing EVERY TIME you send user inputted data to your database.
The Problem - What is SQL Injection:
As the name suggests, SQL Injection is quite simply, when the user injects SQL into your application. How does this happen? Say we have a nice simple login form that takes a username and password, and checks if that's in the database. If it is, the user is logged into an admin section or something. The code for this could look something like this:
PHP:
- // user and password come from a simple POST'ed form
- $user = $_POST[ 'user' ];
- $password = $_POST[ 'password' ];
- $query = "SELECT name, age, credit_card
- FROM usertable
- WHERE username = '$user' AND password = '$password' ";
- $result = mysql_query( $query );
- // check if mysql found anything, and get the record if it did
- if ( mysql_num_rows( $result )> 0 )
- {
- $data = mysql_fetch_assoc( $result );
- echo '
- Hello '.$user.'!
- ';
- echo '
- Your credit card number is '.$data[ 'credit_card' ].'
- ';
- }
- else
- {
- echo '
- Incorrect Username or Password! Go Away!
- ';
- }
- ?>
Ok. This works, BUT it's about as safe as juggling with scalpels. If I enter "simon" as my username, and "secret" as my password, then the query that goes to MySQL looks like this:
PHP:
- SELECT name, age, credit_card FROM usertable WHERE username = 'simon' AND password = 'secret'
and I get logged in quite happily. Fantastic.
The problem comes when I start entering other characters. Let's say that the next user who trys to login is Peter O'Reilly. Naturally he'll want a username something like PeterO'Reilly. If we plug that into our query we get this:
PHP:
- SELECT name, age, credit_card FROM usertable WHERE username = 'PeterO'Reilly' AND password = 'secret'
MySQL blasts along quite happily and hits username='PeterO', and then it gets this "Reilly" thing which it doesn't know what to do with and this happens:
PHP:
- ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'Reilly' and password="secret"; '' at line 1
Nice. We have a broken website, Pete can't login, and he's left with misgivings about our web programming skills.
Even worse - what happens if I enter my password as this?
PHP:
- ' or 1=1 ; --
The query that gets sent to MySQL will look like this:
PHP:
- SELECT name, age, credit_card FROM usertable WHERE username = 'simon' AND password = '' or 1=1 ; -- '
What does this mean? It tells MySQL to find all rows with a username equal to "simon" and a password equal to an empty string OR "1=1". To represent that a bit more logically:
( username = "simon" and password = "" ) || ( 1 = 1 )
Now, 1=1 is always going to be true, so this is equal to:
( false ) || ( true )
Which means that ALL the records in the table will get returned. Our login processer above is going to log me on with someone else's credentials - in fact, those of the first record returned.
Keep in mind, however, that we don't need to escape numbers, and we shouldn't put quote marks around them (it's not standard SQL)- if a variable is a number, then it'll be fine.
The crap attempt to fix it ( magic quotes ):
How to fix this? We need to be escape these quote characters ( both single and double quotes, as well as backslashes). This is done by putting a slash in front of them, e.g. so a ' becomes a \', and MySQL can work out that that quote mark is "protected" by the slash, and is part of the value and ignores it. So, Peter's attempt to login becomes:
PHP:
- SELECT name, age, credit_card FROM usertable WHERE username = 'PeterO\'Reilly' AND password = 'secret';
and my attempt to hack my way in becomes:
PHP:
- SELECT name, age, credit_card FROM usertable WHERE username = 'simon' AND password = '\' or 1=1 ; -- ';
MySQL now thinks my password is the string
PHP:
- ' or 1=1 ;
and I won't be able to login.
So where do we get these slashes? Since around PHP version 3.06, PHP tries to do this for you, with a setting called "magic_quotes" . What this does is to automatically add slashes to anything coming in via HTTP get or post requests and via cookies. You can also do this manually using the addslashes() function.
But do not rely on this!
- It could be turned off or on, or not present in your version (and it won't be in PHP6). Therefore YOU CANNOT RELY ON IT, AND HAVE TO HANDLE THIS YOURSELF.
- This means that if you rely on magic quotes, then your code is not portable. Unfortunately, escaping things with a slash is one of those irritating non-standard MySQL features. Most other databases which follow the SQL standards (like Postgres), escape things with another single quote ( O'Reilly => O''Reilly ).
- It's crappy. It doesn't work well with extended characters, and these can be used to get around the slashes. See Chris Shiflett's discussion of this problem
- It's irritating - it pollutes your data with stuff that the user didn't enter. This is the major cause of the magic breeding slashed-quote.
(Aside) The Magic Breeding Slashed-Quote:
I'm sure you've all seen websites that have this really annoying habit of messing up their user\'s post\'s quote marks ( just like that ). I'm calling this the magic breeding slashed quote, because these things propagate like crazy. What's happening here is that the hard working web developer is adding slashes to the data they send to their database - great! BUT, they're not checking for magic quotes, so PHP is escaping the ' once to \', and then when the website runs addslashes() again, php sees a backslash AND a single quote which need escaping ( remember that the three characters that get escaped are \, ', and " ). This therefore becomes \\\'. MySQL comes along and sees an escaped backslash AND an escaped single quote.
Here's what's happening:
- user input: O'Reilly
- magic quotes: O\'Reilly
- addslashes: O\\\'Reilly
- MySQL processes this to: O\'Reilly
and we end up with O\'Reilly stored when we really want O'Reilly.
I've actually seen applications which quite happily store O\'Reilly, andstripslashes() before they display the data - this is just blindingly stupid.
Fixing it.
So, we need a way of escaping data that isn't crappy, isn't as prone to character set issues, and isn't prone to magic breeding slashed quotes.
There are two ways to do this.
- Use better slashes ( PHP4, old mysql client library using the mysql_* functions )
- Use a better technique - bound parameters ( PHP5 with the new mysqli_* client library)
Using better slashes - mysql_real_escape_string( ) (PHP4, mysql_* )
If you're using the old mysql client library ( i.e. the mysql_* functions ), then you have to use the hideously named mysql_real_escape_string()function. This takes into account the character set of the database connection and should handle things appropriately.
Note: mysql_real_escape_string needs an active database connection, or anything sent to it will disappear ( WTF? ), or it will generate an error.
BUT we still need to check for the evil magic_quotes setting, which and remove it. We can do this with the get_magic_quotes_gpc() function ( "gpc" refers to Get, Post, and Cookies which magic quotes operates on ).
So - something like this:
PHP:
- // remove the pesky slashes from magic quotes if it's turned on
- function clean_string( $value, $DB )
- {
- if ( get_magic_quotes_gpc() )
- {
- $value = stripslashes( $value );
- }
- // escape things properly
- return mysql_real_escape_string( $value, $DB );
- }
- $string = "O'Reilly";
- // where $db is your active database connection resource id.
- $safe_string = clean_string( $string, $db );
There's a function described in the PHP manual called quote_smart, that handles this and handles both strings and integers:
PHP:
- // Quote variable to make safe
- function quote_smart($value)
- {
- // Stripslashes
- if ( get_magic_quotes_gpc() )
- {
- $value = stripslashes( $value );
- }
- // Quote if not a number or a numeric string
- if ( !is_numeric( $value ) )
- {
- $value = "'" . mysql_real_escape_string($value) . "'";
- }
- return $value;
- }
Note that you'll need to implement this yourself, and you'll have to rewrite your queries to not have quotes in them eg:
PHP:
- $variable = "O'Reilly";
- $variable = quote_smart( $variable );
- // note that we haven't surrounded $variable with quote marks in
- // the query below since quote_smart does that for us.
- $query = "SELECT x, y, z FROM tablename WHERE user = $variable";
However, this leaving quote marks out of the query irritates me enough, that I generally just type-cast anything which should be a number to a number:
PHP:
- function clean_int( $i )
- {
- if ( is_numeric( $i ) )
- {
- return ( int ) $i;
- }
- // return False if we don't get a number
- else
- {
- return False;
- }
- }
Warning:
This is NOT foolproof. In fact, if the attacker can change the character set on the fly, then this whole system can be avoided. Ilia Alshanetsky has an excellent write up on this.
Fixing it with better technique - bound parameters ( PHP5, MySQLi ):
So - the best solution? Use bound parameters. To use these you'll need to be using the improved mysqli library that comes with PHP5. This technique differs slightly in that you define a query "template" first with placeholders, and then "bind" the parameters to it, and the mysqli library takes care of the appropriate escaping for us:
PHP:
- $variable = "O'Reilly";
- // prepare the query
- $query = $mysqli->prepare( "SELECT x, y, z FROM tablename WHERE user = ?" );
- // bind a parameter - here the first parameter is a short string that specifies the type that the
- // subsequent arguments should be:
- // 's' means a string
- // 'd' means a double
- // 'i' means an integer
- // 'b' is a blob
- $query->bind_param( 's', $variable );
- // execute query:
- $query->execute( );
- // so if we had a more complex query, which updated the user info with
- // "favorite_color" (a string), "age" ( an integer ) and "description", a blob:
- $query = $mysqli->prepare( "UPDATE tablename SET favorite_color = ?, age = ?, description = ? WHERE user = ?" );
- // we would have a bind looking like this:
- $query->bind_param( 'sibs', 'red', 27, $some_blob, $variable );
- $query->execute();
Another benefit of this method is that it's faster to transfer data to the db server. Harrison Fisk has a good discussion of these here.
Another thing to keep in mind:
Now, properly using mysql_real_escape_string or prepared statements should keep you pretty safe, but there are a few characters you might also want to watch out for:
The Percentage Sign (%)
The percentage symbol is commonly used by MySQL to perform LIKE queries - this WON'T get escaped. If your application is doing LIKE comparisons, and your database is large, then it's worth checking for this specifically to avoid a friendly user entering "%" and making your database grind to a halt - e.g.
PHP:
- $user_input = '%';
- $query = "SELECT x,y,z FROM tablename WHERE user LIKE '%$user_input%';
- // becomes LIKE %%% -> and returns all rows in tablename.
Thanks for your wonderful help i get good idea using your code and tips....thanks once again....
ReplyDeleteThanks DataDiary..
ReplyDeleteThanks for Trac Project template.
ReplyDeleteInvoice Templates