This question already has answers here:
Can I parameterize the table name in a prepared statement? [duplicate]
(2 answers)
Closed 5 years ago.
I'm trying to execute delete SQL statement upon button press, which was working if I binded 1 parameter, but I want to make the delete.php generic, as not to have multiple of them just for referencing different tables:
<?php
include "header.php";
include "db.php";
$_POST['table'] = "customer";
$_POST['column'] = "cID";
$_POST['del_id'] = 26;
if(isset($_POST['del_id']))
{
if ($stmt = $conn->prepare("DELETE FROM customer WHERE ? = ?"))
{
$stmt->bind_param('si', $_POST['column'], $_POST['del_id']);
$stmt->execute();
}
else echo("Oops");
}
This binding executes but doesn't do anything to the table, only binding the final value 'del_id', executes correctly, and binding 3 arguments including the table name, just causes prepare() to fail.
I am setting the _POST vars in other places from AJAX POSTs, above is just for testing that this bit works or not. I also haven't gotten round to doing validation yet before that comes up.
Very PHP nooby, likely a simple mistake, or just something I'm not aware of, in which case I'd be rather curious as to why the table/column names can't be parameterised, as it's been eluding me for some time. As a workaround, would some form of concatenation work instead, to be able to drop dynamic names into this query from multiple different places?
You should validate both the table name and column name before running the delete.
Since you can't prepare either the table or column names, just put them in the sql statement before the prepare.
<?php
include "header.php";
include "db.php";
$_POST['table'] = "customer";
$_POST['column'] = "cID";
$_POST['del_id'] = 26;
// Add code to prevent SQL injection
$table = $_POST['table'] == 'customer' ? $_POST['table'] : '';
$column = $_POST['column'] == 'customer' ? $_POST['column'] : '';
if(isset($_POST['del_id']) && $table != '' && $column != '') {
if ($stmt = $conn->prepare("DELETE FROM `".$table."` WHERE `".$column."` = ?"))
{
$stmt->bind_param('i', $_POST['del_id']);
$stmt->execute();
}
else echo("Oops");
}
Yes, to achieve this you will need to have a combination of concatenation and parameters for the Prepared Statement:
if ($stmt = $conn->prepare("DELETE FROM " . $_POST['table'] . " WHERE " . $_POST['column'] . " = ?"))
{
$stmt->bind_param('si', $_POST['del_id']);
$stmt->execute();
}
Make sure you pay attention on additional validations you will need on the table and column names. This should be validated against your data model and not just making sure its a valid identifier. Further, take a look at some ORMs/Query Builders to learn how to elaborate upon this idea. It's a good learning exercise.
Related
This question already has answers here:
What to do with mysqli problems? Errors like mysqli_fetch_array(): Argument #1 must be of type mysqli_result and such
(1 answer)
How can I prevent SQL injection in PHP?
(27 answers)
Reference - What does this error mean in PHP?
(38 answers)
Closed 2 years ago.
I have been using the same code for years and all of a sudden I'm having problems that I cannot figure out. I am making a very simple query to MySQL in PHP using a variable in the statement. When I use the variable, it returns no results. When I manually type in the value of the variable instead, it works. I use this syntax all day long and never have had a problem. What on earth is wrong?
$name = "Fred";
$query = "SELECT * FROM database WHERE name='".$name."'";
$result = mysqli_query($connection, $query);
if (mysqli_num_rows($result) != 0) {
echo "Found record.";
}
If I replace the $name variable with Fred, it finds the record. If I echo the query with the variable before it executes and place that exact statement into MySQL directly in phpMyAdmin, I also get the result. If I leave the statement as-is with the variable in place, I get no result. Please help.
your query states SELECT * FROM database WHERE name='".$name."', this means that your table name is database, now i dont know how you actually created this table but database is a MYSQL reserved keyword change the name of your table to something else or just change your query to
$query = "SELECT * FROM `database` WHERE name='$name'";
assuming that your database connection is fine your code should now work
also worth noting, whenever acquiring data from a database use prepared statements instead of raw data as it makes you vulnerable to sql injection, in your case your code should be something like this
$name = "Fred";
$stmt = $dbconnection->prepare("SELECT * FROM table_name WHERE name=?")
$stmt->bind_param("s", $name);
$stmt->execute();
$result = $stmt->get_result();
if($result->num_rows != 0)
{
echo "Found record.";
}
this is more secure
You shouldn't use mysqli excepted for old projects you can't upgrade, it's outdated and suffers from potential sql injection vulnerabilities.
Instead, I recommand you to learn PDO and prepared statements.
Your request should look like this :
$name = 'Fred';
$sql = "SELECT * FROM my_user_table WHERE name = :name";
// You should have set your pdo instance in a script handling your database connexion and reusing it in any script making requests.
$result = $pdo->prepare($sql);
// Here you dynamically inject values in your request and tells pdo what type of data you are expecting
$result->bindValue(':name', $name, PDO::PARAM_STR);
$result->execute();
if( $result->rowCount()) {
echo "{$result->rowCount()} result(s) found";
}
else {
echo 'No result found';
}
Here's the official doc :
https://www.php.net/manual/fr/book.pdo.php
This will also more than probably fix your problem.
This question already has answers here:
Use an array in a mysqli prepared statement: `WHERE .. IN(..)` query [duplicate]
(8 answers)
Closed 11 months ago.
I'm trying to create a select query with dynamic where clause and dynamic parameters but I always get error :
Warning: mysqli_stmt::bind_param(): Number of elements in type
definition string doesn't match number of bind variables
Which I sincerely do not understand since it seems the count is alright. So this is what the code really looks like in its rude format. I can't see what I'm doing wrong.
//get variables
$mediaArray ='Facebook,Twitter,Twitch,';
$otherMedia = 'House';
//convert string to array
$socialArray = explode(',', $mediaArray)
//declare some variables to be used later
$andwhere = '';
$bp = '';
$socialmarray = ''
//get every value from array of social media
foreach($socialArray as $socialmedia){
$socialmarray .=$socialmedia.',';
$andwhere .= " AND socialmedianame=?";
$bp .='s';
}
//test strings
echo $wheres = $andwhere;//AND socialmedianame=? AND socialmedianame=? AND socialmedianame=?
echo $bip = $bp.'s';//ssss
echo $validarayy = rtrim($socialmarray,',');//Facebook,Twitter,Twitch
//select query
$selectquery = $conn->prepare("select * from mediaservices where socialmedianame=? $wheres");
$selectquery->bind_param("$bip",$otherMedia,$validarayy);
$selectquery->execute();
$resultquery = $selectquery->get_result();
Because:
You are using user-supplied data, you must assume that your query is vulnerable to a malicious injection attack and
the amount of data that is to be built into the query is variable/indefinite and
you are only writing conditional checks on a single table column
You should use a prepared statement and merge all of the WHERE clause logic into a single IN statement.
Building this dynamic prepared statement is more convoluted (in terms of syntax) than using pdo, but it doesn't mean that you need to abandon mysqli simply because of this task.
$mediaArray ='Facebook,Twitter,Twitch,';
$otherMedia = 'House';
$media = array_unique(explode(',', $mediaArray . $otherMedia));
$count = count($media);
$conn = new mysqli("localhost", "root", "", "myDB");
$sql = "SELECT * FROM mediaservices";
if ($count) {
$stmt = $conn->prepare("$sql WHERE socialmedianame IN (" . implode(',', array_fill(0, $count, '?')) . ")");
$stmt->bind_param(str_repeat('s', $count), ...$media);
$stmt->execute();
$result = $stmt->get_result();
} else {
$result = $conn->query($sql);
}
foreach ($result as $row) {
// access values like $row['socialmedianame']
}
For anyone looking for similar dynamic querying techniques:
SELECT with dynamic number of LIKE conditions
INSERT dynamic number of rows with one execute() call
In your query:
$selectquery = $conn->prepare("select * from mediaservices where socialmedianame=? $wheres");
The ? represents one parameter to pass in, and the evaluation of $wheres adds another three, giving you four total parameters.
bind_param() should take a string representing the types of the variables to insert as the first parameter, and the variables themselves as the subsequent parameters.
In your bind:
$selectquery->bind_param("$bip",$otherMedia,$validarayy);
$bip evaluates to ssss and $otherMedia is a single string ("House"). You might expect $validarayy to be three strings, but rtrim() returns a string. Thus, it is only one string ("Facebook,Twitter,Twitch"). You pass through two variables when the query is expecting four:
$conn->prepare("select * from mediaservices where socialmedianame=House AND socialmedianame=Facebook,Twitter,Twitch AND socialmedianame=? AND socialmedianame=? AND socialmedianame=?"
To correct this, you'll want to convert $validarayy back to an array, and use the index for the various inputs:
$socialmarray2 = explode(',', $validarayy);
$selectquery->bind_param("$bip", $otherMedia, $socialmarray2[0], $socialmarray2[1], $socialmarray2[2]);
Also note that your sample code has a few missing semicolons; you'll need to fix these in order for your code to work correctly.
This can be seen working here.
Finally, note that even if you were to split the three strings out correctly, the selection of ... AND socialmedianame=Facebook AND socialmedianame=Twitter AND socialmedianame=Twitch will never match any results; socialmedianame can only contain one value. You're probably looking to substitute your AND statements with OR statements.
I am trying to migrate to Mysqli and I got my Mysql code to search for parameters like this:
$querySt = "SELECT userID FROM myTable";
if (isset($_POST["UserID"])) {
if (ctype_digit($_POST["UserID"])) {
addWhereIfNoHave();
$in_userID = mysql_real_escape_string($_POST["UserID"]);
$querySt .= " UserID = '$in_userID'";
}
}
if (isset($_POST["name"])) {
addWhereIfNoHave();
$in_name = mysql_real_escape_string($_POST["name"]);
$querySt .= " imgName LIKE LOWER('%$in_name%')";
}
if (isset($_POST["ScoreLessThan"])) {
if (ctype_digit($_POST["ScoreLessThan"])) {
addWhereIfNoHave();
$in_ScoreLessThan = mysql_real_escape_string($_POST["ScoreLessThan"]);
$querySt .= " Score < '$in_ScoreLessThan'";
}
}
...
...
there are other if statements here looking for other post data, and
they keep on adding parameters into mysql query string just like above.
...
...
//this function is called in those if statements above. It either adds "WHERE" or "AND".
function addWhereIfNoHave(){
global $querySt;
if (strpos($querySt, 'WHERE') !== false){
$querySt .= " OR";
return true;
}else{
$querySt .= " WHERE";
return false;
}
}
This function works ok looking for all the parameters input from PHP post. However, I am migrating this to Mysqli, and I have a bit of trouble converting this code to Mysqli version. For example,
$stmt = $conn->prepare("SELECT userID FROM myTable WHERE UserID = ? AND name= ?");
$stmt->bind_param('ss', $userid, $name);
Suppose, I wanna search the table using 2 variables, I bind 2 variables like above, but in the case of my Mysql above, I keep on extending additional parameters into the string before executing the mysql query.
But for Mysqli, how can we do this? Is it possible to bind additional parameters and extending the string for prepare statement like Mysql code above? How should this problem be approach for Mysqli?
My current problem is mainly with the bind_param. I could concatenate the search query further and add all the '?' into the prepare statement, but with different variable types and number variables needed to be specified in bind_param, this is where I am stuck.
In the below code, I am attempting to take three form variables ($nps,$sch,$joint) and an id ($weld_id) and insert them into an UPDATE query. The problem is that I get $stmt->error "No data supplied for parameters in prepared statement."
When I cast the id of the row as "i". The weird thing is that the statement will execute error free if I put single quotes around the last question mark and cast as 's', however, the actual database row will not update. The function on row 3 does NOT use prepared statements to select the current values for this row in the database.
I have var_dumped all variables, copied them and successfuly run the query on MySQL workbench. I am out of ideas, please help.
$weld = mysqli_real_escape_string($db,$_POST['id']);
$weld = single_weld_query($db,$weld);
if(isset($_POST['edit_weld_parameters'])){
// Query to update 3 parameters on database entry where id = N
$stmt = $db->prepare("UPDATE `welds` SET `size` = '?' , `sch` = '?' , `joint` = '?' WHERE `id` = ?;");
$stmt->bind_param("sssi", $nps, $sch, $joint, $weld_id);
$nps = isset($_POST['size'])? mysqli_real_escape_string($db,$_POST['size']): $weld['size'];
$sch = isset($_POST['sch'])? mysqli_real_escape_string($db,$_POST['sch']): $weld['sch'];
$joint = isset($_POST['joint'])? mysqli_real_escape_string($db,$_POST['joint']): $weld['joint'];
$nps = (strlen($nps) and in_array($nps,$pipe_obj->sizes))? $nps: $weld['size'];
$sch = (strlen($sch) and in_array($sch,$pipe_obj->schedules))? $sch: $weld['sch'];
$joint = (strlen($joint) and in_array(strtoupper($joint),$pipe_obj->joint_types))? $joint: $weld['joint'];
$weld_id = $weld['id'];
if($stmt->execute()){
echo $weld['weld_number'].' parameters edited.';
}else{
echo $stmt->error;
}
}else{
echo 'ERROR: Form failure.';
}
You're using prepared statements with placeholder values, which is great, but you're also escaping things, which is bad. That ends up double-escaping them. Leave the escaping up to the driver, use placeholder values, and you'll be fine:
if (isset($_POST['edit_weld_parameters'])) {
// Query to update 3 parameters on database entry where id = N
$stmt = $db->prepare("UPDATE `welds` SET `size` = ? , `sch` = ? , `joint` = ? WHERE `id` = ?;");
$stmt->bind_param("sssi",
isset($_POST['size']) ? $_POST['size'] : $weld['size'],
isset($_POST['sch']) ? $_POST['sch'] : $weld['sch'],
isset($_POST['joint'])? $db,$_POST['joint'] : $weld['joint'],
$weld['id']
);
if ($stmt->execute()) {
echo $weld['weld_number'].' parameters edited.';
}
else {
echo $stmt->error;
}
}
else {
echo 'ERROR: Form failure.';
}
There's other code you'll need to wrangle in there, you're doing some very odd things to validate those after the fact, but try and stick with this general pattern.
Let the driver do the work. Do not put '?' in your query. Do not inline strings with interpolation. Don't escape anything that's already a placeholder value. Do try and keep your logic clean and obvious.
I'm trying to make code insert data into MYSQL from arrays
my problem is, it's not protected against SQL Injection.
I searched at every where ,How can i prevent it compeletly.
I looked for this question
How can I prevent SQL injection in PHP?
but i found two answers make me rethinking again.
https://stackoverflow.com/a/8255054/6523558
Every answer here covers only part of the problem.
In fact, there are four different query parts which we can add to it dynamically a string a number an identifier a syntax keyword.and prepared statements covers only 2 of them
https://stackoverflow.com/a/60442/6523558
I looked around for something will help me and this what i found
http://www.w3schools.com/sql/sql_injection.asp
http://www.w3schools.com/sql/sql_datatypes_general.asp
But nothing helped me to prevent it completely from my code.
I'm using this code to insert array data to MYSQL.
It's prevent it by using base64.
$tbname = "some_table";
$array1 = array("one"=>"1a","two"=>"2b");
$S1["add1"] = " (";
$S1["add2"] = " VALUES (";
foreach($array1 as $k1=>$n1){
if($n1 !== ""){
$S1["add1"] .= $k1 . ", ";
$S1["add2"] .= "'" . base64_encode($n1) . "', ";
};}
$S1["add1"] = substr($S1["add1"],0,-2);
$S1["add1"] .= ")";
//if($S1["add1"] == ")"){$_SESSION["sql_msg"] = "You have to put at least one input";} else {
$S1["add2"] = substr($S1["add2"],0,-2);
$S1["add2"] .= ")";
$sql = "INSERT INTO " . $tbname . $S1["add1"] . $S1["add2"];
//if ($conn->query($sql) === TRUE) {$_SESSION["sql_msg"] = "New record created successfully";
//} else {$_SESSION["sql_msg"] = "Error: " . $sql . "<br>" . $conn->error;};}
//ref1();
echo $sql;
Based on my article (which is more focused on disclosing bad and wrong practices), An SQL injection against which prepared statements won't help
The protection from SQL injection is actually simple, and can be formulated in just two statements:
use placeholders for the every data value
whitelist everything else
Given all that, you should
whitelist your table and field names
create a query consists placeholders and filtered out table and field names
send your variable array into execute.
For this purpose first define an array with all the allowed field names
$allowed = ["one","two"];
Then out of this array you will need to to create a SET statement for the INSERT query that should look like
one = :one, two = two:
For this you need a code like this
$allowed = ["one","two"];
$params = [];
$setStr = "";
foreach ($allowed as $key)
{
if (isset($array1[$key]))
{
$setStr .= "`".str_replace("`", "``", $key)."` = :".$key.",";
$params[$key] = $_POST[$key];
}
}
$setStr = rtrim($setStr, ",");
Note that we are also getting data values into distinct array
Finally, get your query from parts (given a table name is already hardcoded in you script) and then prepare and execute it using array with values
$tbname = "some_table";
$sql = "UPDATE `$tbname` SET $setStr";
$pdo->prepare($sql)->execute($params);
I guess the best way is by using
$sql = str_getcsv($sql,";")["0"];
before the execution to prevent any extra commands