Dynamic Prepared Statements - Does this open security holes? - php

This is similar to this question - Are Dynamic Prepared Statements Bad? (with php + mysqli), however since it is 4 years old I wanted to get a more upto date answer.
I've written a class which, although I haven't tested it on more copmlex sql queries, it has worked without fail on simple sql queries, however I'm not sure if doing so has bypassed one of the main reasons for prepared statements - security.
I have made use of the call_user_func_array which was easy enough with the bind_param statements however with the bind_result was a little trickier. I originally used get_result however the host I've gone with doesn't have mysqlnd available, but I managed to get around using the metadata. This is the full class I have written.
Do you think this is secure?
The passed in values are:
$sql is the passed in sql statement:
SELECT * FROM users WHERE id = ? AND created_timestamp > ?
$mysqli is the mysqli connection
$para is the placeholder in the prepared statement:
array ($types = 'ii', 23, 1235376000)
The class:
class crudModel {
function ps($sql, $mysqli, $para) {
//this function should work for just about any simple mysql statement
//for more complicated stuff like joins, unions etc,. we will see
if ($prep = $mysqli->prepare($sql)) {
call_user_func_array(array($prep, 'bind_param'), $this->makeValuesRef($para, $mysqli));
$prep->execute();
$meta = $prep->result_metadata();
while ($field = $meta->fetch_field()) {
$parameters[] = &$row[$field->name];
}
call_user_func_array(array($prep, 'bind_result'), $parameters);
while ($prep->fetch()) {
foreach ($row as $key=>$val) {
$x[$key] = $val;
}
$data[] = $x;
}
return $data;
}
}
function makeValuesRef($array, $mysqli) {
$refs = array();
foreach($array as $key => $value) {
$array[$key] = $mysqli->real_escape_string($value); //i don't think escaping is necessary, but it can't hurt (??)
$refs[$key] = &$array[$key];
}
return $refs;
}
}

What you're doing here isn't a dynamic prepared statement. You're just putting some syntatic sugar on top of the MySQLi API (which sucks).
In short, there aren't really any security concerns present from the code you've shown here. In fact, this sort of practice is quite good, because it makes it easier to verify that you're doing it correctly later (since the MySQLi API sucks).
So you're fine. I would worry about the areas you're generating the queries, and ensuring that you're not accidentally putting user-data into them without white-listing...

Related

Converting regular mysql into prepared statements

Im new to database and i have written a LOT of PHP code that accesses a database using MySQL.
I didnt take into account SQL injection attacks so i have to re-write all that PHP code to use mysql prepared statements.
After looking at videos on how to used prepared SQL statements, to perform just ONE SQL command requires a whole lot of "prepared" statements. My existing code has lots of different SQL statements all over the place, it would be a nightmare to change all that code to pack and unpack all the required preparation for each "prepared" statement command.
Is there some kind of wrapper i can use to prevent turning one line of regular SQL into 6 or 7 lines of prepared statements?
For example use to do this line line of SQL
SELECT * from users where userid=10
needs many more lines of prepared SQL statements, especially if there are lots of other SQL statements too it now becomes very complex.
Is there was some sort of one line wrapper that i can call that accepts the template SQL string, plus the parameters, which also executes the command and returns the result in just one line of wrapper for different types of MYSQL statements it would be great and the code would be much less confusing looking and error prone.
For example
$users=WrapAndExecute($db,"SELECT * from users where userid=?","s",$userid);
$data=WrapAndExecute($db,"UPDATE table SET username=?,city=?","ss",$name,$city);
$result=WrapAndExecute($db,"DELETE from table where id=?","s",$userid);
$result=WrapAndExecute($db,"INSERT into ? (name,address) VALUES(?,?)","ss","users",$name,$address);
Each of those lines above would create a prepared statement template, do the bind, execute it and return the result that a regular MYSQL statement would. This would create minimal impact on existing code.
Anybody knows how to do this or if some easy php library or class already exists to do this, that i can just import and start using it?
Thanks
You don't need to change a query to a prepared statement if it has no PHP variables in it. If it has just constant expressions, it's safe from SQL injection.
$sql = "SELECT * from users where userid=10"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
You don't need to change a query that contains PHP variables, as long as the value of that variable is a constant specified in your code. If it doesn't take its value from any external source, it's safe.
$uid = 10;
$sql = "SELECT * from users where userid=$uid"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
You don't need to change a query that contains PHP variables, as long as you can filter the value to guarantee that it won't risk an SQL injection. A quick and easy way to do this is to cast it to an integer (if it's supposed to be an integer).
$uid = (int) $_GET['uid'];
$sql = "SELECT * from users where userid=$uid"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
That leaves cases where you are using "untrusted" values, which may have originated from user input, or reading a file, or even reading from the database. In those cases, parameters are the most reliable way to protect yourself. It's pretty easy:
$sql = "SELECT * from users where userid=?"; // Safe!
// two lines instead of the one line query()
$stmt = $pdo->prepare($sql);
$stmt->execute([$_GET['uid']]);
$data = $stmt->fetchAll();
In a subset of cases, you need one additional line of code than you would normally use.
So quit your whining! ;-)
Re your comment about doing prepared statements in mysqli.
The way they bind variables is harder to use than PDO. I don't like the examples given in http://php.net/manual/en/mysqli.prepare.php
Here's an easier way with mysqli:
$sql = "SELECT * from users where userid=?"; // Safe!
$stmt = $mysqli->prepare($sql);
$stmt->bind_param('i', $_GET['uid']);
$stmt->execute();
$result = $stmt->get_result();
$data = $result->fetch_all();
I don't like the stuff they do in their examples with bind_result(), that's confusing and unnecessary. Just use get_result(). So with mysqli, you need two more lines of code than you would with PDO.
I've written query wrappers for mysqli that emulate the convenience of PDO's execute() function. It's a PITA to get an array mapped to the variable-arguments style of bind_param().
See the solution in my answers to https://stackoverflow.com/a/15933696/20860 or https://stackoverflow.com/a/7383439/20860
I were in the same boat, and I wrote such a wrapper that works exactly the way you want, save for it's being a class, not a function.
$user = $sdb->getRow("SELECT * from users where userid=?s", $userid);
$sdb->query("UPDATE table SET username=?s, city=?s", $name, $city);
$sdb->query("DELETE from table where id=?s", $userid);
$sdb->query("INSERT into ?n (name,address) VALUES(?s,?s)","users", $name, $address);
The above is a working code, as long as you have somewhere in your bootstrap file
$db = mysqli_connect(...);
...
require 'safemysql.class.php';
$sdb = new SafeMySQL('mysqli' => $db);
Note that none of the other suggestions could do anything like that.
Also note that if I were writing it today, I would have used PDO, as this class is duplicating a lot of functionality already exists in PDO.
Take a look at the PDO extension in PHP - http://php.net/manual/en/intro.pdo.php: it it secure against injections thanks to prepared statements; also, it allows you to connect to many different databases (e.g. MySQL, MSSQL, etc.).
You can then build your own wrapper as you wish to keep it clean; for example your own wrapper could be as follows:
(following example will return user rows as objects)
// connect to DB
$GLOBALS['default_db'] = new DB('localhost','db_name','username','password') ;
// Get users and output results
$query = new DBQuery('SELECT * FROM users WHERE userid = ?',array(10)) ;
var_dump($query -> results()) ;
var_dump($query -> num_rows()) ;
// DB connection
class DB {
public $connection;
public function __construct($host , $dbname , $username , $password) {
$this->connection = new \PDO('mysql:host=' . $host . ';dbname=' . $dbname , $username , $password);
}
}
// Wrapper
class DBQuery {
private $num_rows = 0;
private $results = array();
public function __construct($query , $params = null , $class_name = null , DB $db = null) {
if ( is_null($db) ) {
$db = $GLOBALS['default_db'];
}
$statement = $db->connection->prepare($query);
$statement->execute($params);
$errors = $statement->errorInfo();
if ( $errors[2] ) {
throw new \Exception($errors[2]);
}
$fetch_style = ($class_name ? \PDO::FETCH_CLASS : \PDO::FETCH_OBJ);
$this->results = $class_name ? $statement->fetchAll($fetch_style , $class_name) : $statement->fetchAll($fetch_style);
$this->num_rows += $statement->rowCount();
while ( $statement->nextrowset() ) {
$this->results = array_merge($this->results,$class_name ? $statement->fetchAll($fetch_style , $class_name) : $statement->fetchAll($fetch_style));
$this->num_rows += $statement->rowCount();
}
}
public function num_rows() {
return $this->num_rows;
}
public function results() {
return $this->results;
}
}
Since a key requirement seems to be that you can implement this with minimal impact on your current codebase, it would have been helpful if you had told us what interface you currently use for running your queries.
While you could use PDO:
that means an awful lot of work if you are not already using PDO
PDO exceptions are horrible
Assuming you are using procedural mysqli (and have a good reason not to use mysqli_prepare()) its not that hard to write something (not tested!):
function wrapAndExecute()
{
$args=func_get_args();
$db=array_shift($args);
$stmt=array_shift($args);
$stmt_parts=explode('?', $stmt);
if (count($args)+1!=count($stmt_parts)) {
trigger_error("Argument count does not match placeholder count");
return false;
}
$real_statement=array_shift($stmt_parts);
foreach ($args as $k=>$val) {
if (isnull($val)) {
$val='NULL';
} else if (!is_numeric($val)) {
$val="'" . mysqli_real_escape_string($db, $val) . "'";
}
$real_statement.=$val . array_shift($stmt_parts);
}
return mysqli_query($db, $real_statement);
}
Note that this does not handle IS [NOT] NULL nicely nor a literal '?' in the statement nor booleans (but these are trivial to fix).

MySQLi Prepared statements - Wrapper function for SELECT

I recently started using MySQLi prepared statements. I did not liked how many rows of code that was needed for just a simple select statement. So I created a wrapper function, see the code below the questions below.
Note: get_results() or PDO is not an option for me.
My questions are:
Will it slow down the performance noticeably?
Will it be more memory intensive because of the use of the result array?
Will the $stmt->close() before the return cause any problems? For example maybe the result array data is also are freed from memory?
Do I need to close or free anything else (except from closing the db connection)?
Do you see any other problems with the function or could it be improved?
Code:
class DatabaseHelper{
static function select($con, $query, $formats, $params){
$a_params = array();
$param_type = '';
$n = count($formats);
for($i = 0; $i < $n; $i++) {
$param_type .= $formats[$i];
}
$a_params[] = & $param_type;
for($i = 0; $i < $n; $i++) {
$a_params[] = & $params[$i];
}
$stmt = $con->prepare($query);
call_user_func_array(array($stmt, 'bind_param'), $a_params);
$stmt->execute();
$meta = $stmt->result_metadata();
while ($field = $meta->fetch_field()) {
$columns[] = &$row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $columns);
while ($stmt->fetch()) {
foreach($row as $key => $val) {
$x[$key] = $val;
}
$results[] = $x;
}
$stmt->close();
return $results;
}
}
Used like this for example:
$users = DatabaseHelper::select($conn, "SELECT name,username FROM users WHERE id > ?", "i", array(30));
foreach ($users as $row){
echo $row['username'] . " ". $row['name'] . "<br />";
}
Will it slow down the performance noticeably?
No.
Will it be more memory intensive because of the use of the result array?
No, as long as you are selecting sensible amounts of data. In a modern application you have to select all the data first anyway, as the business logic should be separated from display logic.
Will the $stmt->close() before the return cause any problems? For example maybe the result array data is also are freed from memory?
Why not to try and see?
Do I need to close or free anything else (except from closing the db connection)?
You rather don't have to close a statement either.
Do you see any other problems with the function or could it be improved?
First and foremost. As it's a class you are writing, and not a function, there is absolutely no point in passing the connection through a parameter. Make it a static property.
Also, I would suggest to make types the last parameter with default value. In most cases you don't have to nitpick with types - a default string will do.
Besides, as your PHP version is 5.6 you can use the splat operator just to reduce the amount of code. You can check this answer of mine for the details
I would also suggest to split your function into several methods - one to execute the query and others to get the results. It will let you to re-use the same code for all kinds of queries
make sure you are watching for mysqli errors as explained here
So, ideally you'd call your query this way
$users = DatabaseHelper::getAll("SELECT name,username FROM users WHERE id > ?", [30]);
foreach ($users as $row){
echo $row['username'] . " ". $row['name'] . "<br />";
}
where getAll() method is using query() method internally to perform a query and then to fetch all the results. Similarly you may want to write getRow() and getOne() methods

Convert normal SQL statements to prepared statements

I am using PHP 7, MySQL. I had been coding for my project a long time and it now has thousands of MySQL queries. I was not aware of prepared statements then.
Now, to avoid SQL injection, I want to use prepared statements but it is difficult for me to convert all of them one by one to prepared statement.
Is there any way I could parse a normal statement and convert it to prepared statement automatically using PHP? For every MySQL query, I pass it to a PHP function before passing to MySQL command.
public function dbquery($query,$dbname,$dbclose="-1")
{
$this->mysqli->select_db($dbname);
$GLOBALS["dbr_total"]++;$GLOBALS["dbr_query"]++;
$colarr=Array();$tares=Array();
if ($result = $this->mysqli->query($query))
{
$GLOBALS["dbretry"]=0;
$finfo = $result->fetch_fields();
$c=0;
foreach ($finfo as $val)
{
$colarr[$c]=$val->name;//get all colum names in this array
$c++;
}
$co=0;
while($obj = $result->fetch_object())
{
for($k=0;$k<count($colarr);$k++)
{
$elem=$colarr[$k];
$tares[$co][$elem]=$obj->{$colarr[$k]};
}
$co++;
}
if($co==0)
{
$GLOBALS["dbretry"]=0;
if($dbclose!="-1"){$this->dbclose();}
return EMPTY_RESULT;
}
}
else
{
if($GLOBALS["dbretry"]>3)
{
$GLOBALS["dbretry"]=0;
$errmsg=$this->mysqli->error;
$errno=$this->mysqli->errno;
if($dbclose!="-1"){$this->dbclose();}
$errobj=new ta_errorhandle();
$errobj->senderror("OOPS! Could Not process your query!".$errmsg,$errno,"1");
}
else
{
$GLOBALS["dbretry"]++;
$this->dbquery($query,$dbname);
}
}
//QUERY DONE
if($dbclose!="-1"){$this->dbclose();$result->close();}
unset($obj);unset($finfo);unset($query);unset($result);unset($colarr);unset($c);unset($co);
return $tares;
}
public function dbinsert($query,$dbname,$dbclose="-1")
{
$this->mysqli->select_db($dbname);
$GLOBALS["dbr_total"]++;;$GLOBALS["dbr_insert"]++;
if (!$this->mysqli->query($query))
{
$errmsg=$this->mysqli->error;
$errno=$this->mysqli->errno;
die("<br><br>".$errmsg."<br><br>".$errno);
if($GLOBALS["dbretry"]>3)
{
$GLOBALS["dbretry"]=0;
$logobj=new ta_logs();
$logobj->store_templogs("PROBLEM EXECUTING QUERY:".$query." ON ".$dbname);
return $this->mysqli;
}
else
{
$GLOBALS["dbretry"]++;
$this->dbinsert($query,$dbname);
}
}
else
{
$GLOBALS["dbretry"]=0;
}
if($dbclose!="-1"){$this->dbclose();}
return SUCCESS;
}
Now what I do is call $dbobj->dbquery("my query","database name"); where $dbobj is an object for the class of these functions.
How do I convert these functions so that whatever query I receive as parameter is used and converted to prepared statements? I cant rewrite every query in my code. I have written more than 10,000+ queries already.
The problem is that you appear to pass the whole query as a string to your dbquery() method.
Prepared statements only protect against sql injection in case a value is passed as a parameter to the query, not if the value is already in the query.
Since your entire query is a single string, with no placeholders and no values passed separately, you cannot use the prepared statements to protect against sql injection without changing how you pass the queries and parameters to dbquery() method. Therefore, you need to rewrite your queries in case you want to take advantage of sql prevention feature of the prepared statements.

Benefiting from PDO Prepared statement when used in a function or method

Per http://php.net/manual/en/pdo.prepared-statements.php
The query only needs to be parsed (or prepared) once, but can be
executed multiple times with the same or different parameters. When
the query is prepared, the database will analyze, compile and optimize
its plan for executing the query. For complex queries this process can
take up enough time that it will noticeably slow down an application
if there is a need to repeat the same query many times with different
parameters. By using a prepared statement the application avoids
repeating the analyze/compile/optimize cycle. This means that prepared
statements use fewer resources and thus run faster.
As such, the following will benefit with improved performance when using a prepared statements:
<?php
...
$stmt=$conn->prepare('SELECT a FROM mytable WHERE x=?'); //I often use globals or similar for $conn
foreach($array as $id){
$stmt->execute(array($id));
$data=$stmt->fetchAll(PDO::FETCH_COLUMN);
...
}
...
?>
To eliminate duplicated code, I wish to execute the query in a function.
How could I benefit from the improved performance of a prepared statement under this scenario?
Note the the following code provides no efficiency benefits, and is actually slower than not using prepared statements in the first place.
<?php
function getStuff($x)
{
...
$stmt=$conn->prepare('SELECT a FROM mytable WHERE x=?');
$stmt->execute(array($x));
$data=$stmt->fetchAll(PDO::FETCH_COLUMN);
...
return $data;
};
...
foreach($array as $x){
$data=getStuff($x);
...
}
...
?>
I have used this in the past:
function getStuff($x)
{
global $conn;
static $stmt;
if (null === $stmt) {
$stmt = $conn->prepare('SELECT a FROM mytable WHERE x=?');
}
This works if the function is stateless.
For instance, it would not make sense to make the $conn variable an argument:
function getStuff($conn, $x)
{
static $stmt;
if (null === $stmt) {
$stmt = $conn->prepare('SELECT a FROM mytable WHERE x=?');
}
Because, the static statement won't necessarily belong to the supplied connection.
However you do it, you need to make the statement persist either within the function, or otherwise use the function as a factory and cache the statement elsewhere.
EDIT, testing non-oop scenario:
echo '<pre>';
function do_something()
{
static $i = 0;
echo $i . PHP_EOL;
$i++;
}
do_something();
do_something();
do_something();
do_something();
do_something();
Output:
0
1
2
3
4

How to use php array in a Prepared Statement for SQL IN Operator using SQLi? [duplicate]

This question already has answers here:
Can I bind an array to an IN() condition in a PDO query?
(23 answers)
Closed 8 years ago.
This is my code:
if(isset($_POST['abc']))
{
$things['abc'] = mysqli_real_escape_string($connect, implode("','", $_POST['abc']));
$result = mysqli_query($connect, "SELECT * FROM this_list WHERE abc_column IN ('{$things['abc']}')");
if (!$result)
{
echo "Error fetching results: " . mysqli_error();
}
else
{
while ($row = mysqli_fetch_array($result))
{
$abc[] = $row['description'];
}
}
}
The above code uses mysqli_real_escape_string(), and $things is an array with checkbox values that is received via POST. This array contains the list of strings separated by comma that I am using in the query.
When I was searching on the net, I noticed that some people say mysqli_real_escape_string() may prevent sql injection, I was thinking maybe prepared statement for checkbox values might be more safer against sql injection.
I have used prepared statement with separate parameters to prevent sql injection. But I am stuck on this one and I dont know how to change the above code to a prepare() statement since it uses an array $things['abc']. I tried searching and everytime I search array in prepared statement, I am getting info on Java, etc.. Can someone enlighten me on how I can do this with php please?
EDIT:
After the help from onetrickpony code below, this is what I have now:
if(isset($_POST['abc']))
{
$ph = rtrim(str_repeat('?,', count($_POST['abc'])), ',');
$query = sprintf("SELECT col1 FROM abc_table WHERE col2 IN (%s)", $ph);
$stmt = mysqli_prepare($connect, $query);
// bind variables
$params = array();
foreach($_POST['abc'] as $v)
$params[] = &$v;
array_unshift($params, $stmt, str_repeat('s', count($_POST['abc']))); // s = string type
call_user_func_array('mysqli_stmt_bind_param', $params);
mysqli_stmt_execute($stmt);
// Get the data result from the query.
mysqli_stmt_bind_result($stmt, $col1);
/* fetch values and store them to each variables */
while (mysqli_stmt_fetch($stmt)) {
$name[] = $col1;
echo $name;
}
//loop to echo and see whats stored in the array above
foreach($name as $v) {
echo $v;
}
// Close the prepared statement.
$stmt->close();
}
In the above code, the sqli method for prepare statement seems to work which is great. However, when I use the mysqli_stmt_bind_result(), the $name[] array inside the while loop only seems to print the last row.
UPDATE:
onetrickpony's code with the mysqli method for using php array in a Prepared Statement worked fine and it was a very good approach he had suggested. However, I have been having nightmare with the second half of the code which is trying to get the fetched array results to work. After trying for more than a day, I have given up on that and I have made the switch to PDO. Again onetrickpony's advise below was totally worth it. Making the switch to PDO made the code so much easier and simpler and couldnt believe it.
Try this:
// build placeholder string (?,?...)
$ph = rtrim(str_repeat('?,', count($_POST['abc'])), ',');
$query = sprintf("SELECT * FROM this_list WHERE abc_column IN (%s)", $ph);
$stm = mysqli_prepare($connect, $query);
// bind variables (see my notes below)
$params = array();
foreach($_POST['abc'] as $v)
$params[] = &$v;
// s = string type
array_unshift($params, $stm, str_repeat('s', count($_POST['abc'])));
call_user_func_array('mysqli_stmt_bind_param', $params);
mysqli_stmt_execute($stm);
It appears that mysqli_stmt_bind_param cannot be called multiple times to bind multiple variables. And even worse, it requires referenced variables. I'd recommend you switch to PDO, just because of these limitations that force you to write ugly code :)

Categories