You are on page 1of 3

MySQL security

The main security risk with PHP and MySQL working together is the user typing in something to a form which you did not want them to type.

One problem - quotes


Imagine a form with a text field for username. Instead of their username the user types in: x'; DELETE FROM firsttable WHERE username!=' Your PHP page would take the data from the POST global variable and might do something like this with it: $query="SELECT * FROM firsttable WHERE username='$username'"; $result=mysql_query($query); Unfortunately, to MySQL the $query= line would actually look like this once the data is there: $query="SELECT * FROM firsttable WHERE username='x'; DELETE FROM firsttable WHERE username!='';"; $result=mysql_query($query); This would be processed as two separate queries. The second would delete records. Using DROP you could do an even more thorough job of ruining someone's site and life. xkcd on SQL injection

Logging in with quotes


If the aim is to help yourself to data rather than crippling the site you might be able to get around this: $query="SELECT * FROM users WHERE username = '$username' AND password = '$password'"; by typing in this to the username and password boxes on the log in form: In the username field: ' OR 1=1 /* In the password field: */; When sent to the server the query will now look like this: SELECT * FROM users WHERE username = '' OR 1=1 /*' AND password = '*/;' All record will now be selected because of the added OR (the AND has been commented out).

A solution to quotes and more


Whenever data comes from the user you need to remove any risky characters (quotes mainly): $username=mysql_real_escape_string($_POST[username]); This will add escape characters (\) to every quote in the string. That stops anyone from adding extra SQL statements by closing your quotes early. Use this on any user-generated data which will then be used in a query.

A second problem - variables not in quotes


The function mysql_real_escape_string() will not help if this query is used: $userID=2; $query="SELECT bankaccountnumber FROM firsttable WHERE userID=$userID"; As the variable is holding a number it has not been put inside quotes in the query. Normally $userID would come from POST or GET therefore the user could type: 2523 or 1=1 and the query would always succeed in finding a match.

The second solution


Always put the variable in single quotes even when it is a number.

Passwords - hashing
If user passswords are stored in plain text on the database server then anyone with access (administrator or hacker) will have some valuable data. To prevent this passwords should not even be stored in the database. This might sound strange but MD5() is a built-in MySQL function which will take any string (e.g. a password) and make a mess of it! The mess it makes cannnot, in theory, be reassembled into the password. In practice the methods used to "mess up" the data can be cracked but it is hard to do. MD5 (or SHA1 which is the main alternative) are very similar to encryption but are designed to be one way. The way to use them is therefore: 1. 2. 3. 4. process the password with MD5 store the result and not the password when the user types in their password process that in the same way compare the processed result stored with the one type in and if they match the password was correct

The MD5 function is called within the query every time the password data is INSERTed: $insertquery="INSERT INTO users (username, password) VALUES ('$username', MD5('$password'))"; or used in a WHERE: $selectquery="SELECT bankaccountnumber FROM users WHERE username=$username AND password=MD5('$password'))"; The big advantage is that even the database administrator cannot access user passwords. MD5 is not perfect (there are ways to reverse both it and SHA1) and so extra precautions may be advisable. One is to combine the MD5 checksum for the password (the name for the result) with another MD5 checksum to provide something which is a mix of two hashes and therefore is much harder to crack. This is a slightly awkward way of implementing the concept of a "salt" (explained below) which is not supported by MD5.

Confidential information - encryption


It's best to assume that someone will eventually get into your database and see the data. If any of the data could be misused then it is best to encrypt it. Traditionally PASSWORD() or MD5() are used to obscure passwords but for normal data you need to be able to decrypt it so that it can be used. AES_ENCRYPT is claimed to be the most secure encryption available for MySQL: $insertquery="INSERT INTO firsttable (username, password) VALUES ('$username', AES_ENCRYPT('$password', 'blah'))"; $selectquery="SELECT bankaccountnumber FROM firsttable WHERE username=$username AND password=AES_ENCRYPT('$password', 'blah'))"; The data provided in the variables ($password in both) is encrypted using a "seed" or "salt" value (blah). The use of AES and a seed means that it is much harder to decrypt the data if it is accessed. It does take extra processing so should only be used on important or sensitive data. Encryption using AES_ENCRYPT but with a different seed would give a different result which is how the salt helps. AES_DECRYPT can extract the encrypted data given the seed so in that way this form of encryption is less secure by nature than the one-way "encryption" of MD5. The database administrator can decrypt any of the data which is not possible with MD5. However AES encryption is currently considered to be free of any of the flaws found in MD5 and so could only be broken using brute force methods which would take even the best equipped people unfeasibly long periods of time. If you can find a way to alter the salt for each user that will make the job even harder. Note that AES results need to be stored as binary and not text (use VARBINARY data type with a length of 16 characters more than the longest possible input). Note also that VARBINARY cannot be exported via a text file the way text fields can be.

AES for passwords


If you want to use AES for passwords it is vital that no one can decrypt the stored passwords. To do this just use the password as both the clear text input and the salt: $insertquery="INSERT INTO firsttable (username, password) VALUES ('$username', AES_ENCRYPT('$password', '$password'))"; $selectquery="SELECT bankaccountnumber FROM firsttable WHERE username=$username AND password=AES_ENCRYPT('$password', '$password'))"; Now the only person who could decrypt is the person who knows the password. This does mean that if the user forgets their password there is no way to recover it. That means it would be a bad idea to use the password to encrypt other data if the loss of that data would be a problem.

Conclusions
Always run POST or GET data through mysql_real_escape_string() and always put quotes around the variable inside the query. That makes you safe. Always hash passwords and encrypt data and then the data is safe. Copyright Martin Matthews 2010-2011, all rights reserved

You might also like