I'm building a database class and thought it'd be a good idea to incorporate some form of SQL injection prevention (duh!). Here's the method that runs a database query:
class DB
{
var $db_host = 'localhost';
var $db_user = 'root';
var $db_passwd = '';
var $db_name = 'whatever';
function query($sql)
{
$this->result = mysql_query($sql, $this->link);
if(!$this->result)
{
$this->error(mysql_error());
} else {
return $this->result;
}
}
}
There's more in the class than that but I'm cutting it down just for this. The problem I'm facing is if I just use mysql_real_escape_string($sql, $this->link); then it escapes the entire query and leads to a SQL syntax error. How can I dynamically find the variables that need to be escaped? I want to avoid using mysql_real_escape_string() in my main code blocks, i'd rather have it in a function.
Thanks.
The problem is that by the time you have built an SQL query it is too late to be able to prevent injection by finding variables - otherwise it'd already be built into PHP.
The escaping needs to be done much earlier when you build the queries. You could use a query building class.
However I would recommend a different approach - which is to have a layer that provides a database table as an object, here is an example user object which is derived from the base db entity class which provides a complete interface to a database using active record pattern, and the iterator pattern.
I'll illustrate this with some examples; the neat thing here is iterators as you can abstract away a lot more and have some quite generic classes further on down the line to extract the data.
To create a user record using the above approach:
$user = new DbUser();
$user->create();
$user->set_email('test#example.com');
$user->write();
To read a user record:
$user = new DbUser();
$user->set_email('text#example.com');
if ($user->load_from_fields())
{
}
To iterate through records:
$user_iterator = DbUser::begin();
if ($user_iterator->begin())
{
do
{
$user = $user_iterator->current();
echo $user->get_email();
} while ($user_iterator->next());
}
There are two approaches to prevent an SQL injection attack: Blacklist based approach and Whitelist based approach.
Blacklist approach implies that you need to check the whole query string and identify unwanted code and remove it. This is hell lot of difficult.
Instead, use the white-list approach by using parametrized SQL. This way you will be sure that the only query that will be executed will be the one which you intentionally built using your code, and any injection attempt will fail as all the injection queries will be a part of parameter and hence wont be executed by the database.
You are trying to find a way of preventing the injection after the query has already been built up, which indirectly means a blacklist based approach.
Try using parametrized SQL in your code, its one of the secure coding principles adapted globally.
Either parametrized or try building the DB class in order to pass all the values for WHERE ( and whatever may use values ) using a something like :
$db->where(x,y);
ie.
$db->where('userid','22');
And in the class corpus use something like
function where(var x, var y) // method
{
$this->where .= x . ' = '.mysql_real_escape_string(y);
}
Of course this needs cleaning to support multiple WHERE inputs.
The whole point of code-injection prevention is that you want to differentiate between your sql and sql that users injected. You can't do that any more at this lowest level. Lets give an example:
select * from users where username='test' and password='itisme' or '4'='4'
This seems perfect valid sql, but it may also be an sql injected version of:
"select * from users where username='test' and password='" . "itisme' or '4'='4". "'"
So you have to do it further up in your code, or use wrappers as the others suggested.
To do this the right way, you have to sanitize the stuff as you're building the query, or put that into your class to pass in parameters independent of the core query.
The whole idea of preventing SQL injection attacks is to prevent users from running their own SQL. Here, it seems like you want to allow users to run their own SQL, so why limit them?
Or if you're not not allowing users to pass SQL into the query() method. This is too low of a level to implement escaping parameters. As you've realized, you want to escape parameters only, not the entire SQL statements.
If you parametrize the SQL, then you could escape just the parameters. In this case, I'm assuming the users can affect the values of the parameters, and not the SQL.
Take a look at PDO's bindParam() or Zend_DB's query() methods to get an idea of how this is implemented in other database interfaces.
Me, I solved this problem by adding parameters to the query function.
I found that codeigniter did it pretty nicely, so I adapted it to my own tastes.
Example:
$result = Database::query('INSERT INTO table (column1,column2,column3) VALUES(?,?,?)',array($value1,$value2,$value3));
public static $bind_marker = '?';
public static function query($query, $binds = FALSE)
{
if($binds !== FALSE)
{
$query = self::compile_binds($query,$binds);
}
// $query now should be safe to execute
}
private static function compile_binds($query, $binds)
{
if(strpos($query, self::$bind_marker) === FALSE)
{
return $query;
}
if(!is_array($binds))
{
$binds = array($binds);
}
$segments = explode(self::$bind_marker, $query);
if(count($binds) >= count($segments))
{
$binds = array_slice($binds, 0, count($segments)-1);
}
$result = $segments[0];
$i = 0;
foreach($binds as $bind)
{
if(is_array($bind))
{
$bind = self::sanitize($bind);
$result .= implode(',',$bind);
}
else
{
$result .= self::sanitize($bind);
}
$result .= $segments[++$i];
}
return $result;
}
public static function sanitize($variable)
{
if(is_array($variable))
{
foreach($variable as &$value)
{
$value = self::sanitize($value);
}
}
elseif(is_string($variable))
{
mysql_real_escape_string($variable);
}
return $variable;
}
The major addition I added from codeigniter's version is I can use an array as a parameter which is useful for using "IN":
$parameters = array
(
'admin',
array(1,2,3,4,5)
);
$result = Database::query("SELECT * FROM table WHERE account_type = ? AND account_id IN (?)",$parameters);
Why reinvent the wheel ?
Just extend PDO and use parameterized queries. If you don't know what it is, read the doc which contains a lot of examples to get started.
Related
How can I pull multiple rows from a database using a function?
The function I have is:
function search($subject, $table) {
$query = "SELECT {$subject} ";
$query .= "FROM {$table} ";
$content = mysql_query($query);
return $content;
}
On the page which is calling the function I have:
if (isset($_POST['search'])){
$search = $_POST['search'];
}
$content = search($subjectName, $tableName);
while ($results = mysql_fetch_assoc($content)){
$phrase = $results[$subjectName];
//if phrase exists in database
if (strpos($search,$phrase) !== false) {
echo $phrase;
//if phrase does not exist in database
} else {
echo 'fail';
}
This setup does not work, however if I put everything into the function it works:
function search($subject, $table, $where = 0, $is = 0) {
global $search;
$query = "SELECT {$subject} ";
$query .= "FROM {$table} ";
if ($where > 0) {
$query .= "WHERE {$where} = '{$is}' ";
}
$content = mysql_query($query);
while ($results = mysql_fetch_assoc($content)){
$phrase = $results[$subject];
//if phrase exists in database
if (strpos($search,$phrase) !== false) {
echo $phrase;
//if phrase does not exist in database
} else {
echo 'fail';
}
}
return $content;
}
On Page:
search('main_subject', 'main_search');
The problem is that I would like to call that function again in the if statement to have it search for another phrase. Is there an easier way to do this?
EDIT: The current setup pulls the first item in an infinite loop.
There are a number of issues that should be addressed here:
First, if you are trying to search a field for a specific partial match, you would likely want to use SQL LIKE construct.
SELECT field FROM table WHERE field LIKE '%search phrase%'
Doing this would eliminate the need for you to iterate through each row trying to do a string comparison for your search phrase, as you would only be returned the rows that match the search phrase and nothing more.
Second, using global to make data available to your function is really poor practice. You really should be passing any data needed by the function to the function as a parameter. This would include your search string and probably your database connection/object.
function search($field, $table, $search, $db) {
...
}
Third, You are doing nothing at all to prevent against SQL injection right now. You need to escape the input data or, better yet use prepared statements.
Fourth, you really should not be using the mysql_* functions. They are deprecated. Try using mysqli or PDO (and I would highly recommend going ahead and learning how to use prepared statements with either of these.) You might start with mysqli ,at it provide procedural functions similar to mysql_* so the learning curve might be less steep (though really most experienced developers would prefer the object-oriented usage).
Fifth, to your original question. If you want search for multiple phrases, there are a couple approaches you can take. You can either pass all the phrases at once like this:
SELECT field FROM table WHERE field LIKE '%search phrase%' OR field LIKE '%another search phrase%'
Or, you could just make iterative function calls to get the results you want. This really depends on whether you only want to search for the second phrase if the first is not successful (use the iterative approach) or whether you just want all possible matches in one query (use the LIKE-OR-LIKE approach).
Im not trying to use a loop. I just one one value from one column from one row. I got what I want with the following code but there has to be an easier way using PDO.
try {
$conn = new PDO('mysql:host=localhost;dbname=advlou_test', 'advlou_wh', 'advlou_wh');
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
echo 'ERROR: ' . $e->getMessage();
}
$userid = 1;
$username = $conn->query("SELECT name FROM `login_users` WHERE username='$userid'");
$username2 = $username->fetch();
$username3 = $username2['name'];
echo $username3;
This just looks like too many lines to get one value from the database. :\
You can use fetchColumn():
$q= $conn->prepare("SELECT name FROM `login_users` WHERE username=?");
$q->execute([$userid]);
$username = $q->fetchColumn();
You could create a function for this and call that function each time you need a single value. For security reasons, avoid concatenating strings to form an SQL query. Instead, use prepared statements for the values and hardcode everything else in the SQL string. In order to get a certain column, just explicitly list it in your query. a fetchColumn() method also comes in handy for fetching a single value from the query
function getSingleValue($conn, $sql, $parameters)
{
$q = $conn->prepare($sql);
$q->execute($parameters);
return $q->fetchColumn();
}
Then you can simply do:
$name = getSingleValue($conn, "SELECT name FROM login_users WHERE id=?", [$userid]);
and it will get you the desired value.
So you need to create that function just once, but can reuse it for different queries.
This answer has been community edited addressing security concerns
Just like it's far too much work to have to get into your car, drive to the store, fight your way through the crowds, grab that jug of milk you need, then fight your way back home, just so you can have a milkshake.
All of those stages are necessary, and each subsequent step depends on the previous ones having been performed.
If you do this repeatedly, then by all means wrap a function around it so you can reuse it and reduce it down to a single getMyValue() call - but in the background all that code still must be present.
What is a proper way to filter parameters passed in functions? The goal is to make the function secure, especially when working with a database.
Example:
function user_profile($user_id)
{
//get user's profile data
$query = "SELECT * FROM `users` WHERE `user_id` = $user_id";
}
$user_id is a URI segment.
Other general examples are welcomed.
To escape strings, use the same method you'd use outside the function:
$user_id= mysql_real_escape_string($user_id);
If you're expecting the value to be, for example, an integer and would like to return error from the function if it isn't, you can do something like:
if (!is_int($user_id)) {
return FALSE;
}
else // do you query
Or if you expect it to match some specific pattern, do so with preg_match():
// For example, $user_id should be 4 letters and 4 numbers
if (!preg_match("/^[A-Z]{4}[0-9]{4}$/", $user_id)) {
return FALSE;
}
else // do you query
There's a couple of ways. The OLD way is to use mysql_real_escape_string(). However, many people nowadays complain bitterly about this, and say the proper way is to use prepared statements.
Create a filter class to handle all your filtering. Before you pass the variable into the function as a parameter, pass it through the filter class first. Or run the parameter through the filter class in the first line of your function.
So essentially, you're creating an abstract layer that 'filters'.
So the kind of filtering you're wanting to do in your scenario is to filter against sql injection/code injections.
So create a wrapper with this filter class around the mysql_real_escape_string() function.
The idea is to create an extensible filter class that can be used anywhere else in your application that is conceptually high level enough to handle all future needs.
final class Filter
{
static public function sqlInjections($some_parameter)
{
// my code to prevent injections by filtering $some_parameter
return mysql_real_escape_string($some_paramters);
}
static public function badWords()
{
// code in the future that can be added to filter bad words
}
}
call it like so $filtered_parameter = Filter::sqlInjections($some_paramter);
If your user_id field is a string in your database, then, you'll use mysql_real_escape_string(), or mysqli_real_escape_string(), or PDO::quote() -- depending on the API you're working with :
$query = "SELECT * FROM `users` WHERE `user_id` = '"
. mysql_real_escape_string($user_id) . "'";
or
$query = "SELECT * FROM `users` WHERE `user_id` = '"
. mysqli_real_escape_string($user_id) . "'";
or, with PDO -- provided that $db is a PDO object :
$query = "SELECT * FROM `users` WHERE `user_id` = '"
. $db->quote($user_id) . "'";
But, if it's an integer, you should make sure that the value passed to it is indeed an integer -- which is generally done using intval() :
$query = "SELECT * FROM `users` WHERE `user_id` = "
. intval($user_id);
Edit: I just realized you said it's an URL segment -- so, not an integer. I don't delete this idea, though: it might help someone else who would read this answer.
Another solution would be to not build a query containing that value -- and use prepared statements.
See :
For mysqli : mysqli::prepare()
And with PDO : PDO::prepare()
Use mysql_real_escape_string()
$query = "SELECT * FROM users WHERE user_id = '" . mysql_real_escape_string($user_id) . "'";
You can do two or Three complementary ways to prevent SQL injection:
The escape functions commented above.
Query the other way around:
function user_profile($user_id)
{
//get user's profile data
$query = "SELECT * FROM users WHERE {$user_id} = user_id";
}
User prepare and execute functions/methods if your database engine allow it
http://php.net/manual/en/pdo.prepare.php
http://www.php.net/manual/en/pdostatement.execute.php
I'm trying to write a function that is versatile in the queries it is allowed to make, but also safe from injection. The code below throws an error as is, but if I run it with 'name' instead of ':field' it works fine.
$field = "name";
$value = "joe";
function selectquery($field, $value)
{
global $dbcon;
$select = $dbcon->prepare('SELECT * FROM tester1 WHERE :field = :value');
if($select->execute(array(':field' => $field, ':value' => $value)));
{
$row = $select->fetch();
for ($i=0; $i<3; $i++)
{
echo $row[$i]."\n";
}
}
}
How would I allow the table/fields/values to be changed, without allowing injection attacks? mysql_real_escape_string() seems kind of like a step backwards. Any ideas?
I may be mistaken, but I don't believe you can supply fields as parameters in PDO.
Why not just specify it as argument to the function? Unlike the data being supplied by the user, the fields are finite, well defined and don't change often. As in
selectquery('name',$value);
and have your query be
$field = "name";
$value = "joe";
function selectquery($field, $value)
{
global $dbcon;
$select=$dbcon->prepare("SELECT * FROM tester1 WHERE $field = :value");
if($select->execute(array(':value' => $value)));
//etcetera
Since you're supplying the field name for the function call yourself, this is safe unless you're worried you are going to attack yourself with SQL injection.
If for some odd reason the name of the field is coming from user input, you could make an array of allowed fields. That way, you're safe from injection because the values can only come from your array. I don't know why the field name would be coming from user input and thus be untrusted, unless perhaps you're making an API? Other wise there's probably a better way to achieve the goal.
Anyhow, this would be a potential solution, to use a whitelist for table names:
$field = "name";
$value = "joe";
$allowed_fields=array('name','other_name','sandwich');
function selectquery($field_name, $value)
{
global $dbcon,$allowed_fields;
if(!in_array($field_name,$allowed_fields)){ return false; }
else{ $field=$field_name; }
$select=$dbcon->prepare("SELECT * FROM tester1 WHERE $field = :value");
if($select->execute(array(':value' => $value)));
//etcetera
Use MDB2 autoExecute
http://pear.php.net/manual/en/package.database.mdb2.intro-auto.php
<?php
// Once you have a valid MDB2 object named $mdb2...
$table_name = 'user';
$fields_values = array(
'id' => 1,
'name' => 'Fabien',
'country' => 'France'
);
$types = array('integer', 'text', 'text');
$mdb2->loadModule('Extended');
$affectedRows = $mdb2->extended->autoExecute($table_name, $fields_values,
MDB2_AUTOQUERY_INSERT, null, $types);
if (PEAR::isError($affectedRows)) {
die($affectedRows->getMessage());
}
?>
Database identifiers (column names, table names and database names) can not and should not be escaped, therefore you can't use them in SQL prepared queries.
Sometimes you might need to backtick those identifiers though (use ` for MySQL and " for SQLite).
Binding a variable binds it as data, specifically to prevent it from changing the syntax of the query. Furthermore, having a fixed syntax allows engines to analyze prepared queries once and then run them quickly for each set of values. I would encourage you not to build a hand-holding layer on top of SQL, but if you must, consider a preg_replace('/\W/', '', $field).
PHP Data Objects unfortunately doesn't expose a method to quote a field identifier.
As an alternative, PEAR::MDB2 (spiritual predecessor to PHP Data Objects) has a ->quoteIdentifier() option which allows you to achieve what you want in a safe manner.
function selectquery($field, $value)
{
global $dbcon;
$select = $dbcon->prepare('SELECT * FROM tester1 WHERE ' . $dbcon->quoteIdentifier($field) . ' = :value');
if($select->execute(array('field' => $field, 'value' => $value)));
{
$row = $select->fetchRow();
for ($i=0; $i<3; $i++)
{
echo $row[$i]."\n";
}
}
}
I understand that this solution is less than optimal (changing abstraction layer in the middle of developing a project is cumbersome) but unfortunately, PDO provides no safe way of doing what you want to do.
Seconding Andrew Moore's response: the only way to go is identifier quoting, and PDO doesn't provide the necessary method. Rather than use MDB2, you might just want to borrow its implementation of identifier quoting. The function is simple enough that you should be able to write your own and vet it for bugs fairly easily.
Split the input string on . into a list of parts (there might be only one)
For each part:
Replace all ` with ``.
Add a ` to the beginning and to the end unless the part is empty.*
Join the parts with ..
As an example, quote_identifier("one two.three") should be `one two`.`three` -- pretty straightforward.
For extra safety you could also verify that the string doesn't contain any characters that are illegal even in quoted identifiers (particularly nulls, see the MySQL docs) but in truth MySQL should catch those. MDB2 doesn't bother.
*: This check is necessary because .columnname is legal, and should quote to .`columnname` and not ``.`columnname`.
Should be a simple question, I'm just not familiar with PHP syntax and I am wondering if the following code is safe from SQL injection attacks?:
private function _getAllIngredients($animal = null, $type = null) {
$ingredients = null;
if($animal != null && $type != null) {
$query = 'SELECT id, name, brief_description, description,
food_type, ingredient_type, image, price,
created_on, updated_on
FROM ingredient
WHERE food_type = \'' . $animal . '\'
AND ingredient_type =\'' . $type . '\';';
$rows = $this->query($query);
if(count($rows) > 0) {
etc, etc, etc
I've googled around a bit and it seems that injection safe code seems to look different than the WHERE food_type = \'' . $animal . '\' syntax used here.
Sorry, I don't know what version of PHP or MySQL that is being used here, or if any 3rd party libraries are being used, can anyone with expertise offer any input?
UPDATE
What purpose does the \ serve in the statement?:
WHERE food_type = \'' . $animal . '\'
In my googling, I came across many references to mysql_real_escape_string...is this a function to protect from SQL Injection and other nastiness?
The class declaration is:
class DCIngredient extends SSDataController
So is it conceivable that mysql_real_escape_string is included in there?
Should I be asking to see the implementation of SSDataController?
Yes this code is vulnerable to SQL-Injection.
The "\" escapse only the quote character, otherwise PHP thinks the quote will end your (sql-)string.
Also as you deliver the whole SQL-String to the SSDataControler Class, it is not possible anymore to avoid the attack, if a prepared string has been injected.
So the class SSDataControler is broken (vulnerable) by design.
try something more safe like this:
$db_connection = new mysqli("host", "user", "pass", "db");
$statement = $db_connection->prepare("SELECT id, name, brief_description, description,
food_type, ingredient_type, image, price,
created_on, updated_on
FROM ingredient
WHERE food_type = ?
AND ingredient_type = ?;';");
$statement->bind_param("s", $animal);
$statement->bind_param("s", $type);
$statement->execute();
by using the bind method, you can specify the type of your parameter(s for string, i for integer, etc) and you will never think about sql injection again
You might as well use mysql_real_escape_string anyway to get rid of anything that could do a drop table, or execute any other arbitrary code.
It doesn't matter where in the SQL statement you put the values, at any point it can have a ';' in it, which immediately ends the statement and starts a new one, which means the hacker can do almost anything he wants.
To be safe, just wrap your values in mysql_real_escape_string($variable). So:
WHERE Something='".mysql_real_escape_string($variable)."'
$animal can be a string which contains '; drop table blah; -- so yes, this is vunerable to SQL injection.
You should look into using prepared statements, where you bind parameters, so that injection cannot occur:
http://us3.php.net/pdo.prepared-statements
It is vulnerable. If I passed: '\'; DROP TABLE ingredient; SELECT \''
as $type, poof, goodbye ingredient table.
If its a private function, kind of. If you're concerned about people with access to the source injecting SQL, there are simpler ways to accomplish their goal. I recommend passing the arguments to mysql_real_escape_string() before use, just to be safe. Like this:
private function _getAllIngredients($animal = null, $type = null) {
$animal = mysql_real_escape_string($animal);
$type = mysql_real_escape_string($type);
...
}
To be extra safe, you might even pass it to htmlentities() with the ENT_QUOTES flag instead - that would also help safeguard against XSS type stuff, unless you're putting the contents of those DB entries into a <script> tag.
But the safest thing you can do is write your own sanitizing function, which would make the best of various techniques, and also allow you to easily protect against new threats which may arise in the future.
Re: UPDATE
The \'' serves to enclose the variable in quotes, so that when it goes through to SQL, nothing is broken by whitespace. IE: Where Something=Cold Turkey would end with an error, where Something='Cold Turkey' would not.
Furthermore, the class extension won't affect how you put together MySQL Statements.
If the the user or any 3rd party has anyway of injecting a value of $animal into your system (which they probably do), then yes it is vunerable to sql injection.
The way to get around this would be to do
private function _getAllIngredients($animal = null, $type = null) {
$ingredients = null;
if($animal != null && $type != null) {
$query = 'SELECT id, name, brief_description, description,
food_type, ingredient_type, image, price,
created_on, updated_on
FROM ingredient
$rows = $this->query($query);
if(count($rows) > 0) {
if($rows['animal'] == $animal && $rows['ingredient_type'] == $type) {
Note: I removed WHERE statements from sql and added if statements to loop