PHP MYSQL - empty variable fails on insert - php

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.

Related

Prepared statement return false always

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

Is this dynamic SQL query generation safe from injections?

Is there something that may escape the sanitation in my script or is it safe from most SQL injections? The way I understand it, if you pass query as prepared argument, it does not matter how the query was build, right?
Edit2: I edited the code to reflect the suggestions of binding the $_POST values
$q = $pdo->prepare('SHOW COLUMNS FROM my_table');
$q->execute();
$data = $q->fetchAll(PDO::FETCH_ASSOC);
$key = array();
foreach ($data as $word){
array_push($key,$word['Field']);
}
$sqlSub= "INSERT INTO other_table(";
$n = 0;
foreach ($key as $index){
$sqlSub = $sqlSub.$index.", ";
$n = $n + 1;
}
$sqlSub = $sqlSub.") VALUES (";
for ($i=1; $i<$n;$i++){
$sqlSub = $sqlSub."?, ";
}
$sqlSub = $sqlSub.."?)";
$keyValues = array();
for($i=0;i<n;$i++){
array_push($keyValues,$_POST[$key[$i]]);
}
$q->$pdo->prepare($sqlSub);
q->execute($keyValues);
EDIT: This is how the final query looks like after suggested edits
INSERT INTO other_table($key[0],...,$key[n]) VALUES (?,...,nth-?);
No. The example code shown is not safe from most SQL Injections.
You understanding is entirely wrong.
What matters is the SQL text. If that's being dynamically generated using potentially unsafe values, then the SQL text is vulnerable.
The code is vulnerable in multiple places. Even the names of the columns are potentially unsafe.
CREATE TABLE foo
( `Robert'; DROP TABLE Students; --` VARCHAR(2)
, `O``Reilly` VARCHAR(2)
);
SHOW COLUMNS FROM foo
FIELD TYPE NULL
-------------------------------- ---------- ----
Robert'; DROP TABLE Students; -- varchar(2) YES
O`Reilly varchar(2) YES
You would need to enclose the column identifiers in backticks, after escaping any backtick within the column identifier with another backtick.
As others have noted, make sure your column names are safe.
SQL injection can occur from any external input, not just http request input. You can be at risk if you use content read from a file, or from a web service, or from a function argument from other code, or the return value of other code, or even from your own database... trust nothing! :-)
You could make sure the column names themselves are escaped. Unfortunately, there is no built-in function to do that in most APIs or frameworks. So you'll have to do it yourself with regular expressions.
I also recommend you learn about PHP's builtin array functions (http://php.net/manual/en/ref.array.php). A lot of your code could be quicker to develop the code, and it will probably better runtime performance too.
Here's an example:
function quoteId($id) {
return '`' . str_replace($id, '`', '``') . '`';
}
$q = $pdo->query("SHOW COLUMNS FROM my_table");
while ($field = $q->fetchColumn()) {
$fields[] = $field;
}
$params = array_intersect_key($_POST, array_flip($fields));
$fieldList = implode(",", array_map("quoteId", array_keys($params)));
$placeholderList = implode(",", array_fill(1, count($params), "?"));
$sqlSub = "INSERT INTO other_table ($fieldList) VALUES ($placeholderList)";
$q = $pdo->prepare($sqlSub);
$q->execute($params);
In this example, I intersect the columns from the table with the post request parameters. This way I use only those post parameters that are also in the set of columns. It may end up producing an INSERT statement in SQL with fewer than all the columns, but if the missing columns have defaults or allow NULL, that's okay.
There is exactly one way to prevent SQL injection: to make sure that the text of your query-string never includes user-supplied content, no matter how you may attempt to 'sanitize' it.
When you use "placeholders," as suggested, the text of the SQL string contains (probably ...) question marks ... VALUES (?, ?, ?) to indicate each place where a parameter is to be inserted. A corresponding list of parameter values is supplied separately, each time the query is executed.
Therefore, even if value supplied for last_name is "tables; DROP TABLE STUDENTS;", SQL will never see this as being "part of the SQL string." It will simply insert that "most-unusual last_name" into the database.
If you are doing bulk operations, the fact that you need prepare the statement only once can save a considerable amount of time. You can then execute the statement as many times as you want to, passing a different (or, the same) set of parameter-values to it each time.

How to only update an sql table column if a variable is not empty with php?

I am getting my variables from form fields using php :
$url=$_POST['url'];
$tags=$_POST['tags'];
$skillArea=$_POST['skill_area'];
$description=$_POST['description'];
$slideshowImageFileName=($_FILES['imageNameSlideshow']['name']);
But when I run my sql insert query, I get an error if one of the variables is empty, so I have taken to write if statements to deal with this to rewrite the query string, but surely, that's not the answer? It seems very messy
if(empty($slideshowImageFileName)){
$query1="INSERT INTO portfolio (item_name,image_path,description,url) VALUES('$itemName','$imageFileName','$description','$url')";
}else{
$query1="INSERT INTO portfolio (item_name,image_path,description,url,slideshow_image_path) VALUES('$itemName','$imageFileName','$description','$url','$slideshowImageFileName')";
}
I suppose you are looking for something like this:
$slideshowImageFileName = (isset($_FILES['imageNameSlideshow']['name']) && !empty($_FILES['imageNameSlideshow']['name'])) ? $_FILES['imageNameSlideshow']['name'] : NULL;
This will check if the name of the slideshowimage is set and not empty. if it is NULL will be assigned to the variable, if its correct the value will be assigned.
You could replace NULL with "" if you want an empty string to be added.
Try to set the value of $slideshowImageFileName to empty string or a single space as your database table will accept, and use the second query always.
if(empty($slideshowImageFileName)){
$slideshowImageFileName = "";
}
$query1="INSERT INTO portfolio (item_name,image_path,description,url,slideshow_image_path) VALUES('$itemName','$imageFileName','$description','$url','$slideshowImageFileName')";
I am agreed with Mr. Ray. But there is another solution apart from that. Probably slideshow_image_path field on the table doesn't allow null. So you may change the attribute by allowing null and it will work.
I'd probably construct a builder if I'm sure I'll get a lot of optional data.
Like this:
$acceptedKeys = array
('item_name',
'image_path',
'description',
'url',
'slideshow_image_path');
$inserts = array();
foreach($_GET as $key => $var) {
if(in_array($key, $acceptedKeys)) {
// clean and validate your keys here!
$inserts[$key] = $var;
}
}
$customKeys = implode(array_keys($inserts), ',');
$customValues = implode($inserts, ',');
$query = "INSERT INTO portfolio ($customKeys) VALUES($customValues)";
There's a few options to this.
Simplest one is to make sure the variables are always set, even if not passed through:
//Set up your database connection as normal, check errors etc.
$db = mysqli_connect($host,$user,$password,$db);
$url = isset($_POST['url']) ? mysqli_real_escape_string($db, $_POST['url']) : "";
$tags= isset($_POST['tags']) ? mysqli_real_escape_string($db, $_POST['tags']) : "";
Escaping data is good practice :) In your INSERT query you'll still need to wrap the values in quotes, or you could do that in the above code as per your preference.
http://uk3.php.net/manual/en/mysqli.construct.php

Does this work to stop sql injections

I have been using the block of code below to supposedly stop sql injections. It is something someone showed me when I first started php(which was not that long ago)
I place it in every page just as shown on the open. I am wondering if it is effective? I do not know how to test for sql injections
<?php
//Start the session
session_start();
//=======================open connection
include ('lib/dbconfig.php');
//===============This stops SQL Injection in POST vars
foreach ($_POST as $key => $value) {
$_POST[$key] = mysql_real_escape_string($value);
}
foreach ($_GET as $key => $value) {
$_GET[$key] = mysql_real_escape_string($value);
}
My typical insert and update queries look like this
$insert = ("'$email','$pw','$company', '$co_description', '$categroy', '$url', '$street', '$suite', '$city', '$state', '$zip', '$phone', '$date', '$actkey'");
mysql_query("INSERT INTO provider (email, pw, company, co_description, category, url, street, suite, city, state, zip, phone, regdate, actkey) VALUES ($insert)") or die ('error ' . mysql_error());
mysql_query("UPDATE coupon SET head='$_POST[head]', fineprint='$_POST[fineprint]', exdate='$exdate', creationdate=NOW() WHERE id='$cid'") or die ('error ' . mysql_error());
That's somewhat effective, but it's suboptimal -- not all of the data you receive in _GET and _POST will go into the database. Sometimes you might want to display it on the page instead, in which case mysql_real_escape_string can only hurt (instead, you'd want htmlentities).
My rule of thumb is to only escape something immediately before putting it into the context in which it needs to be escaped.
In this context, you'd be better of just using parameterized queries -- then escaping is done for you automatically.
This is not enough.
1. You're missing cookies, $_COOKIE variable.
2. If you use $_REQUEST you're in trouble.
3. You didn't show your queries, you must enquote each variable with single quotes '' when you put it into query (especiall when the data is supposted to be an integer and you might think that quote is not necessary in that case, but that would be a big mistake).
4. Data used in your query could come from other source.
The best way is to use data binding and have the data escaped automatically by the driver, this is available in PDO extension.
Example code:
$PDO = new PDO('mysql:dbname=testdb;host=127.0.0.1' $user, $password);
$stmt = $PDO->prepare("SELECT * FROM test WHERE id=? AND cat=?");
$stmt->execute(array($_GET["id"], $_GET["cat"]));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
You can also bind data using string keys:
$stmt = $PDO->prepare("SELECT * FROM test WHERE id = :id AND cat = :cat");
$stmt->execute(array(":id" => $_GET["id"], ":cat" => $_GET["cat"]));
If you want to learn PDO, you might find useful these helper functions I use:
http://www.gosu.pl/var/PDO.txt
PDO_Connect(dsn, user, passwd) - connects and sets error handling.
PDO_Execute(query [, params]) - only execute query, do not fetch any data.
PDO_InsertId() - last insert id.
PDO_FetchOne(query [, params]) - fetch 1 value, $count = PDO_FetchOne("SELECT COUNT(*) ..");
PDO_FetchRow(query [, params]) - fetch 1 row.
PDO_FetchAll(query [, params]) - fetch all rows.
PDO_FetchAssoc(query [, params]) - returns an associative array, when you need 1 or 2 cols
1) $names = PDO_FetchAssoc("SELECT name FROM table");
the returned array is: array(name, name, ...)
2) $assoc = PDO_FetchAssoc("SELECT id, name FROM table")
the returned array is: array(id=> name, id=>name, ...)
3) $assoc = PDO_FetchAssoc("SELECT id, name, other FROM table");
the returned array is: array(id=> array(id=>'',name=>'',other=>''), id=>array(..), ..)
Each of functions that fetch data accept as 2nd argument parameters array (which is optional), used for automatic data binding against sql injections. Use of it has been presented earlier in this post.
Kind of.
The mysql_real_escape_string function takes the given variable and escapes it for SQL queries. So you can safely append the string into a query like
$safe = mysql_real_escape_string($unsafe_string);
$query = 'SELECT * FROM MyTable WHERE Name LIKE "' . $safe . '" LIMIT 1';
It does NOT protect you against someone putting malicious code into that query to be displayed later (i.e. XSS or similar attack). So if someone sets a variable to be
// $unsafe_string = '<script src="http://dangerous.org/script.js"></script>'
$safe = mysql_real_escape_string($unsafe_string);
$query = 'UPDATE MyTable SET Name = "' . $safe . '"';
That query will execute as you expect, but now on any page where you print this guy's name, his script will execute.
This is completely WRONG approach.
In fact, you are mimicking infamous magic quotes, which is acknowledged as a bad practice. With all it's faults and dangers.
To help you understand why your initial way was wrong Magic quotes in PHP
To help you understand why escaping has nothing to do with "data safety" yet not sufficient to protect your query: Replacing mysql_* functions with PDO and prepared statements
To help you understand when prepared statements not sufficient either and what to do in these cases: In PHP when submitting strings to the database should I take care of illegal characters using htmlspecialchars() or use a regular expression?
this is not to prevent SQL Injection the real escape method only add \ to the dangerous
characters like " or ' so a string with "hi"do'like" will become "hi\"do\'like\" so it is
less dangerous
this method is not always usefull ; in case you want to display the content of tha escaped
variable in a page it will only destroy it and make it less readable

passing a null value to mysql database

A user fills out a form and if they choose to not fill out a field that is not required php does this:
if($_SESSION['numofchildren']=="")
$_SESSION['numofchildren']=null;
But when I use the session variable in a mysql query, the result is not null, but is 0. The column is a tinyint(4) that allows NULL.
Why am I getting a 0 instead of NULL?
Probably because PHP doesn't convert 'null' into 'NULL'. You are probably just inserting an empty value.
INSERT INTO TABLE (`Field`) ('')
You probably have the default for the column set to '0', and that means that it will insert a 0 unless you specify a number or NULL
INSERT INTO TABLE ('Field') (NULL)
To fix this, check for Null Values before you do the query.
foreach($values as $key => $value)
{
if($value == null)
{
$values[$key] = "NULL";
}
}
I have a feeling that prepared statements will have the foresight to do this automagically. But, if you are doing inline statements, you need to add a few more things.
MySQL values must have quotes around them, but Nulls don't. Therefore, you are going to need to quote everything else using this
foreach($values as $key => $value)
{
if($value == null)
{
$values[$key] = "NULL";
}
else
{
// Real Escape for Good Measure
$values[$key] = "'" . mysql_real_escape_string($value) . "'";
}
}
Then, when you create the statement, make sure to not put quotes around any values
$SQL = "INSERT INTO TABLE (Field) VALUES(".$values['field'].")";
turns into
$SQL = "INSERT INTO TABLE (Field) VALUES("Test Value")";
or
$SQL = "INSERT INTO TABLE (Field) VALUES(NULL)";
Have a look at the table definition for whichever table you're inserting into. The 'default' value for that field is probably set to zero.
The version of MySql you are using is quite important in determining precisely how MySql treats Data Type Default Values.
The above link says:
For numeric types, the default is 0,
with the exception that for integer or
floating-point types declared with the
AUTO_INCREMENT attribute, the default
is the next value in the sequence.
You all where probably right, but all I had to do is put quotes around the null.
if($_SESSION['numofchildren']=="")
$_SESSION['numofchildren']='NULL';
I had the same problem some minutes ago, but then I figured it out. In my case I was making the query with the NULL variables between quotes like these ", '. Let me explain myself...
This is what you want to do:
INSERT INTO `tbl_name` (`col1`, `col2`) VALUES (NULL,"some_value");
So if you want to use a NULL variable it should be "NULL", like this:
$var1="NULL"; $var2="some_value";
Now, if you want to use $var2, you will type '$var2' in the query, but you shouldn't do the same for $var1:
INSERT INTO `tbl_name` (`col1`, `col2`) VALUES ($var1,'$var2');
If you put $var1 between quotes, you'll get a 0 instead NULL.
For me it didn't work to put NULL var in database, I used var char(2).
So I just made 2 queries. This way it will work 100%. For your example it would be:
if($_SESSION['numofchildren']=="")
{
$updatequery="
UPDATE table
SET table1='$value', table2='$value2', numofchilrdrentable=(NULL)
";
}
else
{
$updatequery="
UPDATE table
SET table1='$value', table2='$value2', numofchilrdrentable='$_SESSION[numofchildren]'
";
}
$updatequeryresult=mysql_query($updatequery) or die("query fout " . mysql_error() );
edit: var char -> var char(2)
null parsed to string becomes 0. Try using is_null() to check that first and place NULL instead of 0 in the query.
Or, try using PDO and PDO::prepare for a perfect and hacker-safe query.
It's very confusing especially when values were posted from a web form. I do it like that:
We assume you need a database field named 'numofchildren' that will accept possible values: NULL, 0, 1, 2.., etc. up to 99 and default should be the SQL NULL value.
SQL field should be defined as:
.. `numofchildren` INT( 2 ) NULL DEFAULT NULL
When you insert your data for the NULL values you pass strings like 'NULL' and look for them when looping the incoming data. The other values you just cast to integers:
foreach ($data as $v) {
$v['numofchildren'] = !isset($v['numofchildren']) || $v['numofchildren'] === 'NULL' ? '(NULL)' : (int) $v['numofchildren'];
$q = "INSERT INTO tablename (numofchildren) VALUES ({$v['numofchildren']}) ";
...
}
Note that {$v['numofchildren']} in SQL query is not surrounded with single quotes because you do not pass strings but integers (0,1,2..) or SQL NULL.
I believe it's clear and short and covers the issue.
if you want set NULL for any column in DATABASE
at first
You should check is_null for that column
secuond :if the variable you want
Set to null code you must insert "null" in double quote then submit to database
If you set null to double quote("") nothing, nothing will be sent and the database will get an error
for example :
function insert_to_db($var){
...
sql="INSERT INTO table VALUES($var)"
...
}
when you use in code with "" and without "" =>
function insert_to_db(null)// error : INSERT INTO table VALUES()
correct:
function insert_to_db("null")//its ok

Categories