SQL injection vulnerability with prepared statement that uses concatenation - php

Would parameterized code that uses concatenation in this way have a SQL injection vulnerability? I assume that it would, but I'm not certain what POST data would exploit it.
foreach ($_POST as $key => $value) {
$columns .= ($columns == "") ? "" : ", ";
$columns .= $key;
$holders .= ($holders == "") ? "" : ", ";
$holders .= ":".$value;
}
$sql = "INSERT INTO request ($columns) VALUES ($holders)";
$stmt = $this->pdo->prepare($sql);
foreach($_POST as $key => $value) {
$field = ":".$key;
$stmt->bindValue($field, $value);
}
$stmt->execute();

You need an array to store all the columns of request table and check if the post key is present in the array.
PHP code:
$request_columns = array('column1','column2');// all the columns of request table
foreach ($_POST as $key => $value) {
if(in_array($key,$request_columns)){
$columns .= ($columns == "") ? "" : ", ";
$columns .= $key;
$holders .= ($holders == "") ? "" : ", ";
$holders .= ":".$key;
}
}
$sql = "INSERT INTO request ($columns) VALUES ($holders)";
$stmt = $this->pdo->prepare($sql);
foreach($_POST as $key => $value) {
if(in_array($key,$request_columns)){
$field = ":".$key;
$stmt->bindValue($field, $value);
}
}
$stmt->execute();

#KetanYekale is correct that you need to filter $_POST for known column names.
Here's an alternative way of doing it, using some PHP builtin functions.
$request_columns = array('column1','column2');// all the columns of request table
# get a subset of $_POST, only those that have keys matching the known request columns
$post_only_columns = array_intersect_key(
$_POST,
array_flip($request_column)
);
# make sure columns are delimited like `name` in case they are SQL reserved words
$columns = implode(array_map(function ($col) { return "`$col`"; }, array_keys($post_only_columns), ', ';
# use ? positional holders, not named holders. it's easier in this case
$holders = implode(array_fill(1, count($post_only_columns), '?'), ', ');
$sql = "INSERT INTO request ($columns) VALUES ($holders)";
$stmt => $this->pdo->prepare($sql);
# no need to bindValue() or use a loop, just pass the values to execute()
$stmt->execute( array_values($post_only_columns) );
PHP has many Array functions that you can use in different scenarios to make your code faster and more concise. You can use these functions to avoid writing some types of foreach looping code.

Related

PHP PDO : Binding value in one foreach loop regarding insert into on duplicate key update

I'm using two foreach loops to bind the values to the positional parameters, one for INSERT INTO, the other for DUPLICATE KEY UPDATE. I was trying to reduce it to just one loop. But array_merge didn't work. Can someone tell me how can I get it done by using just one loop?
Here's an example:
$facts = array("age","race");
$ifield = "";
$ufield="";
$comma = "";
foreach($facts as $f)
{
$$f = (isset($_POST["$f"])?$_POST["$f"]:"");
if ($$f)
{
$ifield .= $comma.$f;
$ufield .= "{$comma}{$f} = ?";
$comma = ",";
}
}
$i = explode(',',$ifield);
$in = str_repeat('?,', count($i) - 1) . '?';
$sql .= "INSERT INTO `users` (user_id,model_no,{$ifield}) VALUES(?,?,$in)";
$sql .= " ON DUPLICATE KEY UPDATE $ufield";
$users = $dbh->prepare($sql);
$user_id = $_SESSION['user_id'];
$model_no = $_POST["model_no"];
$users->bindValue(1,$user_id);
$users->bindValue(2,$model_no);
/****************************************************************/
$i = 3;
foreach($facts as $f)
{
global $$f;
if ($$f)
{
$users->bindValue($i++, $$f);
}
}
foreach($facts as $f)
{
global $$f;
if ($$f)
{
$users->bindValue($i++, $$f);
}
}
$users->execute();
You don't need to use bound parameters in the ON DUPLICATE KEY clause. Use:
$ufield = "$comma $f = VALUES($f)";
Using VALUES(columndname) in the ON DUPLICATE KEY clause makes it use the value that would have been inserted into that column. See the documentation

PHP generate sql where clause from array

I would like to create dinamically in my php class a where clause from a array where are defined search fields.
$search = array('brand' => 'something', 'model' => 'something');
$myclass->testarr($search);
CLASS
public function testarr($search){
if (!empty($search)){
foreach ($search as $key => $value) {
$where = $key . " = " . $value;
}
$clause = !empty($where) ? 'WHERE' : '';
$result = $this->db->mysqli->query
("SELECT * FROM tb1 $clause $where");
}
}
My problem is to manage a clause with more than one field by entering the suffix AND. How could I do that? Thanks
I would advice to do this:
$where = array();
if (!empty($search) && is_array($search)) {
foreach ($search as $key => $value) {
$where[] = $key . " = " . $value;
}
}
if (!empty($where))
$query = sprintf('SELECT * FROM tb1 WHERE %s', implode('AND ', $where));
else
$query = 'SELECT * FROM tb1';
Using implode makes things easier.
Beware however of escaping issues, as your code is prone to security issues.
There is one flaw with your code: $where = $key . " = " . $value; will overwrite $where in each iteration, you need to use .= to concatenate. Then this could be done e.g. the following way
$where = "";
foreach ($search as $key=>$value) {
if (!empty($where)) $where .= " AND ";
$where .= $key . " = " . $value;
}
$clause = !empty($where) ? 'WHERE '.$where : '';
This will add a AND before every condition, starting from the second (because for the first the if will fail).
I suggest researching prepared statements, these will make your code alot more secure and once you understood the concept, they become quite easy to handle (imo). Because if that is most of your code at the moment, you are quite vulnerable to SQL injections.

Updating via PDO and automatic function

I'm preparing my own function due to hurry the updates automatically.
I have that code:
$allowededitablefields = array('mail');
$userid = $_GET['uid'];
$query = 'UPDATE users SET ';
foreach ($_POST as $key => $value) {
if(!in_array($key,$allowededitablefields)) {
unset($_POST[$key]);
}
else {
$query .= $key.' = :'.$key.',';
}
}
$query = substr($query, 0, -1);
$query .= ' WHERE id='.$userid;
$statement = $this->_db->prepare($query);
foreach ($_POST as $key => $value) {
$statement->bindParam(':'.$key,$value);
}
$statement->execute();
If in $allowededitablefields array, I have only a value, it works properly, but if I push some values to the array, for example $allowededitablefields = array('mail','country',...); the fields in the table take the same values.
$value holds the value of the last iteration when the foreach loop ends.
change the bindParam to this.
$statement->bindParam(':'.$key,$_POST[$key]);
This should work, but your approach is fundamentally flawed. It undermines the whole purpose of prepared statements.

way of doing flexible "INSERT INTO" query with PHP mysqli?

Okay this is going to be a little complex.
But right now i am using a homewritten function to create a query for creating inserting a page into the db.
And i was wondering if there was a smarter way to do a flexible "insert into" method.
The problem is that i have some fields which are optional to type in when creating a page so right now i am using this where i am running through all fields and checks whether they are set or not. :
//creates an Array which can be used to make a MySQL query
function createQueryArray($new) {
if (isset($this->users_id))
$this->query_array['users_id'] = mysql_real_escape_string($this->users_id);
if (isset($this->pagename))
$this->query_array['pagename'] = mysql_real_escape_string($this->pagename);
if (isset($this->seo_pagetitle))
$this->query_array['seo_pagetitle'] = mysql_real_escape_string($this->seo_pagetitle);
if (isset($this->seo_description))
$this->query_array['seo_description'] = mysql_real_escape_string($this->seo_description);
if (isset($this->seo_keywords))
$this->query_array['seo_keywords'] = mysql_real_escape_string($this->seo_keywords);
if (isset($this->seo_robots))
$this->query_array['seo_robots'] = mysql_real_escape_string($this->seo_robots);
if (isset($this->seo_canonical))
$this->query_array['seo_canonical'] = mysql_real_escape_string($this->seo_canonical);
if (isset($this->type))
$this->query_array['page_type'] = mysql_real_escape_string($this->type);
//$this->query_array['last_edited'] = date("Y-m-d H:i:s");
}
Afterwards i am calling this function with the array and the table i wanna insert the page into:
function createInsertStm($arr, $table) {
$mysqlQuery = ("INSERT INTO $table (");
$insert = "";
$values = "";
if (is_array($arr))
foreach ($arr as $key => $value) {
if ($insert == "")
$insert .= $key;
else
$insert .= ', ' . $key;
if ($values == "")
$values .= (preg_match('/(MAX\(id\))(.*?)/', $value)) ? $value : '"' . $value . '"';
else
$values .= (preg_match('/(MAX\(id\))(.*?)/', $value)) ? "," . $value : ',"' . $value . '"';
}
$mysqlQuery .= $insert;
$mysqlQuery .= ') VALUES (';
$mysqlQuery .= $values;
$mysqlQuery .= ')';
return $mysqlQuery;
}
$db->query($queryArray["pages"]);
Is it posible to use a prepared statement and then just skip some of the fields or something similar?
If you setup a default value in the table definition, then you can omit the value on insert.
Check the following page for more info on default values:
http://dev.mysql.com/doc/refman/5.0/en/data-type-defaults.html

PHP Implode Associative Array

So I'm trying to create a function that generates a SQL query string based on a multi dimensional array.
Example:
function createQueryString($arrayToSelect, $table, $conditionalArray) {
$queryStr = "SELECT ".implode(", ", $arrayToSelect)." FROM ".$table." WHERE ";
$queryStr = $queryStr.implode(" AND ",$conditionalArray); /*NEED HELP HERE*/
return $queryStr;
}
$columnsToSelect = array('ID','username');
$table = 'table';
$conditions = array('lastname'=>'doe','zipcode'=>'12345');
echo createQueryString($columnsToSelect, $table, $conditions); /*will result in incorrect SQL syntax*/
as you can see I need help with the 3rd line as it's currently printing
SELECT ID, username FROM table WHERE
lastname AND zipcode
but it should be printing
SELECT ID, username FROM table WHERE
lastname = 'doe' AND zipcode = '12345'
You're not actually imploding a multidimensional array. $conditions is an associative array.
Just use a foreach loop inside your function createQueryString(). Something like this should work, note it's untested.:
$terms = count($conditionalArray);
foreach ($conditionalArray as $field => $value)
{
$terms--;
$queryStr .= $field . ' = ' . $value;
if ($terms)
{
$queryStr .= ' AND ';
}
}
Note: To prevent SQL injection, the values should be escaped and/or quoted as appropriate/necessary for the DB employed. Don't just copy and paste; think!
function implodeItem(&$item, $key) // Note the &$item
{
$item = $key . "=" . $item;
}
[...]
$conditionals = array(
"foo" => "bar"
);
array_walk($conditionals, "implodeItem");
implode(' AND ', $conditionals);
Untested, but something like this should work. This way you can also check if $item is an array and use IN for those cases.
You will have to write another function to process the $conditionalArray, i.e. processing the $key => $value and handling the types, e.g. applying quotes if they're string.
Are you just dealing with = condition? What about LIKE, <, >?
Forgive me if its not too sexy !
$data = array('name'=>'xzy',
'zip'=>'3432',
'city'=>'NYK',
'state'=>'Alaska');
$x=preg_replace('/^(.*)$/e', ' "$1=\'". $data["$1"]."\'" ',array_flip($data));
$x=implode(' AND ' , $x);
So the output will be sth like :
name='xzy' AND zip='3432' AND city='NYK' AND state='Alaska'
I'd advise against automated conditionals creation.
Your case is too local, while there can be many other operators - LIKE, IN, BETWEEN, <, > etc.
Some logic including several ANDs and ORs.
The best way is manual way.
I am always doing such things this way
if (!empty($_GET['rooms'])) $w[]="rooms='".mesc($_GET['rooms'])."'";
if (!empty($_GET['space'])) $w[]="space='".mesc($_GET['space'])."'";
if (!empty($_GET['max_price'])) $w[]="price < '".mesc($_GET['max_price'])."'";
Though if you still want it with this simple array, just iterate it using
foreach ($conditions as $fieldname => $value)...
and then combine these variables in the way you need. you have 2 options: make another array of this with field='value' pairs and then implode it, or just concatenate, and substr trailing AND at the end.
I use a variation of this:
function implode_assoc($glue,$sep,$arr)
{
if (empty($glue)) {$glue='; ';}
if (empty($sep)) {$sep=' = ';}
if (is_array($arr))
{
foreach ($arr as $k=>$v)
{
$str .= $k.$sep.$v.$glue;
}
return $str;
} else {
return false;
}
};
It's rough but works.
Here is a working version:
//use: implode_assoc($v,"="," / ")
//changed: argument order, when passing to function, and in function
//output: $_FILES array ... name=order_btn.jpg / type=image/jpeg / tmp_name=G:\wamp\tmp\phpBDC9.tmp / error=0 / size=0 /
function implode_assoc($arr,$glue,$sep){
$str = '';
if (empty($glue)) {$glue='; ';}
if (empty($sep)) {$sep=' = ';}
if (is_array($arr))
{
foreach ($arr as $key=>$value)
{
$str .= $key.$glue.$value.$sep;
}
return $str;
} else {
return false;
}
}
I know this is for the case of a pdo mysql type.. but what i do is build pdo wrapper methods, and in this case i do this function that helps to build the string, since we work with keys, there is no possible way to mysql inject, since i know the keys i define / accept manually.
imagine this data:
$data=array(
"name"=>$_GET["name"],
"email"=>$_GET["email"]
);
you defined utils methods...
public static function serialize_type($obj,$mode){
$d2="";
if($mode=="insert"){
$d2.=" (".implode(",",array_keys($obj)).") ";
$d2.=" VALUES(";
foreach ($obj as $key=>$item){$d2.=":".$key.",";}
$d2=rtrim($d2,",").")";}
if($mode=="update"){
foreach ($obj as $key=>$item){$d2.=$key."=:".$key.",";}
}
return rtrim($d2,",");
}
then the query bind array builder ( i could use direct array reference but lets simplify):
public static function bind_build($array){
$query_array=$array;
foreach ($query_array as $key => $value) { $query_array[":".$key] = $query_array[$key]; unset($query_array[$key]); } //auto prepair array for PDO
return $query_array; }
then you execute...
$query ="insert into table_x ".self::serialize_type( $data, "insert" );
$me->statement = #$me->dbh->prepare( $query );
$me->result=$me->statement->execute( self::bind_build($data) );
You could also go for an update easy with...
$query ="update table_x set ".self::serialize_type( $data, "update" )." where id=:id";
$me->statement = #$me->dbh->prepare( $query );
$data["id"]="123"; //add the id
$me->result=$me->statement->execute( self::bind_build($data) );
But the most important part here is the serialize_type function
Try this
function GeraSQL($funcao, $tabela, $chave, $valor, $campos) {
$SQL = '';
if ($funcao == 'UPDATE') :
//Formata SQL UPDATE
$SQL = "UPDATE $tabela SET ";
foreach ($campos as $campo => $valor) :
$SQL .= "$campo = '$valor', ";
endforeach;
$SQL = substr($SQL, 0, -2);
$SQL .= " WHERE $chave = '$valor' ";
elseif ($funcao == 'INSERT') :
//Formata SQL INSERT
$SQL = "INSERT INTO $tabela ";
$SQL .= "(" . implode(", ", array_keys($campos) ) . ")";
$SQL .= " VALUES ('" . implode("', '", $campos) . "')";
endif;
return $SQL;
}
//Use
$data = array('NAME' => 'JOHN', 'EMAIL' => 'J#GMAIL.COM');
GeraSQL('INSERT', 'Customers', 'CustID', 1000, $data);

Categories