Sanitize search query with php - php

When a user searches on my site, I grab the get request and do a mysql query against it. My original code looked something like this: $q = $_GET['q'];.
Right now urldecode is adding slashes for me, but I decided to aslo try filter_var with the santize string filter.
Here is my sql when I use $q = urldecode($_GET['q']);:
SELECT * FROM item WHERE title LIKE '%you\'re%' OR description LIKE '%you\'re%' ORDER BY date DESC
and here is my sql when I use: q = filter_var(urldecode($_GET['q']), FILTER_SANITIZE_STRING);
SELECT * FROM item WHERE title LIKE '%you\'re%' OR description LIKE '%you\'re%' ORDER BY date DESC
The sql is exactly the same, but I get different results and I'm not sure why? Just using urldecode returns the correct results from the database, but filter_var returns nothing (even though the sql is the same).
I guess my question is, is what's the best way to sanitize and search query string?

Urldecode is the wrong function to use - PHP will automatically decode any variables in $_GET so you don't need to, and the PHP Manual says doing so is dangerous.
Often people talk about sanitizing input, but I prefer to think about sanitizing output.
For example, sanitizing input would be:
$q = urldecode($_GET['q']);
$sql = "SELECT * FROM item WHERE title LIKE '%{$q}%'"
// later
echo "These items match '$q'";
And sanitizing output:
$sql = "SELECT * FROM item WHERE title LIKE '%".mysql_real_escape_string($_GET['q'])."%'"
// later
echo "These items match '".htmlspecialchars($_GET['q']).'";
Notice how in the latter example I've used different functions - one for converting the data into a mysql safe format, the other for converting the data into an HTML safe format. You can't know which function you want to run until you know what you're doing with the data.
Others have mentioned parameterised queries. Yes, these are about as secure as you can get and avoid accidental errors, but are not easy to switch to overnight.

Don't try to sanitize your data.
Use parametrized queries.
See http://bobby-tables.com/php.html for examples.

I would do:
$q = mysql_real_escape_string( stripslashes( $_GET['q'] ) );

The best variant is to use php Sanitize filters http://php.net/manual/en/filter.filters.sanitize.php,

Related

how to create a dynamic query using 'IN' in mysql

I want to pass a string that contains many usernames, seperated by a comma,and want to pass it to query
$name_list='kesong,nicholas,jane'; //this value would change
$select_occupy=mysql_query("SELECT * FROM user_data WHERE `username` IN('$name_list') ORDER BY `occupy_date` ");// i think this would just search for a username which is 'kesong,nicholas,jane', instead searching them seperately
echo mysql_num_rows($select_occupy); //would echo 0
I know it works only when you specify like IN('$name1','$name2','$name3'), but I need a dynamic one
As a suggestion, just hold your names into an array and do like below:
$names=array('name1','name2','name3');
$namesToCheck="";
foreach($names as $name){
$namesToCheck.="'$name',";
}
//to remove the last ,
$namesToCheck=substr($namesToCheck,0,-1);
Now, you can put $namesToCheck into your IN query.
EDIT:
In this answer, this is assumed that you will prevent any possible SQL injections as current answer is just an idea about your question. The minimum suggestion to perform preventing SQL injections would be using mysql_real_escape_string function, which escapes special characters in a string for use in an SQL statement. For example:
$namesToCheck.="'".mysql_real_escape_string($name)."',";
//OR DO THIS ON FINAL STRING
NOTE THAT This extension is deprecated as of PHP 5.5.0. You can take a look at the PHP's official document in the following link:
mysql_real_escape_string
You can do it like this :
$namesToCheck = "'" .implode( "','" ,explode( ',' ,$name_list ) ) ."'";
And then use the $namesToCheck in the IN clause of your query .
The above code would convert :
kesong,nicholas,jane
to :
'kesong','nicholas','jane'

Escaping quotes and percentage sign in SQL

I checked similar questions but couldn't find any solution to my particular problem. I have a PHP method that I use as follows:
SELECT * FROM login WHERE userID = 10 //To get this
$result = query("SELECT * FROM login WHERE userID = '%d' ", $userID) //I use this
so the character set '%d' is replaced by what I post in the $userID and the result is returned as JSON. Now i am trying to use it for a search function using.
select * from login where userName like '%searchString%' //Now to get this
$result = query("SELECT * FROM login WHERE userName LIKE '%'%s'%'", $username) // I am trying this
However I got error probably due to not escaping strings properly. Is it possible for any of you to solve this with given information?
Thanks
arda
You also need to change the where clause to use LIKE instead of =
$result = query("select * from login where userName like '%%s%'", $username)
I'm assuming your query method will search/replace the %s with the value of $username.One thing to be mindful is that using "select *" results in an inefficient query execution plan, you should change the * to a list of the columns from the table you want to retrieve. Also, be mindful of SQL injection attacks. See this link http://en.wikipedia.org/wiki/SQL_injection.
you may try by changing this '%'%s'%'
select * from login where userName like '%searchString%' //Now to get this
$username=mysql_real_escape_string($username);
$result = query("SELECT * FROM login WHERE userName = '%%s%'", $username) // I am trying this
I found the solution to be easier than I thought. I simply passed %searchString% as an argument instead of plain searchString
Escaping quotes and escaping percentage signs are two different matters.
First the quotes. The bad way is to "quote the quotes", ie replace all single quotes with two single quotes. It works, but there are disadvantages. The better way is to use query parameters. I don't work with php so I don't know all the details, but I read a lot of comments and answers here on StackOverflow telling php users to use prepared statements. They may or may not escape quotes. My guess is that they do.
For percentage signs, you have to surround them with square brackets to keep them from being treated as wild cards. For example, if your where clause is:
where somefield like '75%'
and you want it to return
75% of bus passengers like singing
but not return
75 bottles of beer on the wall
then your where clause has to be:
where somefield like '75[%]%'

UPDATE only provided fields in MySQL table using PHP

I have a user table with and id field and 10 other fields storing user details of various types that the user can change via various web forms. I want to have a PHP script that gets POSTed changed values for some subset of these fields, and UPDATEs only those fields that are received in the POST data. I'm finding this surprisingly difficult to do in a way that doesn't suck. We use mysqli for all database interaction in the rest of this application so mysqli-based solutions are strongly preferred.
The options I've considered and dismissed so far:
1) Run a separate UPDATE query for every field provided in the POST data - yuck, I don't want to hit the database up to 10 times for something that could be done in one query.
2) Have a dictionary mapping field names to the fields' data types, and iteratively construct the query by looping through the provided fields, checking whether they are text fields or not, calling mysqli_real_escape_string on the string fields and otherwise sanitizing the others (e.g. by type checking or sprintf with '%i' placeholders). - Yuck! I could probably safely do things this way if I was careful, but I don't want to make a habit of using this kind of approach because if I'm careless I'll leave myself open to SQL injection. Parameterized queries don't give me the potential to screw up dangerously, but this approach does. My ideal is to never concatenate any data into an SQL query manually and always rely upon parameterized queries; the database libraries of other languages, like Python, let me easily do this.
3) Use a parameterized query - this is my ideal for everything, since as long as I insert all externally-provided data into my query via the bind_param method of a mysqli statement object, I'm immune to SQL injection and don't have to worry about sanitization, but using parameterized queries seems to be impossible here. The trouble is that bind_param requires that the data be passed as variables, since all arguments after the first are passed by reference. I can reasonably elegantly iteratively construct a query with ? placeholders, and while I'm at it construct the string of types that gets passed as the first argument to bind_param ('ssiddsi' etc.), but then there's no way I can see to choose at runtime which of my 10 fields I pass to bind_params (unless I have a switch statement with 10^2 cases).
Is there some PHP language construct I'm missing (something similar to array unpacking) that will allow me to choose at runtime which variables to pass as arguments to bind_param? Or is there some other approach I haven't considered that will let me solve this simple problem cleanly and safely?
You can easily combine 2 and 3 by means of my SafeMySQL library.
The code will look like
$allowed = array('title','url','body','rating','term','type');
$data = $db->filterArray($_POST,$allowed);
$sql = "UPDATE table SET ?u WHERE id=?i";
$db->query($sql, $data, $_POST['id']);
note that $allowed array doesn't make all these fields necessarily updated - it just filters POST fields out. So, even $_POST with only id and url would be correctly updated.
Nevertheless, using prepared statements, although toilsome, also quite possible.
See the code below
public function update($data, $table, $where) {
$data_str = '' ;
foreach ($data as $column => $value) {
//append comma each time after first item
if (!empty($data_str)) $data_str .= ', ' ;
$data_str .= "$column = $value" ;
}
$sql = "UPDATE $table SET $data_str WHERE $where";
mysqli_query($sql) or die(mysqli_error());
return true;
}
$data is an array, in your case it's $_POST.
If you want to be more specific about the data to be saved from $_POST array, you can define an array of allowed columns. For example,
$allowed = array('id', 'username', 'email', 'password');
By doing this, you can filter your $_POST array and pass it to update() function.

mysql_real_escape_string again if copying data?

Before I put data into my database I pass it through mysql_real_escape_string.
If I want to copy that same data into another table, do I need to pass it through mysql_real_escape_string again before I copy it?
I wrote a small script to test the issue and it looks like the answer is yes:
$db = new AQLDatabase();
$db->connect();
$title = "imran's color";
$title = mysql_real_escape_string($title);
$sql = "insert into tags (title, color) values ('".$title."','#32324')";
$db->executeSQL($sql);
$sql = "select * from tags where color = '#32324' ";
$result = $db->executeSQL($sql);
while($row= mysql_fetch_array($result))
{
$new_title = $row['title'];
}
$new_title = mysql_real_escape_string($new_title);
$sql = "insert into tags (title, color) values ('".$new_title."','DDDDD')";
$db->executeSQL($sql);
NOTE: If I remove the second mysql_real_escape_string call, then the second insert won't take place
Are doing something like this?
save mysql_real_escape_string($bla) to database
fetch $bla from database
save $bla again (in another table..)
Fetching $bla from the database will "unescape" it so it could be a harmful string again. Always escape it again when saving it.
Before I put data into my database I always make it go the Mysql_real_Escape_String thing.
You are doing right. Just keep it as is. Not database though but query it is.
The only note: only strings should be escaped using this function. It shouldn't be used with any other query parts.
do I need to make it go through the Mysql_real_Escape_String again before I copy it?
Didn't you answer your question already? Before I put [string-type] data into my [query] I always make it go the Mysql_real_Escape_String thing. Is your data going to SQL query? So, here is an answer you have already.
Well, if you are sure this data is already properly escaped, there is no need to.
mysql_real_escape_string is for 1) escaping 2) security purposes. Since it's your own data base and as long as you pass data to another database outside a potential hacker reach - you are already safe
Its already scaped, just copy it as is, if you want to undo the mysql_real_escape_string you can use stripslashes($sting) to remove it
PD: This is false and now i understand why.

does PHP mysql_real_escape_string() protect database name?

I know that mysql_real_escape_string()
prepends backslashes to the following characters: \x00, \n, \r, \, ', " and \x1a
I know how this protects a query from injection into something like a variable in a where clause. But here's a scenario I am unsure of:
$query = "SELECT * FROM $db WHERE 1";
If $db is taken from a user input, then the user could insert something like:
$db = 'RealDatabase WHERE 1; DELETE FROM RealDatabase WHERE 1; SELECT FROM RealDatabase';
From my understanding, mysql_real_escape_string() would not affect this string,
making the final query:
$query = "SELECT * FROM RealDatabase WHERE 1; DELETE FROM RealDatabase WHERE 1; SELECT FROM RealDatabase WHERE 1";
which would delete the database. Is there another level of protection I am unaware of?
The level of protection you are looking for is supplied by backticks:
"SELECT * FROM `$db` WHERE 1";
Backticks are used to qualify identifiers that could otherwise be ambiguous (ie. MySQL reserved words), and if you are accepting user input or have variably-named columns or databases, you absolutely should use backticks, or I can promise that you will run into trouble in the future. For example, what if you had a system where a temporary field name was created with some user input, only it turned out the field ended up being named update?
"SELECT field1,field2,update FROM table;"
It fails miserably. However:
"SELECT `field`,`field2`,`update` FROM table"
works just fine. (This is actually a real example from a system I worked on a few years ago that had this problem).
This solves your problem in terms of putting in bad SQL. For instance, the following query will simply return an "unknown column" error, where test; DROP TABLE test is the injected attack code:
"SELECT * FROM `test; DROP TABLE test`;"
Be careful though: SQL Injection is still possible with backticks!
For instance, if your $db variable contained data that had a backtick in it, you could still inject some SQL in the normal way. If you're using variable data for database and field names, you should strip it of all backticks before putting it into your statement, and then qualifying it with backticks once inside.
$db = str_replace('`','',$db);
$sql = "SELECT * FROM `$db` WHERE 1";
I utilize a database wrapper which has separate functions for sanitizing data and sanitizing database identifiers, and this is what the latter does :)
You should really look into binding your SQL queries.
This will protect you from basically all SQL injection. It boils down to this:
(taken from PHP.net)
$stmt = mssql_init('NewUserRecord');
// Bind the field names
mssql_bind($stmt, '#username', 'Kalle', SQLVARCHAR, false, false, 60);
// Execute
mssql_execute($stmt);
And PHP has support for binded queries on basically all databases. Oh and of course you should still sanitize all input & output(display).
More info:
- http://php.net/manual/en/function.mssql-bind.php
No, mysql_real_escape_string isn't going to help you here. The function is not context-sensitive (it can't be, because it doesn't HAVE any context), and this is a completely different threat model.
You need to go and validate that the table exists, without sending the user-inputted table name directly to the server. The best solution is to use a server-side array/look-up table containing the table names they are allowed to use. If they try to use something that's not in there, then don't let them.
If you really need ALL of the tables, then you can just ask the server "what tables do you have?" and run through it's output (optionally caching it for some period of time to prevent asking the server every time) - but chances are, eventually you'll have a table that you don't want then to poke around in, and then you need to use the array thing anyway, so just go ahead and do that.
Instead of inserting the database name in the get query you can make a separate table of database names and ids. Then append only the id to the query. Then you can look up the corresponding database name for that id and use that. You can then make sure that the id received is numeric (is_numeric) and you can also be certain that the user can only choose from the databases that are in your list.
(Additionally this will prevent users from finding out names of databases and possibly use them elsewhere in an SQL injection on your site.)
Using the first method you parse the database name before using it in your query and make sure it contains no spaces.
Since table names do not accept whitespace characters, just strip them out. That would make the above $DB RealDatabaseWHERE1;DELETEFROMRealDatabase..... Such would invalidate the query, but prevent the flaw.
If you want to prevent this kind of 'hackish' things, just do explode(' ', $db) then get the result array's [0]. That would get the first part (RealDatabase) and nothing else.
Its just best to use it any time that you have questionable data being used. If you are specifying the table yourself and there's no room for tampering, there's no need to escape it. If your users are deciding anything that could potentially get run as a query, escape it.
If you really really must use a get from the user (bad bad bad) for your database then use the following style of coding...
$realname = '';
switch ($_GET['dbname']){
case 'sometoken' : $realname = 'real_name'; break;
case 'sometoken1' : $realname = 'real_name1'; break;
case 'sometoken2' : $realname = 'real_name2'; break;
case 'sometoken3' : $realname = 'real_name3'; break;
case 'sometoken4' : $realname = 'real_name4'; break;
case default : die ('Cheeky!!!');
}
$query = "SELECT * FROM `{$realname}` WHERE 1";
or alternatively ...
$realname = $tablenames[$_GET['dbname']];
if (!$realname)
die ('Cheeky!!!');
Using these 2 ways or some similar coding will protect your input from unexpected values.
It also means the user never gets to see the real table or database names which they may be able to infer information from.
Make sure you check the content of $_GET['dbname'] to make sure it's valid first otherwise warnings will be issued.
I still say this is a very bad design, it is reminiscent of allowing users to provide a filename and passing that through to I/O functions without a check. It simply too unsafe to consider.
Security is too important to let laziness rule.

Categories