I created a class to handle CRUD more easily and got a problem.
My current class runs a crud in this syntax:
$crud = Core::Db()->builder();
$userData =$crud
->select("city,adress")
->from('users')
->where('name')
->like(':name')
->read(array(':name','%stefan'));
When you call crud method, automatically creates and inserts sql sentence to private variable $this->sql. write() prepare data and binds prepared statements(if exists).
Last example generates the following SQL:
$this-sql = "SELECT city,address FROM users WHERE name LIKE :name";
Everything works perfect. Binds :name and adds'%stefan'. All ok.
this is my problem. if i add :
$userData =$crud
->select("city,adress")
->from('users')
->where('name')
->like('%stefan')
->read();
returns :
//bad syntax
$this->sql = "SELECT `city`,`address` FROM `users` WHERE `name` LIKE %stefan";
If I add quotes to data, the second example will work fine, but the first will fail:
// First example, bad sintax
$this-sql = "SELECT `city`,`address` FROM `users` WHERE `name` LIKE ':name'";
// Second example, good
$this-sql = "SELECT `city`,`address` FROM `users` WHERE `name` LIKE '%stefan' ";
I had thought of a solution ... Add quotes in like() method call and stripes it if the name is equal than a name of binded parameter binded in write():
private function write(array $input_parameters=array(),$style=NULL)
{
// clean possible errors in holders
foreach($input_parameters as $key => $value):
if(strpos($this->sql,"'".$key."'") !== FALSE)
$this->sql = str_replace("'".$key."'",$key,$this->sql);
if(strpos($this->sql,'"'.$key.'"') !== FALSE)
$this->sql = str_replace('"'.$key.'"',$key,$this->sql);
endforeach;
// .... code ....//
}
This solutions works fine in this case, but if you have something like this:
$userData =$crud
->select("id")
->from('blog')
->where('content')
->like(':word')
->_or('something')
->like(':word')
->read(array(':word','something'));
returns:
$this->sql = "SELECT `id` FROM `blog` WHERE `name` LIKE :word or `something` LIKE :word ";
This isn't a real life example, but its only for shows why i think its a bad solution, can become a security risk.
I cant find a way to binds data without secure problems, or damaged sql, i miss something here.
Can you think of anything else? I forgotten something? I'm paranoid? Now my head hurts so much, i cant think like a human.
Your help will be welcome, thanks
Related
I was wondering if we can still use values from a freshly DELETE row as a SELECT or do we really need to SELECT it before ?
Example :
Transform this
$foo = $db->prepare("SELECT * FROM table WHERE id= :id");
$foo->execute(array(
"id" => $table_id
));
$foo = $foo->fetch(PDO::FETCH_ASSOC);
$delete_foo = $bdd->prepare("DELETE FROM table WHERE id = :id");
$delete_foo->execute(array(
"id" => $table_id
));
echo $foo['name'] . " has been deleted !";
Into this :
$delete_foo = $bdd->prepare("DELETE FROM table WHERE id = :id");
$delete_foo->execute(array(
"id" => $table_id
));
$delete_foo = $delete_foo->fetch(PDO::FETCH_ASSOC);
echo $delete_foo['name'] . " has been deleted !";
It would be easier. I was just wondering, I use the 1st method but it just went in mind and I don't find answers.
In postgresql, there is a proprietary extension to the delete statement called RETURNING. Sql Server provides something similar, they call it OUTPUT
For example, OUTPUT DELETED.* in the following DELETE statement
returns all columns deleted from the ShoppingCartItem table:
DELETE Sales.ShoppingCartItem
OUTPUT DELETED.*;
Unfortunately, mysql does not have anything like the above. If you delete a row it's gone (unless you roll back the transaction instead of commiting). If you want to Select the data, you need to execute the SELECT before the DELETE
DELETE queries wont return any results (besides rows affected), so a PDO::query wont have any usable data to fetch.
For the example provided, an extra select query just makes no sense. As you have your $value already.
I would rather say that you need to simplify your PDO code at whole. Compare the below code snippet with yours
$foo = $db->run("SELECT foo FROM table WHERE value = ?", [$value])->fetchColumn();
$db->run("DELETE FROM table WHERE value = ?", [$value]);
echo "$foo has been deleted!";
the run() function can be achieved by a very small PDO modification:
class MyPDO extends PDO
{
public function run($sql, $args = NULL)
{
$stmt = $this->prepare($sql);
$stmt->execute($args);
return $stmt;
}
}
the code is taken from my article, Simple yet efficient PDO wrapper
I'm trying to write a simple, full text search with PHP and PDO. I'm not quite sure what the best method is to search a DB via SQL and PDO. I found this this script, but it's old MySQL extension. I wrote this function witch should count the search matches, but the SQL is not working. The incoming search string look like this: 23+more+people
function checkSearchResult ($searchterm) {
//globals
global $lang; global $dbh_pdo; global $db_prefix;
$searchterm = trim($searchterm);
$searchterm = explode('+', $searchterm);
foreach ($searchterm as $value) {
$sql = "SELECT COUNT(*), MATCH (article_title_".$lang.", article_text_".$lang.") AGINST (':queryString') AS score FROM ".$db_prefix."_base WHERE MATCH (article_title_".$lang.", article_text_".$lang.") AGAINST ('+:queryString')";
$sth = $dbh_pdo->prepare($sql);
$sql_data = array('queryString' => $value);
$sth->execute($sql_data);
echo $sth->queryString;
$row = $sth->fetchColumn();
if ($row < 1) {
$sql = "SELECT * FROM article_title_".$lang." LIKE :queryString OR aricle_text_".$lang." LIKE :queryString";
$sth = $dbh_pdo->prepare($sql);
$sql_data = array('queryString' => $value);
$sth->execute($sql_data);
$row = $sth->fetchColumn();
}
}
//$row stays empty - no idea what is wrong
if ($row > 1) {
return true;
}
else {
return false;
}
}
When you prepare the $sql_data array, you need to prefix the parameter name with a colon:
array('queryString' => $value);
should be:
array(':queryString' => $value);
In your first SELECT, you have AGINST instead of AGAINST.
Your second SELECT appears to be missing a table name after FROM, and a WHERE clause. The LIKE parameters are also not correctly formatted. It should be something like:
sql = "SELECT * FROM ".$db_prefix."_base WHERE article_title_".$lang." LIKE '%:queryString%' OR aricle_text_".$lang." LIKE '%:queryString%'";
Update 1 >>
For both SELECT statements, you need unique identifiers for each parameter, and the LIKE wildcards should be placed in the value, not the statement. So your second statement should look like this:
sql = "SELECT * FROM ".$db_prefix."_base WHERE article_title_".$lang." LIKE :queryString OR aricle_text_".$lang." LIKE :queryString2";
Note queryString1 and queryString2, without quotes or % wildcards. You then need to update your array too:
$sql_data = array(':queryString1' => "%$value%", ':queryString2' => "%$value%");
See the Parameters section of PDOStatement->execute for details on using multiple parameters with the same value. Because of this, I tend to use question marks as placeholders, instead of named parameters. I find it simpler and neater, but it's a matter of choice. For example:
sql = "SELECT * FROM ".$db_prefix."_base WHERE article_title_".$lang." LIKE ? OR aricle_text_".$lang." LIKE ?";
$sql_data = array("%$value%", "%$value%");
<< End of Update 1
I'm not sure what the second SELECT is for, as I would have thought that if the first SELECT didn't find the query value, the second wouldn't find it either. But I've not done much with MySQL full text searches, so I might be missing something.
Anyway, you really need to check the SQL, and any errors, carefully. You can get error information by printing the results of PDOStatement->errorCode:
$sth->execute($sql_data);
$arr = $sth->errorInfo();
print_r($arr);
Update 2 >>
Another point worth mentioning: make sure that when you interpolate variables into your SQL statement, that you only use trusted data. That is, don't allow user supplied data to be used for table or column names. It's great that you are using prepared statements, but these only protect parameters, not SQL keywords, table names and column names. So:
"SELECT * FROM ".$db_prefix."_base"
...is using a variable as part of the table name. Make very sure that this variable contains trusted data. If it comes from user input, check it against a whitelist first.
<< End of Update 1
You should read the MySQL Full-Text Search Functions, and the String Comparison Functions. You need to learn how to construct basic SQL statements, or else writing even a simple search engine will prove extremely difficult.
There are plenty of PDO examples on the PHP site too. You could start with the documentation for PDOStatement->execute, which contains some examples of how to use the function.
If you have access to the MySQL CLI, or even PHPMyAdmin, you can try out your SQL without all the PHP confusing things. If you are going to be doing any database development work as part of your PHP application, you will find being able to test SQL independently of PHP a great help.
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
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
how do you embed your sql scripts in php? Do you just write them in a string or a heredoc or do you outsource them to a sql file? Are there any best practices when to outsource them ? Is there an elegant way to organize this?
Use a framework with an ORM (Object-Relational Mapping) layer. That way you don't have to put straight SQL anywhere. Embedded SQL sucks for readability, maintenance and everything.
Always remember to escape input. Don't do it manually, use prepared statements. Here is an example method from my reporting class.
public function getTasksReport($rmId, $stage, $mmcName) {
$rmCondition = $rmId ? 'mud.manager_id = :rmId' : 'TRUE';
$stageCondition = $stage ? 't.stage_id = :stageId' : 'TRUE';
$mmcCondition = $mmcName ? 'mmcs.username = :mmcName' : 'TRUE';
$sql = "
SELECT
mmcs.id AS mmc_id,
mmcs.username AS mmcname,
mud.band_name AS mmc_name,
t.id AS task_id,
t.name AS task,
t.stage_id AS stage,
t.role_id,
tl.id AS task_log_id,
mr.role,
u.id AS user_id,
u.username AS username,
COALESCE(cud.full_name, bud.band_name) AS user_name,
DATE_FORMAT(tl.completed_on, '%d-%m-%Y %T') AS completed_on,
tl.url AS url,
mud.manager_id AS rm_id
FROM users AS mmcs
INNER JOIN banduserdetails AS mud ON mud.user_id = mmcs.id
LEFT JOIN tasks AS t ON 1
LEFT JOIN task_log AS tl ON tl.task_id = t.id AND tl.mmc_id = mmcs.id
LEFT JOIN mmc_roles AS mr ON mr.id = t.role_id
LEFT JOIN users AS u ON u.id = tl.user_id
LEFT JOIN communityuserdetails AS cud ON cud.user_id = u.id
LEFT JOIN banduserdetails AS bud ON bud.user_id = u.id
WHERE mmcs.user_type = 'mmc'
AND $rmCondition
AND $stageCondition
AND $mmcCondition
ORDER BY mmcs.id, t.stage_id, t.role_id, t.task_order
";
$pdo = new PDO(.....);
$stmt = $pdo->prepare($sql);
$rmId and $stmt->bindValue('rmId', $rmId); // (1)
$stage and $stmt->bindValue('stageId', $stage); // (2)
$mmcName and $stmt->bindValue('mmcName', $mmcName); // (3)
$stmt->execute();
return $stmt->fetchAll();
}
In lines marked (1), (2), and (3) you will see a way for conditional binding.
For simple queries I use ORM framework to reduce the need for building SQL manually.
It depends on a query size and difficulty.
I personally like heredocs. But I don't use it for a simple queries.
That is not important. The main thing is "Never forget to escape values"
You should always really really ALWAYS use prepare statements with place holders for your variables.
Its slightly more code, but it runs more efficiently on most DBs and protects you against SQL injection attacks.
I prefer as such:
$sql = "SELECT tbl1.col1, tbl1.col2, tbl2.col1, tbl2.col2"
. " FROM Table1 tbl1"
. " INNER JOIN Table2 tbl2 ON tbl1.id = tbl2.other_id"
. " WHERE tbl2.id = ?"
. " ORDER BY tbl2.col1, tbl2.col2"
. " LIMIT 10, 0";
It might take PHP a tiny bit longer to concatenate all the strings but I think it looks a lot nicer and is easier to edit.
Certainly for extremely long and specialized queries it would make sense to read a .sql file or use a stored procedure. Depending on your framework this could be as simple as:
$sql = (string) View::factory('sql/myfile');
(giving you the option to assign variables in the view/template if necessary). Without help from a templating engine or framework, you'd use:
$sql = file_get_contents("myfile.sql");
Hope this helps.
I normally write them as function argument:
db_exec ("SELECT ...");
Except cases when sql gonna be very large, I pass it as variable:
$SQL = "SELECT ...";
$result = db_exec ($SQL);
(I use wrapper-functions or objects for database operations)
$sql = sprintf("SELECT * FROM users WHERE id = %d", mysql_real_escape_string($_GET["id"]));
Safe from MySQL injection
You could use an ORM or an sql string builder, but some complex queries necessitate writing sql. When writing sql, as Michał Słaby illustrates, use query bindings. Query bindings prevent sql injection and maintain readability. As for where to put your queries: use model classes.
Once you get to a certain level, you realise that 99% of the SQL you write could be automated. If you write so much queries that you think of a properties file, you're probably doing something that could be simpler:
Most of the stuff we programmers do is CRUD: Create Read Update Delete
As a tool for myself, I built Pork.dbObject. Object Relation Mapper + Active Record in 2 simple classes (Database Abstraction + dbObject class)
A couple of examples from my site:
Create a weblog:
$weblog = new Weblog(); // create an empty object to work with.
$weblog->Author = 'SchizoDuckie'; // mapped internally to strAuthor.
$weblog->Title = 'A test weblog';
$weblog->Story = 'This is a test weblog!';
$weblog->Posted = date("Y-m-d H:i:s");
$weblog->Save(); // Checks for any changed values and inserts or updates into DB.
echo ($weblog->ID) // outputs: 1
And one reply to it:
$reply = new Reply();
$reply->Author = 'Some random guy';
$reply->Reply = 'w000t';
$reply->Posted = date("Y-m-d H:i:s");
$reply->IP = '127.0.0.1';
$reply->Connect($weblog); // auto-saves $reply and connects it to $weblog->ID
And, fetch and display the weblog + all replies:
$weblog = new Weblog(1); //Fetches the row with primary key 1 from table weblogs and hooks it's values into $weblog;
echo("<h1>{$weblog->Title}</h1>
<h3>Posted by {$weblog->Author} # {$weblog->Posted}</h3>
<div class='weblogpost'>{$weblog->Story}</div>");
// now fetch the connected posts. this is the real magic:
$replies = $weblog->Find("Reply"); // fetches a pre-filled array of Reply objects.
if ($replies != false)
{
foreach($replies as $reply)
{
echo("<div class='weblogreply'><h4>By {$reply->Author} # {$reply->Posted}</h4> {$reply->Reply}</div>");
}
}
The weblog object would look like this:
class Weblog extends dbObject
{
function __construct($ID=false)
{
$this->__setupDatabase('blogs', // database table
array('ID_Blog' => 'ID', // database field => mapped object property
'strPost' => 'Story', // as you can see, database field strPost is mapped to $this->Story
'datPosted' => 'Posted',
'strPoster' => 'Author',
'strTitle' => 'Title',
'ipAddress' => 'IpAddress',
'ID_Blog', // primary table key
$ID); // value of primary key to init with (can be false for new empty object / row)
$this->addRelation('Reaction'); // define a 1:many relation to Reaction
}
}
See, no manual SQL writing :)
Link + more examples: Pork.dbObject
Oh yeah i also created a rudimentary GUI for my scaffolding tool: Pork.Generator
I like this format. It was mentioned in a previous comment, but the alignment seemed off to me.
$query = "SELECT "
. " foo, "
. " bar "
. "FROM "
. " mytable "
. "WHERE "
. " id = $userid";
Easy enough to read and understand. The dots line up with the equals sign keeping everything in a clean line.
I like the idea of keeping your SQL in a separate file too, although I'm not sure how that would work with variables like $userid in my example above.