PHP Prevent SQL Injection - php

I'm trying to prevent SQL Injection using PHP with PDO. I've used this as a reference. http://wiki.hashphp.org/PDO_Tutorial_for_MySQL_Developers . My code doesn't give me any error but the values that are getting in are all null.
The vlaues I'm trying to insert are not null. I know this because I've echo'ed them out:
echo "\nDate: ".$date." Name: ".$name." mail: ".$mail."Comment: ".$comment." website: ".$website;
$sql = "INSERT post SET timeDate = :timeDate and name = :name and mail = :mail and comment = :comment and website = :website";
$stmt = $db->prepare($sql);
$stmt->bindParam(":timeDate", $date);
$stmt->bindParam(":name", $name);
$stmt->bindParam(":mail", $mail);
$stmt->bindParam(":comment", $comment);
$stmt->bindParam(":website", $website);
$stmt->execute();

Don't use AND in between your assignments—use commas.
$sql = "INSERT post
SET timeDate = :timeDate,
name = :name,
mail = :mail,
comment = :comment,
website = :website";
Your statement using AND between the terms is no error, because the statement is actually valid. It just doesn't do what you thought it would.
It's as if you did this:
SET timeDate = (:timeDate and name = :name and mail = :mail and comment = :comment and website = :website")
This sets only timeDate to the result of one long boolean expression.
The other columns are not getting assigned anything, they're just being compared to parameterized values. Since this is a new row you haven't inserted yet, all the other columns are naturally NULL, so the comparisons will be NULL. Therefore AND-ing them together will be NULL, and that's the ultimate value that will be assigned to your timeDate column.
The other columns are not assigned any value in this statement, and their default is presumably NULL.
This is a weird and useless statement, but strictly speaking, it's not an error.
I also encourage you to use PDO more simply. Instead of using bindParam() for everything, you can pass an array to execute(). This does the same thing as if you had done bindValue() for each of the parameters. You can do this with named parameters or positional parameters.
$stmt = $db->prepare($sql);
$stmt->execute([
"timeDate" => $date,
"name" => $name,
"mail" => $mail,
"comment" => $comment,
"website" => $website]);
This is especially handy if you already have your parameter values stored in an array.
The protection against SQL injection is just as good as using bindParam().

Related

Which PDO bind approach should be used for greater security?

I know of two ways to use PDO in PHP to update a MySQL database record. Please could someone explain which one I should use for better security and the difference and I am a little confused.
Method One:
$user = "root";
$pass = "";
$dbh = new PDO('mysql:host=somehost;dbname=somedb', $user, $pass);
$sql = "UPDATE coupons SET
coupon_code = :coupon_code,
valid_from = :valid_from,
valid_to = :valid_to,
discount_percentage = :discount_percentage,
discount_amount = :discount_amount,
calculationType = :calculationType,
limit = :limit
WHERE coupon_code = :coupon";
$stmt = $dbh->prepare($sql);
$stmt->bindParam(':coupon_code', $_POST['coupon_code'], PDO::PARAM_STR);
$stmt->bindParam(':valid_from', $_POST['$valid_from'], PDO::PARAM_STR);
$stmt->bindParam(':valid_to', $_POST['valid_to'], PDO::PARAM_STR);
$stmt->bindParam(':discount_percentage', $_POST['discount_percentage'], PDO::PARAM_STR);
$stmt->bindParam(':discount_amount', $_POST['discount_amount'], PDO::PARAM_STR);
$stmt->bindParam(':calculationType', $_POST['calculationType'], PDO::PARAM_STR);
$stmt->bindParam(':limit', $_POST['limit'], PDO::PARAM_STR);
$stmt->bindParam(':coupon', $_POST['coupon_code'], PDO::PARAM_STR);
$stmt->execute();
Method Two:
$dbtype="somedbtype";
$dbhost="somehost";
$dbname="somedb";
$dbuser="someuser";
$dbpass= "somepass";
$conn = new PDO("mysql:host=$dbhost;dbname=$dbname",$dbuser,$dbpass);
$title = 'PHP Pattern';
$author = 'Imanda';
$id = 3;
$sql = "UPDATE books
SET title=?, author=?
WHERE id=?";
$q = $conn->prepare($sql);
$q->execute(array($title,$author,$id));
From what I can see, method two does not bind the data, rather insert it directly into the query as an array type. Does this make the script more susceptible to SQL injection or other security risks?
The only difference between the two is that if you pass the array in to the execute function rather than calling bindParam yourself, it treats all parameters as PDO::PARAM_STR automatically, whereas in calling bindParam yourself you could bind them as integers, etc.
From the docs:
input_parameters
An array of values with as many elements as there are bound parameters in the SQL statement being executed. All values are treated as PDO::PARAM_STR.
You can also see from the examples there that you can use named parameters (e.g. :limit) when passing the array into the execute function. You don't have to just put ?. In that case you give the array a key:
$sth->execute(array(':calories' => $calories, ':colour' => $colour));
It's mostly a matter of preference. Both protect you from injection.
Though I think it's much easier to force data type with bind(), where as using execute(array()) will be using strings.

PDO DELETE also creates a new INSERT

I have a strange problem where every time I do a simple DELETE query to delete WHERE email =. For some reason after deletion it also does a new INSERT with the same email? There is no INSERT anywhere and there are no triggers... Does anybody know why this happens? The table has a email and a nr with auto_increment.
$check_email = $_POST['email'];
$query = "SELECT `email` FROM `newsletter` WHERE email = '$check_email';";
$sth = $dbh->prepare($query);
$sth->execute();
$row = $sth->fetch(PDO::FETCH_ASSOC);
$check_users_email = $row['email'];
if($check_users_email != ''){
$query_update = "DELETE FROM `newsletter` WHERE email = '$check_users_email';";
}
$sth = $dbh->prepare($query_update);
$sth->execute();
Before deletion: email=test#email.com | nr=1
After deletion: email=test#email.com | nr=2
it might be in your sql string, since you're using prepared statements.
in PDO you should use named or unnamed placeholders. then after preparing the query, you pass the prams as an array when you execute the statement.
If you're using PDO, no need to use single quotes. just the column name and for the search value just use placeholders and then pass on the values on execution as an array.
NOTE: i renamed the PDO object $sth inside the 'if' statement, just to avoid name clash. also i moved the last 2 lines inside the 'if' statement, because you need the value of the sql string '$query_update' which will not be available if that statement returned false.
also to check if the variable $check_users_email is empty, you can use empty() or strlen().
try this:
$check_email = $_POST['email'];
$query = "SELECT email FROM newsletter WHERE email = :check_email";
$sth = $dbh->prepare($query);
$sth->execute(array(':check_email' => $check_email));
$row = $sth->fetch(PDO::FETCH_ASSOC);
$check_users_email = $row['email'];
if($check_users_email != ''){
$query_update = "DELETE FROM newsletter WHERE email = :check_users_email";
$sth2 = $dbh->prepare($query_update);
$sth2->execute(array(':check_users_email' => $check_users_email));
}

PDO execute with array returns null

I'm trying to use good PDO as always and almost everything works but one query:
$primary = 'my_id';
$table = 'my_table';
// This or...
$statement = $this->conn->prepare("SELECT MAX(:id) AS id FROM :table");
$statement->bindParam(':id', $primary, PDO::PARAM_STR);
$statement->bindParam(':table', $table, PDO::PARAM_STR);
$statement->setFetchMode(PDO::FETCH_ASSOC);
$statement->execute();
// This one. Both doesn't work.
$statement = $this->conn->prepare("SELECT MAX(:id) AS id FROM :table");
$statement->setFetchMode(PDO::FETCH_ASSOC);
$arr = array(
':id' => 'my_id',
':table' => 'my_table',
);
$statement->execute($arr);
These just return a null array. I feel so confused. So I have tried that:
$statement = $this->conn->prepare("SELECT MAX(".$primary.") AS id FROM ".$table);
$statement->setFetchMode(PDO::FETCH_ASSOC);
$statement->execute();
And it works. I feel like I'm missing something but can't figure it out. So clearly there's a problem with binding I tried different variations such as writing one of the variable manually, but no luck so far.
Thanks in advance for any help...
You can't use parameters as table names in PDO, so you will have to change this to avoid that. This is not a limitation of PDO, but a limitation of MySQL directly. The manual states that
Parameter markers can be used only where data values should appear,
not for SQL keywords, identifiers, and so forth.
Table and column names are identifiers, so using placeholders for them is not supported. See this question for an alternative method.

mysqli prepared statement without bind_param

I have this code for selecting fname from the latest record on the user table.
$mysqli = new mysqli(HOST, USER, PASSWORD, DATABASE);
$sdt=$mysqli->('SELECT fname FROM user ORDER BY id DESC LIMIT 1');
$sdt->bind_result($code);
$sdt->fetch();
echo $code ;
I used prepared statement with bind_param earlier, but for now in the above code for first time I want to use prepared statement without binding parameters and I do not know how to select from table without using bind_param(). How to do that?
If, like in your case, there is nothing to bind, then just use query()
$res = $mysqli->query('SELECT fname FROM user ORDER BY id DESC LIMIT 1');
$fname = $res->fetch_row()[0] ?? false;
But if even a single variable is going to be used in the query, then you must substitute it with a placeholder and therefore prepare your query.
However, in 2022 and beyond, (starting PHP 8.1) you can indeed skip bind_param even for a prepared query, sending variables directly to execute(), in the form of array:
$query = "SELECT * FROM `customers` WHERE `Customer_ID`=?";
$stmt = $db->prepare($query);
$stmt->execute([$_POST['ID']]);
$result = $stmt->get_result();
$row = $result->fetch_assoc();
The answer ticked is open to SQL injection. What is the point of using a prepared statement and not correctly preparing the data. You should never just put a string in the query line. The point of a prepared statement is that it is prepared. Here is one example
$query = "SELECT `Customer_ID`,`CompanyName` FROM `customers` WHERE `Customer_ID`=?";
$stmt = $db->prepare($query);
$stmt->bind_param('i',$_POST['ID']);
$stmt->execute();
$stmt->bind_result($id,$CompanyName);
In Raffi's code you should do this
$bla = $_POST['something'];
$mysqli = new mysqli(HOST, USER, PASSWORD, DATABASE);
$stmt = $mysqli->prepare("SELECT `fname` FROM `user` WHERE `bla` = ? ORDER BY `id` DESC LIMIT 1");
$stmt->bind_param('s',$_POST['something']);
$stmt->execute();
$stmt->bind_result($code);
$stmt->fetch();
echo $code;
Please be aware I don't know if your post data is a string or an integer. If it was an integer you would put
$stmt->bind_param('i',$_POST['something']);
instead. I know you were saying without bind param, but trust me that is really really bad if you are taking in input from a page, and not preparing it correctly first.

MYSQLI prepared statement bind_param types does not work

I have been using prepared insert statements for some years and assumed it was binding parameters properly or would give an error but it seems not as the following php binds and inserts a record without any errors but changes a string which should be an int to a zero. So it may be ok for preventing SQL injection attacks but you would end up with a spurious record in the table. e.g.:
$q = "INSERT INTO `table` (id1, id2) VALUES (?, ?)";
$stmt = mysqli_stmt_init($dbc);
$stmt->prepare($q);
$id1 = 'aaaaaaa';
$id2= 'aaaaaaa';
$result = $stmt->bind_param('ii', $id1, $id2);
echo '<p>' . $result . '</p>'; // Trying to bind a string to an int returns true!
echo $dbc->error; // nothing
$stmt->execute(); // inserts record changing $id2 to zero but auto-increments primary key $id1
echo $dbc->error; // nothing
This is running in an Xampp environment with Apache/2.2.14, PHP/5.3.1 and MySQL 5.1.41. Can anyone tell me what is going on?
$stmt->bind_param() doesn't check the given variables for a certain type, it only converts them into the specified type. And your string 'aaaaaaa' is converted into an int-value: 0. That's the way php does it.
The database insert statement is the wrong place to check, if your variables contain useful/correct values. Do that before and only try to insert them, if your validations work.
To do the validation for an int, you could use the php-function is_numeric() or is_int().
I'm not an expert for sure, but at first look.
You have:
$id1 = 'aaaaaaa';
$id2= 'aaaaaaa';
$result = $stmt->bind_param('ii', $id1, $id2);
Thing is your 'ii' parameter says that you will be binding integers! And in fact your $id1 and $id2 are strings. For strings you should go with:
$result = $stmt->bind_param('ss', $id1, $id2);

Categories