Unable to locate error with PDO prepared statement [duplicate] - php

I'm working on a dynamic query that uses variables to specify a table, a field/column, and a value to search for. I've gotten the query to work as expected without the variables, both in phpMyAdmin (manually typing the query) and from within the code by concatenating the variables into a complete query.
However, when I use bindParam() or bindValue() to bind the variables, it returns an empty array.
Here's my code:
function search_db($db, $searchTerm, $searchBy, $searchTable){
try{
$stmt = $db->prepare('
SELECT
*
FROM
?
WHERE
? LIKE ?
');
$stmt->bindParam(1, $searchTable);
$stmt->bindParam(2, $searchBy);
$stmt->bindValue(3, '%'. $searchTerm.'%');
$stmt->execute();
} catch(Exception $e) {
return array();
}
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// database initialization, creates the $db variable
require(ROOT_PATH . "include/database.php");
$matches = search_db($db, 'search term', 'myColumn', 'myTable');
var_dump($matches);
Expected results: an array of rows from the database
Actual results: an empty array

Unfortunately, placeholder can represent a data literal only. So, a very common pitfall is a query like this:
$opt = "id";
$sql = "SELECT :option FROM t";
$stm = $pdo->prepare($sql);
$stm->execute([':option' => $opt]);
$data = $stm->fetchAll();
This statement will return just a literal string 'id' in the fieldset, not the value of the column named id.
So, a developer must take care of identifiers oneself - PDO offers no help for this matter.
To make a dynamical identifier safe, one has to follow 2 strict rules:
to format identifiers properly
to verify it against a hardcoded whitelist.
To format an identifier, one has to apply these 2 rules:
Enclose the identifier in backticks.
Escape backticks inside by doubling them.
After such formatting, it is safe to insert the $table variable into query. So, the code would be:
$field = "`".str_replace("`","``",$field)."`";
$sql = "SELECT * FROM t ORDER BY $field";
However, although such formatting would be enough for the cases like ORDER BY, for most other cases there is a possibility for a different sort of injection: letting a user choose a table or a field they can see, we may reveal some sensitive information, like a password or other personal data. So, it's always better to check dynamical identifiers against a list of allowed values. Here is a brief example:
$allowed = array("name","price","qty");
$key = array_search($_GET['field'], $allowed);
$field = $allowed[$key];
$query = "SELECT $field FROM t"; //value is safe
For keywords, the rules are same, but of course there is no formatting available - thus, only whitelisting is possible and ought to be used:
$dir = $_GET['dir'] == 'DESC' ? 'DESC' : 'ASC';
$sql = "SELECT * FROM t ORDER BY field $dir"; //value is safe

Related

Is the in_array function a safe way of blocking code injection/sql injection?

If i have a php file which is receiving a $_GET['value'] is it safe from sql injection or code-injection for me to start my php file with
if (in_array($_GET['value'], $allowed_values);)
{ /* normal page code handling the $_GET['value'] */
} else { unset($_GET['name'])
}
$allowed values is obviously an array of all values which i am expecting as safe for $_Get['value']. Is this still unsafe? Thank you.
Yes, that's a common and safe technique that can be used in situations where query parameters can't be used. For instance, if the value will be used as a table or column name, you can't provide it as a query parameter, you have to substitute it directly into the SQL string. Whitelisting like this is the recommended way to ensure that this is safe.
It depends on the values in the $allowed_values array, and how you are interpolating the value into your SQL query.
For example:
$allowed_values = [ 'a word' ];
if (in_array($_GET['value'], $allowed_values)) {
$sql = "SELECT * FROM mytable WHERE id = {$_GET['value']};";
}
Definitely not safe. It results in the SQL:
SELECT * FROM mytable WHERE id = a word;
This is a syntax error.
Why would you not just use SQL query parameters? Then you don't need to worry if it's safe or not. Query parameters separate the values from the SQL parsing, so there's no way any kind of value can cause SQL injection.
You won't have to have an $allowed_values array. You won't have to remember to check if the GET input is in the array. You won't have to worry about quoting or escaping.
It's true that query parameters only work for values, that is in place of a quoted string literal or quoted datetime literal or numeric literal. If you need other parts of your query to be dynamic, like the table name or column name or SQL keywords, etc. then use an allow-list solution like you are showing.
But the more common case of interpolating dynamic values is better handled by query parameters:
$sql = "SELECT * FROM mytable WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt-execute( [ $_GET['value'] ] );
let's discuss this thing in little details:
Your code is like this :
if (in_array($_GET['value'], $allowed_values);) {
...........
$sql = "SELECT * FROM mytable WHERE id = $_GET['value']";
...........
}
else {
unset($_GET['name'])
}
now let's assume, you have some values :
the in_array() function will allow only some pre-defined values, you couldn't have the option to take custom user input by $_GET, but as only pre-defined values are allowed,any SQL command will be safe inside if statement.
now take this example of $allowed_values array :
$allowed_values = ['some details' , 'another details' ,3, ' 105; DROP TABLE mytable;', 22 , 'ok'];
If any of these array values have a string that can have potential SQL injection capability, then there will be an issue. but I think you will not put any such string in the array $allowed_values. ( in this above-mentioned example, index 3, ' 105; DROP TABLE mytable;' can delete the table mytable ). else the SQL command will be safe.
now you can add an extra layer of safety in the code, by using PDO for any SQL query. (in this example you do not need that, as in_array() function is 100% safe unless you yourself put any malicious code in the array, as per my above-mentioned example). but for other types of user input where you have to do some SQL query depend on the user input, you can use PDO -prepared statement.
a PDO example is this :
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDBPDO";
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
// set the PDO error mode to exception
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $conn->prepare("INSERT INTO photos (username, kname) VALUES (?, ?)");
$stmt->execute([ $username , $kname ]);
For more info, try w3school link: https://www.w3schools.com/php/php_mysql_prepared_statements.asp

Dynamic select mysqli query with dynamic parameters returns error doesn't match number of bind variables [duplicate]

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.

How to deal with apostrophes and double quotes simultaneously in PHP

I have a HTML form, from which a PHP script extracts values, as shown below:
$dbc = mysqli_connect("all required info here...") or die("Error occurred");
$sent = "Any sentence here...which may contain apostrophe or double quotes or both";
$query = "SELECT * FROM myrecord WHERE sentence = '$sent'";
$result = mysqli_query($dbc, $query);
$data = mysqli_fetch_array($result);
mysqli_close($dbc);
The problem is, that the variable $sent can contain any string with a combination of either apostrophe or double quotes or both. This gives an error when going for execution of mysqli_query().
So even if I escape double quotes in initialization of $sent it will still create problem for execution of mysqli_query(). And if I escape both for ' and " then value of $sent does not remains what it actually needs to be (although I am not sure about whether escaping both ' and " will work or not).
Is there any built in function that automatically escapes all special characters of a string? Or any workaround that solves this problem?
[P.S. I have already searched some previous questions on stackoverflow and haven't been able to find a solution.]
What you want, and what you should do is used prepared statements (parameterized queries). With PDO, that would look something like this:
$stmt = $pdo->prepare('SELECT * FROM myrecord WHERE sentence = :sentence');
$stmt->execute([':sentence' => $sentence]);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
//do stuff
}
mysqli supports them, too, but the API is slightly more cumbersome (IMO) - see mysqli_prepare docs for details:
$stmt = $mysqli->prepare('SELECT * FROM myrecord WHERE sentence = ?');
//or $stmt = mysqli_prepare($connection, $query);
Then, you bind the parameter (the value to be used in the WHERE clause) using bind_param:
$stmt->bind_param('s', $sentence);
//or mysqli_stmt_bind_param($stmt, 's', $sentence);
Then call execute - or mysqli_stmt_execute, and fetch the results using fetch - or mysqli_stmt_fetch...
As mentioned in the comments: the parameters and query string needn't be quoted in any way, because they're treated as separate entities. The result being that you can re-use the same prepared statement with different paramters:
$stmt = $pdo->prepare('SELECT * FROM table WHERE field = :field');
$fieldVals = [123, 46, 32]; // three values:
$results = array_fill_keys($fieldVals, null);
foreach ($fieldVals as $val) {
$stmt->execute([':field' => $val]);//execute with each value in $fieldVals array
$results[$val] = $stmt->fetchAll(PDO::FETCH_ASSOC); // fetch results for this field value
//optional, but good form:
$stmt->closeCursor();
}
you've now used the same statement 3 times, but only had to send the query string once. The query had to be parsed and processed once, and after that, you merely sent the paramters to the DB. This approach is generally faster, safer (prepared statements protect agains most injection attacks), and just all round better.

Can I use a PDO prepared statement to bind an identifier (a table or field name) or a syntax keyword?

I'm working on a dynamic query that uses variables to specify a table, a field/column, and a value to search for. I've gotten the query to work as expected without the variables, both in phpMyAdmin (manually typing the query) and from within the code by concatenating the variables into a complete query.
However, when I use bindParam() or bindValue() to bind the variables, it returns an empty array.
Here's my code:
function search_db($db, $searchTerm, $searchBy, $searchTable){
try{
$stmt = $db->prepare('
SELECT
*
FROM
?
WHERE
? LIKE ?
');
$stmt->bindParam(1, $searchTable);
$stmt->bindParam(2, $searchBy);
$stmt->bindValue(3, '%'. $searchTerm.'%');
$stmt->execute();
} catch(Exception $e) {
return array();
}
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// database initialization, creates the $db variable
require(ROOT_PATH . "include/database.php");
$matches = search_db($db, 'search term', 'myColumn', 'myTable');
var_dump($matches);
Expected results: an array of rows from the database
Actual results: an empty array
Unfortunately, placeholder can represent a data literal only. So, a very common pitfall is a query like this:
$opt = "id";
$sql = "SELECT :option FROM t";
$stm = $pdo->prepare($sql);
$stm->execute([':option' => $opt]);
$data = $stm->fetchAll();
This statement will return just a literal string 'id' in the fieldset, not the value of the column named id.
So, a developer must take care of identifiers oneself - PDO offers no help for this matter.
To make a dynamical identifier safe, one has to follow 2 strict rules:
to format identifiers properly
to verify it against a hardcoded whitelist.
To format an identifier, one has to apply these 2 rules:
Enclose the identifier in backticks.
Escape backticks inside by doubling them.
After such formatting, it is safe to insert the $table variable into query. So, the code would be:
$field = "`".str_replace("`","``",$field)."`";
$sql = "SELECT * FROM t ORDER BY $field";
However, although such formatting would be enough for the cases like ORDER BY, for most other cases there is a possibility for a different sort of injection: letting a user choose a table or a field they can see, we may reveal some sensitive information, like a password or other personal data. So, it's always better to check dynamical identifiers against a list of allowed values. Here is a brief example:
$allowed = array("name","price","qty");
$key = array_search($_GET['field'], $allowed);
$field = $allowed[$key];
$query = "SELECT $field FROM t"; //value is safe
For keywords, the rules are same, but of course there is no formatting available - thus, only whitelisting is possible and ought to be used:
$dir = $_GET['dir'] == 'DESC' ? 'DESC' : 'ASC';
$sql = "SELECT * FROM t ORDER BY field $dir"; //value is safe

Can I bind a parameter to a PDO statement as a comparison operator?

Is this code
class opinion
{
private $dbh;
var $opinionid,$opinion,$note,$actorid,$dateposted;
var $isnew=FALSE;
function loadby($column,$value,$operator="="){
$dbh = new PDO(I deleted parameters here);
$statement=$dbh->prepare("select * from fe_opinion where :column :operator :value");
$statement->bindParam(":column", $column);
$statement->bindParam(":value", $value);
$statement->bindParam(":operator", $operator); //UNSURE, DOUBTFUL
$statement->bindColumn("opinionid", $this->opinionid);
$statement->bindColumn("opinion", $this->opinion);
$statement->bindColumn("note", $this->note);
$statement->bindColumn("actorid", $this->actorid);
$statement->bindColumn("dateposted", $this->dateposted);
$statement->fetch();
return $statement->rowCount(); //please be 1
}
}
injection safe?
$statement->bindParam(":operator", $operator); //UNSURE, DOUBTFUL
Can I bind a parameter to a PDO statement as a comparison operator?
No, you cannot bind operators like that. As a workaround, you can dynamically create the "base" SQL query and use an operator whitelist (which is quite appropriate) to remain safe from injection:
function loadby($column,$value,$operator="="){
$dbh = new PDO(...);
$operator = getOperator($operator);
if(!$operator) {
// error handling
}
$statement=$dbh->prepare("select * from fe_opinion where :column $operator :value");
// the rest like you already do it
}
function getOperator($operator) {
$allowed_ops = array('=', '<', '>'); // etc
return in_array($operator, $allowed_ops) ? $operator : false;
}
Apart from this, the rest is fine and injection-proof "by definition".
Depending on the DBMS and PHP driver, prepared statements can be "real" or emulated.
In the first case, bind parameters are handled directly by the DBMS. In such case, handling an operator as parameter will probably trigger a syntax error. The SQL parser will analyse the query without even looking at parameters and will not find valid SQL code to work on.
In the second case, bind parameters are emulated by the driver: input values get inserted into SQL code (with adequate escaping) and the DBMS receives a complete regular query. I'm not really sure about how current drivers will behave (I'd need to test it) but even if they don't complain about invalid SQL, they'll hit a wall sooner or later: SQL operators are not strings.
Now, would it be a nice feature to get implemented some day? I doubt it is:
You won't benefit from pre-parsed SQL when running repetitive queries: if you change operators, you change the query.
You won't get safe SQL code. How could you?
As mentioned in comment I don't think it's possible to escape the operator and have it work as you expect. The resulting query would probably look something like:
'column' '=' 'value';
You don't need to escape the operator to avoid injection attacks, you can validate your operator before appending it to the string, consider:
class opinion
{
$validOperators = array('=', '>=', '>', '=<', '<');
function loadby($column,$value,$operator="=") {
// Validate operator
if (!in_array($operator, self::$validOperators)) {
throw new Exception('Invalid $operator ' . $operator . ')';
}
$statement=$dbh->prepare("select * from fe_opinion where :column " . $operator . " :value");
}
}
You can actually do it. The sql just gets more complicated. Depending on the number of combinations, the sql can get really huge. But, sometimes when there's only a few choices, its nice.
select *
from someTable
where (case :column
when 'age' then (case :operator
when '>' then age > :value
when '<' then age < :value
end)
when 'price' then (case :operator
when '>' then price > :value
when '<' then price < :value
end)
end)
and someOtherCol = 'foo'
The :value could be another column too, but you'll need to nest yet again another case construct like for the first column, and the combinations are really soaring now.
Anyway... just wanted to show it can be done.

Categories