SQL Injection - is this query secure? - php

I have a a page that appends different parameters to the URL that are used for the query.
For example
http://www.example.com/search.php?category=Schools&country[]=Belgium&country[]=Czech+Republic
My code is like this
if(isset($_GET['country'])){
$cties = "'" . implode("','", $_GET['country']) . "'";
}
else {
$cties = "'Albania','Andorra','Austria','Belarus','Belgium','Bosnia & Herzegovina','Bulgaria','Croatia','Czech Republic','Denmark','Estonia','Faroe Islands','Finland','France','Germany','Gibraltar','Great Britain','Greece','Hungary','Iceland','Ireland','Isle of Man','Italy','Latvia','Liechtenstein','Lithuania','Luxembourg','Macedonia','Malta','Moldova','Monaco','Montenegro','Netherlands','Norway','Poland','Portugal','Serbia','Romania','San Marino','Slovakia','Slovenia','Spain','Sweden','Switzerland','Ukraine','United Kingdom'";
}
if(isset($_GET['category'])){
$cat = $_GET['category'];
}
else{
$cat = " ";
}
try{
// create the Prepared Statement
$stmt = $con->prepare("SELECT * FROM MyTable
WHERE MyDate >= DATE(NOW())
AND (Category=:cat or '' = :cat)
AND Country IN ($cties)
ORDER BY MyDate ASC");
$stmt->bindValue(':cat', $cat, PDO::PARAM_STR);
$stmt->execute();
I was wondering if this query is secure and if not, what I am doing wrong.
Thanks in advance!
I finally got it (thanks to Your Common Sense):
if(isset($_GET['country'])){
$arr = $_GET['country'];
}
else {
$arr = array('Albania','Andorra','Austria','Belarus','Belgium','Bosnia & Herzegovina','Bulgaria','Croatia','Czech Republic','Denmark','Estonia','Faroe Islands','Finland','France','Germany','Gibraltar','Great Britain','Greece','Hungary','Iceland','Ireland','Isle of Man','Italy','Latvia','Liechtenstein','Lithuania','Luxembourg','Macedonia','Malta','Moldova','Monaco','Montenegro','Netherlands','Norway','Poland','Portugal','Serbia','Romania','San Marino','Slovakia','Slovenia','Spain','Sweden','Switzerland','Ukraine','United Kingdom');
}
if(isset($_GET['category'])){
$cat = $_GET['category'];
}
else{
$cat = " ";
}
// create the Prepared Statement
$in = str_repeat('?,', count($arr) - 1) . '?';
$sql = "SELECT * FROM MyTable WHERE MyDate >= DATE(NOW())
AND Country IN ($in)
AND (Category=? or '' = ?)
ORDER BY MyDate ASC";
$stmt = $con->prepare($sql);
$arr[] = $cat; // adding category to array
$arr[] = $cat; // we need it twice here
// finally - execute
$stmt->execute($arr);

Yeah, Now I see your problem. Well, PDO is not too convenient a library for such a task. So, first of all I'll show you how it can be done with my own library:
$sql = "SELECT * FROM MyTable WHERE MyDate >= CURDATE()
AND (Category=?s or '' = ?s)
AND Country IN (?a)
ORDER BY MyDate ASC"
$data = $db->getAll($sql, $cat, $cat, $_GET['country']);
But I quite realize that you all so inclined to familiar methods. Well, let's elaborate with ugly PDO
First of all, what is the goal? The goal is
to create the query that contains placeholders for all the data. I'll stick to positional placeholders as they are easier to implement.
To create an array with all the variables that have to be bound to placeholders
It seems we need two placeholders for category and some unknown number fro cities. All right, this line will create a string of placeholders:
$in = str_repeat('?,', count($arr) - 1) . '?';
which we are going to insert into query.
// $arr is array with all the vars to bind. at the moment it contains cities only
$arr = $_GET['country'];
// creating string of ?s
$in = str_repeat('?,', count($arr) - 1) . '?';
// building query
$sql = "SELECT * FROM MyTable WHERE MyDate >= DATE(NOW())
AND Country IN ($in)
AND (Category=? or '' = ?)
ORDER BY MyDate ASC";
$stm = $db->prepare($sql);
$arr[] = $_GET['category']; // adding category to array
$arr[] = $_GET['category']; // we need it twice here
// finally - execute
$stm->execute($arr);
$data = $stm->fetchAll();

No, the SQL code could be injected in the $_GET['country'] parameter. You don't escape it anywhere.
See PHP PDO: Can I bind an array to an IN() condition?

Related

PDO: Delete query only deleting entries of first value in IN statement [duplicate]

I found this code on SO, which is great for using PDO and the IN() statement together.
$values = explode(',', $values) ; # 1,4,7
$placeholders = rtrim(str_repeat('?, ', count($values)), ', ') ;
$query = "SELECT * FROM table WHERE id IN ($placeholders)";
$stm = $db->prepare($query) ;
$stm->execute($values) ;
However, how can I mix in another addition to the query so the query looks like this:
$query = "SELECT * FROM table WHERE id IN ($placeholders) AND product=?";
$stm = $db->prepare($query) ;
$stm->execute(array($values,$product)) ; //error happens when adding product placeholder
I thought this would work but I get:
Warning: PDOStatement::execute() [pdostatement.execute]: SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens in line 3 (the $stm line)
Any idea how to get this to behave as intended?
UPDATED execute to array, still not working..
Solution
This should work, if $values is an array:
$query = "SELECT * FROM table WHERE id IN ($placeholders) AND product=?";
$stm->execute(array_merge($values, array($product)));
Explanation
execute() expects one parameter - in this case an array - to be provided. By adding array_merge($values, array($product)) you create one array with $product added at the end, so the query should work correctly.
See the demo here: http://ideone.com/RcClX
$stm->execute($values,$product) ; //error happens when adding product placeholder
The problem here is that execute needs a single array. You can't pass multiple arrays, and worse, you can't nest arrays.
We already have a perfectly good $values array, so let's reuse it after you create the placeholder string.
$values = explode(',', $values) ; # 1,4,7
$placeholders = rtrim(str_repeat('?, ', count($values)), ', ') ;
$query = "SELECT * FROM table WHERE id IN ($placeholders) AND product=?";
// New!
$values[] = $product;
$stm = $db->prepare($query);
$stm->execute($values);
And an other solution can be (if you like the :param_name = $value way, as me):
$params = array(
':product' => $product
);
$_in_params = array();
foreach ( $_in_values as $idx_in => $value_in)
{
$_in_params[] = ':param_in_'.$idx_in;
$params[':param_in_'.$idx_in] = $value_in;
}
$query .= "SELECT * FROM table WHERE id IN (".join(',',$_in_params).") AND product=:product";
I'm not sure if this is the best and the most optimal solution, but it's a little bit more human readable :) And it can be helpful if you have a big an complicated query and you want to debug it
(I'm curious if someone have a good argument why NOT to do in this way)
You forgot to prepare it ^_^
$query = "SELECT * FROM table WHERE id IN ($placeholders) AND product=?";
$stm = $db->prepare($query) ;
$stm->execute($values,$product) ; //p00f
And aside from that execute() should only have one parameter
So the above won't work AT ALL!
See the DOCs
Placeholders version if you need it
$values = [1, 4, 7, 8];
$placeholders = preg_filter('/^/', ':prefix_', array_keys($values)));
$query = 'SELECT * FROM table WHERE id IN ( '. implode(', ', $placeholders) . ')';
$stmt = $db->prepare($query);
if (count($values) > 0) {
foreach ($values as $key => $current_value) {
$stmt->bindValue($placeholders[$key] , $current_value, PDO::PARAM_STR);
}
}
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);

Use more placeholders in WHERE statement after IN statement with arrays

I need more placeholders to be added to the following query. Is this possible? I have no idea where to begin and I can not find any options on the Internet.
The query now:
$in2 = str_repeat('?,', count($arrayid) - 1) . '?';
$sql2 = "SELECT COUNT(id) AS totalacc FROM account WHERE id IN ($in2) ";
$stmt2 = $mysqli->prepare($sql2);
$types2 = str_repeat('i', count($arrayid));
$stmt2->bind_param($types2,...$arrayid);
$stmt2->execute();
$stmt2->bind_result($row['totalacc']);
while($stmt2->fetch()) $totalacc = $row['totalacc'];
The query I am aiming for:
$countname1 = '(Hallo)';
$countname = trim(filter_var("%{$countname1}%", FILTER_SANITIZE_STRING));
$in2 = str_repeat('?,', count($arrayid) - 1) . '?';
$sql2 = "SELECT COUNT(id) AS totalacc FROM account WHERE id IN ($in2) AND name LIKE ?";
$stmt2 = $mysqli->prepare($sql2);
$types2 = str_repeat('i', count($arrayid));
$stmt2->bind_param($types2,s,...$arrayid,$countname); // Will never work, but how to do this?
$stmt2->execute();
$stmt2->bind_result($row['totalacc']);
while($stmt2->fetch()) $totalacc = $row['totalacc'];
You just need to append the extra values to your array of parameters and string of types. This should work (untested):
$countname1 = '(Hallo)';
$countname = trim(filter_var("%{$countname1}%", FILTER_SANITIZE_STRING));
$in2 = str_repeat('?,', count($arrayid) - 1) . '?';
$sql2 = "SELECT COUNT(id) AS totalacc FROM account WHERE id IN ($in2) AND name LIKE ?";
$stmt2 = $mysqli->prepare($sql2);
$types2 = str_repeat('i', count($arrayid));
$types2 .= "s"; //append "s" to the end of the $types2 string.
$arrayid[] = $countname; //append the value of $countname to the array of parameters
$stmt2->bind_param($types2, ...$arrayid);
$stmt2->execute();
//...etc
The easiest solution would be to use PDO instead of mysqli. This would be so much easier.
If you are stuck with mysqli then you can achieve a similar thing by simply ignoring the types and appending your result to the array.
$countname1 = '(Hallo)';
$countname = "%{$countname1}%";
$in2 = str_repeat('?,', count($arrayid) - 1) . '?';
$sql2 = "SELECT COUNT(id) AS totalacc FROM account WHERE id IN ($in2) AND name LIKE ?";
$stmt2 = $mysqli->prepare($sql2);
$arrayid[] = $countname;
$stmt2->bind_param(str_repeat('s', count($arrayid)), ...$arrayid);
$stmt2->execute();
$stmt2->bind_result($totalacc);
$stmt2->fetch();
You could even write a function to abstract from all of this code.

How to enter variable into prepared stmt that will retrieve all from the column?

I'm making a search filter on my events website. There are 3 drop down inputs: Location, event type, date.
When the user submits the search filter, the form posts values that changes the mysql query which will display different events on the user screen. I'm having trouble finding a flexible solution.
Right now my query is like this:
$filter = $database->prepared_query("SELECT * FROM onlineevent WHERE event_location = (?) AND event_type = (?) AND event_date = (?)", array($l, $t, $d));
How can I make $l retrieve ALL possible values for event_location? The same goes for $t and $d. I thought I could set $l to '*' but that doesn't work.
The problem now is if the user doesn't select a value for $l, and they do select a value for $t and $d, then the query doesn't work. I want to set the default value for each variable to bring all results for each condition.
So if the user doesn't select any filter and submits the form, the query I'm looking for would look something like this:
$filter = $database->prepared_query("SELECT * FROM onlineevent WHERE event_location = (?) AND event_type = (?) AND event_date = (?)", array(ALL, ALL, ALL));
The original version of Ali_k's answer was almost right, but made the mistake of including the whole clause as a parameter, rather than just the value. That would cause the whole clause to be seen as a string value, rather than as code with values within it.
The idea of building up the string gradually - and, crucially, only adding a clause if there's actually value specified in the search parameters - is correct though. You also need need to build up the parameter array separately at the same rate.
Here's a version which should actually execute correctly:
$sql = "SELECT * FROM onlineevent";
$sqlfilters = "";
$parameters = array();
if( !empty($l) ){
$sqlfilters .= " event_location = ?";
$parameters[] = $l;
}
if( !empty($t) ){
$sqlfilters .= ($sqlfilters != "" ? " AND" : "")." event_type = ?";
$parameters[] = $t;
}
if( !empty($d) ){
$sqlfilters .= ($sqlfilters != "" ? " AND" : "")." event_date = ?";
$parameters[] = $d;
}
if ($sqlfilters != "") sqlfilters = "WHERE ".$sqlfilters; //add a WHERE clause if needed
$sql .= $sqlfilters; //add the filters to the initial SQL
$filter = $database->prepared_query($sql, $parameters);
Maybe I'm misunderstanding you but aren't you just looking for:
$filter = $database->prepared_query("SELECT * FROM onlineevent")
OR
$filter = $database->prepared_query("SELECT * FROM onlineevent WHERE event_location IS NOT NULL AND event_type IS NOT NULL AND event_date IS NOT NULL")
Your question is not quite clear and it is also not clear how the prepare function works, but here is my suggestion:
$array = array();
$query_parms = '';
if( !empty($l) ){
$array[] = $l;
$query_parms .= 'event_location = (?)';
}
if( !empty($t) ){
$array[] = $t;
$query_parms .= count($array) > 1 ? 'AND event_type = (?)' : 'event_type = (?)';
}
if( !empty($d) ){
$array[] = $d;
$query_parms .= count($array) > 1 ? 'AND event_date = (?)' : 'event_date = (?)';
}
$filter = $database->prepared_query("SELECT * FROM onlineevent WHERE " . $query_parms, $array);

SQLITE build query string from values passed

Hi have a function that gets passed strings via the below;
getFilterResults($id, $limit, $misc);
and in my function i connect to SQLite DB and build a query based on the values passed through, like so;
function getFilterResults($id, $limit, $misc) {
$dbConnect = new SQLite3(database);
if(!empty($limit) && !empty($id) && !empty($misc)){
$buildString = ('SELECT * FROM fields ORDER BY id DESC');
}else{
//no params filters arrived so dont filter - aka get all results
$buildString = ("SELECT * FROM fields ORDER BY id DESC");
}
$query = $dbConnect->query($buildString);
//...
}
Issue is how do I build the query if some of the values are empty and I have to decide which value uses/starts of the 'WHERE' query..obviously the first value return that is not null starts of the 'WHERE'..
I can see the long way would be to loop through each one and build - once the first value found that is not null and kick off from there but that seems unpractical and not good practice.
//check if limit set..
if($limit) {
$doLimit = 'LIMIT '.$limit.'';
}
WHERE 'id = '.id.' AND misc = '.$misc.' '.$doLimit.'
Your function taking in ($id, $limit, $misc) really prevents you from being able to do anything else than what you already are... I would recommend using prepared statements though
// Stores values for a prepared statement
$values = [];
$sqlite = new SQLite3(/* ... */);
$query = 'SELECT * FROM fields WHERE id = ? AND misc = ? ORDER BY id DESC';
$values[] = $id;
$values[] = $misc;
if ($limit) {
$query .= ' LIMIT ?';
$values[] = $limit;
}
// Use prepare and let SQLite3 escape the values for you
$stmt = $sqlite->prepare($query);
foreach ($values as $idx => $val) {
$stmt->bindValue($idx, $val);
}
$result = $stmt->execute();

Variable sql query depending on number of search parameter

I need to do a sql query in php for search some entries (so using WHERE). But the field used to search could be of variable number.
I have a page with a search form, with 4 Field. It sends via POST the fields to a search.php that make a query:
$gomme_sql = $data->query("SELECT * FROM table WHERE parameter1 = '$_POST['name1']' AND parameter2 = '$_POST['name2']' ORDER BY id ASC");
But I don't know which field are filled. So, if I don't enter anything in field1 from the search form, I shouldn't have parameter1 = '$_POST['name1']' in the WHERE query.
Have you any idea how to obtain this?
Thank you
You can check the post data before appending that clause to the query in a way like this:
edit: adding additional check:
$sql="select something from someTable ";
if(!empty($_POST['name1']) || !empty($_POST['name2'])) // add as many as you like
{
$sql.=" where ";
if(!empty($_POST['name1']))
{
$sql.="parameter1= $_POST['name1']";
}
// etc etc...
}
$sql.=" ORDER BY id ASC";
and so on.
Having said that, please, please use prepared statements with this sort of input from the user. This is SUPER open to sql injection. Please do read this: How can I prevent SQL injection in PHP?
You can write generic sql select function like this , if you need more complex SQL just modify it.
<?php
function sqlSelect($table, $sel, $wh = '', $groupby = '', $order = '', $add = '') {
$tb = $table;
if (is_array($table)) {
$tb = implode(',', $table);
}
if ($wh) {
if (is_array($wh)) {
$w = array();
foreach ($wh as $k => $v) {
$v = mysqli_real_escape_string($v);
if (is_null($v))
$w [] = "$k=null ";
else
$w [] = "$k ='$v'";
}
$wh = 'where ' . implode(' and ', $w);
}else {
$wh = "where $wh";
}
}
if ($groupby)
$groupby = "group by $groupby";
if ($order)
$order = "order by $order";
$sql = "select $sel from $tb $wh $groupby $order $add ";
return $sql;
}
//set _GET as this is console test
$_GET['name1']='Bob';
$where = array(
'name1'=>$_GET['name1']
);
echo sqlSelect('sometable' , '*' , $where) ."\n";
// select * from sometable where name1 ='Bob'
//or some complex stuff
echo sqlSelect('persons', "age,status" , array('name'=>'Maria' , 'likes'=>'PHP') , null, 'age' , 'limit 20');
//select age,status from persons where name ='Maria' and likes ='PHP' order by age limit 20

Categories