Using PHP/PDO I’m trying to set a MySQL (mysql-5.0.96) variable named “flt_status” whose default is set to ‘NULL’ and which is defined as INT(5), nullable, to NULL.
There are a number of threads here on StackOverFlow (11692773, 1391777) that cover this topic but none of them seems to work for me.
My (very abbreviated) code looks like this;
$vs = “:ip, flt_status, sunrise”;
$match = array(
‘:ip’=>”$ip”,
‘:flt_status’=>”null, PDO::PARAM_INT”,
’:sunrise’=>”$sunrise”
);
$vars = “ip, flt_status, sunrise”;
$sql = "INSERT INTO skeds ( $vars ) VALUES ( $vs )";
$query = $db_found->prepare( $sql );
$query->execute( $match );
I’ve tried a number of techniques outlined in the above discussions and others I’ve found using Google but every time the value of flt_status comes out as zero (0). I've tried using both PDO::PARAM_NULL and PDO::PARAM_INT.
I’ve included the IP and SUNRISE variables in this example so I can better understand any example the more experienced PHP/PDO programmers out there give me.
Can someone show me what I’m doing wrong?
Thanks in advance for any assistance you can offer.
You are doing several things wrong.
$vs = “:ip, flt_status, sunrise”;
First, you're apparently using smart quotes instead of straight quotes. Code needs straight quotes.
Next, you put a : prefix before ip but you missed that prefix before the other two named parameters. You need a : before each one.
$match = array(
‘:ip’=>”$ip”,
‘:flt_status’=>”null, PDO::PARAM_INT”,
’:sunrise’=>”$sunrise”
);
Next, you put null inside a quoted string. That makes it a string literal containing the word 'null', not a true null. These two concepts are different, like the difference between a chicken and a piece of paper with the word "chicken" written on it.
Also, you don't need to put quotes around the variables.
Also, for what it's worth, the : prefix on the array keys of your array are optional. In early versions of PDO, they were mandatory, but now they're not. It does no harm to keep the colon prefix, I just wanted to let you know because it could make it easier in the future to prepare arrays of parameters from another associative array, like $_POST.
$vars = “ip, flt_status, sunrise”;
This is fine except for the continued use of smart quotes.
$sql = "INSERT INTO skeds ( $vars ) VALUES ( $vs )";
Here's where you will get into trouble with your $vs because it contains only one parameter placeholder, followed by two plain column names. Passing column names in the VALUES clause is not illegal in SQL, but it makes no sense to do it.
$query = $db_found->prepare( $sql );
$query->execute( $match );
Okay, except that you are not checking for errors. Unless you have enabled PDO's attribute for throwing exceptions, you need to check the return status of prepare() and execute() because they return false if there's any error. This continues to be one of the most common mistakes among PHP developers!
Here's how I would write this code.
As you connect to PDO, enable exceptions. See http://php.net/manual/en/pdo.error-handling.php
$db_found = new PDO(...);
$db_found->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Then the code for this routine:
$placeholders = ":ip, :flt_status, :sunrise";
$values = array(
'ip' => $ip,
'flt_status' => null,
'sunrise' => $sunrise
);
$columns = "ip, flt_status, sunrise";
$sql = "INSERT INTO skeds ($columns) VALUES ($placeholders)";
$stmt = $db_found->prepare($sql);
$stmt->execute($values);
You have few mistakes in your code, try to adjust it like this:
$sql = "INSERT INTO skeds ( ip, flt_status, sunrise ) VALUES ( :ip, :flt_status, :sunrise )";
$query = $db_found->prepare( $sql );
$query->execute( array(':ip'=> $ip,
':flt_status'=>null,
':sunrise'=>$sunrise
));
When you say
$match = array(
‘:ip’=>”$ip”,
‘:flt_status’=>”null, PDO::PARAM_INT”,
’:sunrise’=>”$sunrise”
);
it's going to use that actual string "null, PDO::PARAM_INT". If you want a NULL, bind the placeholder to the PHP value null.
You have to understand the difference between strings and PHP code.
"null, PDO::PARAM_INT" IS a string. You cannot store PHP code in a string.
"null" AGAIN is not a NULL value either but a string contains word " null
If you want ot bind a null, then just bind PHP NULL value. Yes, as simple as that.
Related
While trying to insert data into the database using prepared statement the prepared statement always returns false and not complete the connection.
I'm using this connection on cpanel (not sure if that's related) tried to change the order tried to change the data type.
$conn = mysqli_connect($servername,$username,$password,$database);
// $sql=$conn->prepare("insert into asset 'assetName'=?, 'grp' ='?' ,'Descrip' = '?' , 'enteredValue' = '?', 'depreciationRate' = '?','entrydate'='?' 'availability'= '?' ,'enteredBy' = '?' , 'updatedOn' = '?' , 'isPeriodic' = '?' , 'assetType' = '?','Frequency'='?','ExitDate'='?'");
if($sql = $conn->prepare("INSERT INTO `asset`(`id`, `assetName`, `grp`, `Descrip`, `enteredValue`, `depreciationRate`, `entrydate`, `availability`, `enteredBy`, `updatedOn`, `isPeriodic`, `assetType`, `Frequency`, `ExitDate`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)")){
$sql->bind_param("sssssssssss",$name,$group,$value,$depreciation,$entryDate,$availability,$enteredBy,$updatedOn,$isPeriodic,$type,$frequency,$exitDate);
$sql->execute();
always return false and nothing has been inserted in the database.
As I said in the comments:
Well you have 14 ? and 11 s by my count. OR sssssssssss and ?????????????? Which as most of us know, is gonna throw an error as your placeholder count doesn't match your values
If you can put your data in an array you can use that array to build your query.
if($sql = $conn->prepare("INSERT INTO `asset`(`id`, `assetName`, `grp`, `Descrip`, `enteredValue`, `depreciationRate`, `entrydate`, `availability`, `enteredBy`, `updatedOn`, `isPeriodic`, `assetType`, `Frequency`, `ExitDate`) VALUES (".implode(',', array_fill(0,count($data), '?')).")")){
$sql->bind_param(str_repeat('s', count($data)),...$data);
Lets walk thought this a bit
Basically you can create your arguments with the same length as the $data with these 2 pieces of code:
implode(',', array_fill(0,count($data), '?')) //implode "?" with "," equal to the length of data
str_repeat('s', count($data)) //create 's' equal to the length of data
Then the real magic happens here with the ... "variadic" (variable length arguments):
$sql->bind_param(str_repeat('s', count($data)),...$data);
In PHP v5.6+ you can just inject the data using ... in and it will unwind it for you. Or in other words, put each array item in as a new argument.
For the fields (columns)
If you want to do the fields too, that is a bit more tricky. You have to be careful of what is in those if you put that data directly into the SQL. For example a User could edit the keys used in a $_POST request in such a way as to do SQLInjection if you just concatenate the Post Keys into the SQL.
One of the simplest ways to solve this is to have a whitelist of fields like so (matched to the column names):
//all allowed column names for this query (case sensitive)
$whitelist = ["id", "assetName", ...];
You can use array_intersect_key to retain only the data you want for the query (assuming the data has matched keys). The keys will be safe to use now in the query as they must match what is in the $whitelist.
//remove unknown keys form input data (~ retain only known ones)
//array_flip($whitelist) = ["id"=>0, "assetName"=>1, ...];
$data = array_intersect_key($_POST, array_flip($whitelist));
if($sql = $conn->prepare("INSERT INTO `asset`(`".implode("`,`", array_keys($data))."`)VALUES(".implode(',', array_fill(0,count($data), '?')).")".)){
$sql->bind_param(str_repeat('s', count($data)),...$data);
Other things
The only thing this doesn't cover is if you want all the fields in $whitelist to always be present. You can solve this with validation of the incoming data or you can merge in some empty fields to insure that all the data is present.
$default = array_fill_keys($whitelist, ''); //["id"=>"", "assetName"=>"", ...] ~ create empty "default" row
//$default['updatedOn'] = date('Y-m-d'); //you can also manually set a value
$data = array_intersect_key(
array_merge(
$default,
$_POST //["id"=>"1", ...] ~ missing assetName
), array_flip($whitelist)); //-> ["id"=>"1","assetName"=>""]
Array fill keys (similar to array fill from before) takes a list of keys, and adds a value in for each. So that gives us an array who's keys are the the values of $whitelist and an empty string for each items value. I call this a default row.
Then we merge this with our original data. Data in the first array will be overwritten by any data with matched keys for the second array ($_POST in my example). So if a key is present like id in the example above, it overwrite the empty one.
Anything not overwritten keeps the empty value from the default row we made. Then the array intersect key removes anything extra like before.
*PS I didn't test any of this so please forgive any syntax errors.
Enjoy!
You have to execute the statement once you've bound the data.
$sql->execute();
The number of parameters are also inconsistent as pointed out by the comments.
I think you don't execute your query calling the execute method :
$conn = mysqli_connect($servername,$username,$password,$database);
// $sql=$conn->prepare("insert into asset 'assetName'=?, 'grp' ='?' ,'Descrip' = '?' , 'enteredValue' = '?', 'depreciationRate' = '?','entrydate'='?' 'availability'= '?' ,'enteredBy' = '?' , 'updatedOn' = '?' , 'isPeriodic' = '?' , 'assetType' = '?','Frequency'='?','ExitDate'='?'");
if($sql = $conn->prepare("INSERT INTO `asset`(`id`, `assetName`, `grp`, `Descrip`, `enteredValue`, `depreciationRate`, `entrydate`, `availability`, `enteredBy`, `updatedOn`, `isPeriodic`, `assetType`, `Frequency`, `ExitDate`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)")){
$sql->bind_param("sssssssssss",$name,$group,$value,$depreciation,$entryDate,$availability,$enteredBy,$updatedOn,$isPeriodic,$type,$frequency,$exitDate);
sql->execute();
sql->close(); // close connection
Ok so im new to binding, here is some code that works. I learned this format from a tutorial but i imagine there is more efficent ways to do it. In my example there is 4 names but in reality i will be doing a lot of inserts and updates in a project im working on that will have 20 or so fields. I like this approach for its clarity but obviously when your talking 20 fields or more it does take a lot of real estate. Lets look at my code first.
Here are the functions it uses:
// prepare the statement
public function query($query){
$this->stmt = $this->dbh->prepare($query);
}
public function bind($param, $value, $type = null){
if (is_null($type)) {
switch (true) {
case is_int($value):
$type = PDO::PARAM_INT;
break;
case is_bool($value):
$type = PDO::PARAM_BOOL;
break;
case is_null($value):
$type = PDO::PARAM_NULL;
break;
default:
$type = PDO::PARAM_STR;
}
}
// run the binding process
$this->stmt->bindValue($param, $value, $type);
}
// execute the prepared statement
public function execute(){
return $this->stmt->execute();
}
and now the actual code
$database->query("
INSERT INTO users(
user_name,
user_password_hash,
user_email,
user_activation_hash
)
VALUES(
:user_name,
:user_password_hash,
:user_email,
:user_activation_hash
)
");
// bind the values
$database->bind(":user_name", "$this->user_name");
$database->bind(":user_password_hash", "$this->user_password_hash");
$database->bind(":user_email", "$this->user_email");
$database->bind(":user_activation_hash", "$this->user_activation_hash");
// execute the statement and insert the values into the database
$database->execute();
it just cries out for a loop, especially since i have a habit of calling post fields, input fields, variables and placeholders the same name, not sure if that is a good or a bad thing but i find its helpful for me when dealing with large forms which i will be.
in any case i could do something like this:
$placeholder_array = array(
"user_name" => "\$this->user_name",
"user_password_hash" => "\$this->user_password_hash",
"user_email" => "\$this->user_email",
"user_activation_hash" => "\$this->user_activation_hash"
);
// well use this copy to edit the array keys and keep original for the binding
$placeholder_copy = $placeholder_array;
// turn the array into a string i.e user_name, user_password_hash....
$fields = implode (", ", array_keys($placeholder_array));
// foreach to add : placeholder prefix for binding
foreach ($placeholder_copy as $key => $value){
$placeholder_copy [':'.$key] = $value;
unset($placeholder_copy[$key]);
}
// turn the copy array which has prefix :user_name into a string
$placeholders = implode (", ", array_keys($placeholder_copy));
$database->query("
INSERT INTO users($fields)
VALUES($placeholders)
");
// bind the values
foreach ($placeholder_copy as $bind_values => $value){
echo '$database->bind("'.$bind_values.'", "'.$value.'");' . "<br />";
}
// execute the statement and insert the values into the database
$database->execute();
i could then turn this into a function with parameters for passing in the associative array and the table name to keep my main code much cleaner.
Now imagine i am going to be doing any amount of these as the project im working on involves tons of big forms submitting data to users. I'm new to PDO and trying to grasp it so there maybe a simpler way of structuring these types of queries, i had a look on google and stackflow but i didnt really get what they were doing so i thought doing my own one would allow people to explain to me better what is going on, i would rather get this right starting my project than have to go back and change everything later. So is there a better approach or is this one ok?
Really appreciate any feedback and im glad now i took peoples advice on here and made the move to PDO.
No, sadly, but PDO offers no help for the matter.
Yet your own approach doesn't seem to me an efficient one either.
First of all, let me indicate that your set of functions is quite useless. I understand no such direct rewriting of API functions. PDO already doing all these things.
With raw PDO you can have even more concise code:
$stm = $pdo->prepare("INSERT INTO users VALUES(NULL,?,?,?,?)");
$data = array($this->user_name,
$this->user_password_hash,
$this->user_email,
$this->user_activation_hash
);
$stm->execute($data);
Regarding your dynamical query building, I find it too much bloated yet still insecure. Some flaws are
there is no point in bothering with ":named" placeholders as they are supposed to be human-readable but no human supposed to read them here. Not to mention PDO will convert them back into ?s before sending to mysql
another objection against named placeholders - while Mysql (as well as HTTP) can let you have a space in a field name all right, a placeholder with a space will just crash your query.
there is no real benefit in this code from your approach of the equal names - you are bound to write them by hand a dozen times each, mind you.
yet if it would be used, you have no white list to check your field list against, which could be a great security breach
too much code
no support for UPDATE query which could be extremely handy as well (if you are using mysql)
when you put this code into function, you will slip to the same bad grounds as others, tempted by the possibility of saving you whole two words!
Here is my earlier approach for the matter:
$allowed = array(
"user_name", "user_password_hash", "user_email", "user_activation_hash"
);
$sql = "INSERT INTO users SET ".pdoSet($allowed, $values);
$stmt = $dbh->prepare($sql);
$stmt->execute($values);
And here goes my current approach, the best of the best (though implemented using mysqli, not PDO), using custom placeholder for the array data:
$placeholder_array = array(
"user_name" => $this->user_name,
"user_password_hash" => $this->user_password_hash,
"user_email" => $this->user_email,
"user_activation_hash" => $this->user_activation_hash
);
$db->query("INSERT INTO users SET ?u", $placeholder_array);
or, in case of direct connection between form fields and SQL columns
$allowed = array(
"user_name", "user_password_hash", "user_email", "user_activation_hash"
);
$insert = $db->filterArray($_POST,$allowed);
$db->query("INSERT INTO users SET ?u", $insert);
This way let me use INSERT IGNORE, INSERT DELAYED, INSERT.. ON DUPLICATE UPDATE, UPDATE, UPDATE with join, and innumerable other options, supporting full SQL language.
What I've done recently and continuing to improve, is building a helper class to simplify PDO based SQL statements in my applications.
Lets take a tour with your example. You want to insert data into a user table:
INSERT INTO users(
user_name,
user_password_hash,
user_email,
user_activation_hash
) VALUES(
:user_name,
:user_password_hash,
:user_email,
:user_activation_hash
)
For inserting a single record, my SQL is constructed like this:
function myInsertSingle($PDO, $table, $ins_array) {
$SQL = "INSERT INTO `".$table."` (".dbFieldList($ins_array)
.") VALUES (".dbValuePList($ins_array).")";
...continued with preparing.
$PDO: My PDO connection.
$table: The table I want to insert to.
$ins_array: A structured array for inserting data.
For your example, the array $ins_array will look like this:
$ins_array = array(
"user_name" => "your_user",
"user_password_hash" => "your_pw_hash",
"user_email" => "your#mail.xyz",
"user_activation_hash" => "your_other_Hash"
);
Notice the similarity to your array!
Two functions are involved. The first one gives me a list of (escaped) fieldnames.
function dbFieldList($fields) {
$set = '';
foreach ($fields as $field => $item) {
$set .= "`".$field."`,";
}
return rtrim($set, ',');
}
Basically from the above array example, the function returns
`user_name`, `user_password_hash`, `user_email`, `user_activation_hash`
...which is the list of the fields needed in the INSERT-statement.
The other one does something similar for the values.
function dbValuePList($fields) {
$set = '';
foreach ($fields as $field => $item) {
$set .= ":".$field.",";
}
return rtrim($set, ',');
}
You guessed it, the result looks like this:
:user_name, :user_password_hash, :user_email, :user_activation_hash
Now you have your SQL statement which you can prepare. To bind the values, if I understand your class correctly, just use a loop which looks like this:
foreach ($ins_array as $field => $item) {
$PDO->bind(':'.$field,$item);
}
Now you can execute the statement.
Summary. Basically with my approach a single INSERT statement is reduced to this:
$ins_array = array(
"user_name" => "your_user",
"user_password_hash" => "your_pw_hash",
"user_email" => "your#mail.xyz",
"user_activation_hash" => "your_other_Hash"
);
myInsertSingle($PDO, 'your_table', $ins_array);
If this looks just like another piece of overhead, keep in mind, I use these functions in other SQL statements like SELECT, UPDATE, DELETE and so on.
The Gist
I want to perform an SQL query that depends on a variable number of parameters in my GET without being vulnerable to SQL injection.
The Parameters
My URL can be formed like this:
https://www.example.com/index.php?param1=blah1,param2=blah2,param3=a,b,c
or like this:
https://www.example.com/index.php?param1=blah1,param2=blah2,param3=a,b,c,d,e,f,g
In other words, param3 can have a variable number of comma-delimited parameters a,b,c,etc.
The White-list
I check to make sure that all parameters in a,b,c,etc. are in an approved white-list before I perform the query.
// $valid_params is an array of pre-approved parameters.
$arr = explode(',', clean($_GET['param3']));
$params = Array();
foreach($arr as $param){
if(in_array($param, $valid_params)){
array_push($params, $param);
}
}
The Query
I set up my database connection like this (with MySQL):
$db_connection = new PDO("mysql:host={$DB_HOST};dbname={$DB_NAME}",$DB_USER,$DB_PASS);
$db_connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db_connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
And I want to perform a query like this (except safely):
$comma_separated_params = implode(',',$params);
$result = $db_connection->query("SELECT {$comma_separated_params} FROM some_table");
The Goal
Does anyone know how I could do this safely and efficiently?
Depending on your concern for overhead, you could just SELECT * and then filter the array in PHP - if the parameter is never sent to the database then there is no room for injection.
However it's not exactly the most elegant solution. Here's how I'd do it:
$comma_separated_params =
implode(
",",
array_map(
function($a) {return "`".$a."`";},
array_intersect(
explode(",",$_GET['param3']),
$valid_params
)
)
)
);
That one-line-wonder (with newlines added for clarity) will take the $_GET['param3'] variable, split it on commas, intersect it with your valid parameters (instead of your foreach loop), wraps each element in backticks (see note below) and finally glues them together with commas.
See, backticks allow you to use literally any string as a field name. Usually it's to allow keywords as names, but it can also allow for column names with spaces, and so on. The only character that has meaning within the backticks are backslashes and backticks - which it is safe to assume are not present since they'd have to be in your list of $valid_params to get this far.
Whitelisting is the way to go here. If you only allow things in that you've already specifically defined you should be okay. As for how efficient, this is all relative. The version you're using will perform well for relatively small lists, such as those with under 100 columns, so I wouldn't worry.
Bonus points for using PDO.
There's a chance your definition of 'allowed' columns and what's actually in the database might diverge. A more relaxed specification might be to grab the fields using SHOW FIELDS for the table in question and only allow those.
If you are only allowing a specific list of predefined values to be passed in param 3, and you are comparing the input values against them, I don;t think you have any injection exposure, as you then have full control over the value that ultimately go into your $comma_seperated_params variable.
This needs some work to finish, but with parameter binding it would look like this:
$binding = array();
$selects = array();
foreach ( $params as $value ) {
$binding[] = ':' . $value;
$selects = '?';
}
$select = implode(',', $select);
$result = $db_connection->prepare("SELECT $select FROM some_table");
foreach ( $binding as $key => $bind ) {
$result->bindParam($key, $bind, PDO::PARAM_STR);
}
$result->execute();
PDO::prepare will help you. This is exactly is recommended by experts. Never use mysql_real_escape_string (string). Always go for prepared statements.
I have an insert statement that inserts variables collected from a form POST on the previous page. If the variables from the form are not filled in it fails on insert (presumably because it is inserting an empty string...) I have the dataype set to allow NULL values - how do I insert null values if the field was left empty from the form POST?
$query = "
INSERT INTO songs (
userid,
wavURL,
mp3URL,
genre,
songTitle,
BPM
) VALUES (
'$userid',
'$wavFile',
'$mp3File',
'$genre',
'$songTitle',
'$BPM'
)
";
$result = mysql_query($query);
The exact manner depends on if you are writing the query or binding parameters to a prepared statement.
If writing your own, it would look something like this:
$value = empty($_POST['bar']) ? null : $_POST['bar'];
$sql = sprintf('INSERT INTO foo (bar) VALUES (%s)',
$value === null ? 'NULL', "'".mysql_real_escape_string($value)."'");
$result = mysql_query($sql);
The main point is that you need to pass in the string NULL (without quotes) if the value should be null and the string 'val' if the value should be "val". Note that since we are writing string literals in PHP, in both cases there is one more pair of quotes in the source code (this makes one pair in the first case, two pairs in the second).
Warning: When inserting to the database directly from request variables, it is very easy to be wide open to SQL injection attacks. Do not be another victim; read about how to protect yourself and implement one of the universally accepted solutions.
For what I understand when something is not filled the post variable is not set as an empty value but rather not set at all so in php you'd do for example:
$genre = isset($_POST['genre']) ? $_POST['genre'] : NULL;
Here's how I do it. I don't like sending anything to an SQL query right from POST (always sanitize!) in the following cas you just run through the POST vars one by one and assign them to a secondary array while checking for 0 length strings and setting them to NULL.
foreach ($_POST as $key => $value) {
strlen($value)=0 ? $vars[$key] = NULL : $vars[$key] = $value
}
Then you can build your SQL query from the newly created $vars[] array.
As Jon states above, this would be the place to also escape strings, strip code and basically do all your server side validation prior to data being inserted into the db.
I'm using the WordPress DB interface to insert some nullable row values. The function:
$wpdb->insert( $table, $data, $format );
takes an array of values in the $data param. If I exclude certain field values from the $data array, the fields show up null in the table. However, I want to be able to populate the $data array with values for all the fields, but make those values null when I need to. However, if I pass values that = NULL, the database values are not null, but blank. What should I do?
Looking at the wpdb source code, the "insert" method ends up being mapped into SQL that places every field value between single-quotes (line 1233):
$sql = "{$type} INTO `$table` (`" . implode( '`,`', $fields ) . "`)
VALUES ('" . implode( "','", $formatted_fields ) . "')";
To insert a real NULL, you need to place an unadorned NULL word into that expression, and it's not allowed for. So what is being inserted is the 4-letter string "NULL".
Note that this method leaves it to the user to be inserting the proper data types and depends on MySQL to automatically convert quoted e.g. numerics into real numeric values.
Looks like the following works:
Instead of:
$nullable_value = NULL;
do:
$nullable_value = mysql_escape_string("NULL");
For explanation see thread answers here and here.
EDIT:
On second glance, this won't work in DB, for the reason #le dorfier states...