I'm building a PDO wrapper that have select, insert, delete, update function, actually I made the update functon that look's like this:
/**
* #param string $table name of the table
* #param array $data data to update
* #param string $where where clause
* #return bool result
*/
public function update($table, $data, $where)
{
ksort($data);
$fieldDetails = NULL;
foreach($data as $key=> $value)
{
$fieldDetails .= "`$key`=:$key,";
}
$fieldDetails = rtrim($fieldDetails, ',');
$sth = $this->prepare("UPDATE $table SET $fieldDetails WHERE $where");
foreach ($data as $key => $value)
{
$sth->bindValue(":$key", $value);
}
return $sth->execute();
}
an example of usage:
$postData = array(
'username' => 'foo',
'role' =>'admin'
);
$this->db->update('login', $postData, "`id` = {$data['id']}");
I don't know if this is secure or I missed something important, someone have suggestione to improve this?
Thanks in advance.
Nope, it is not safe, for two reasons.
First, $data['id'] is put directly to the query and thus is not safe. It have to be bound as well.
Second, $key is put directly to the query and thus is not safe. I wrote an article that demonstrates such a vulnerability and offers a solution: An SQL injection against which prepared statements won't help
Third, $table is put directly to the query and thus is not safe. I know it is meant to be hardcoded in the function call, but you asked here whether this function is safe, while we cannot know how you're calling it.
Besides, as the project grows, it's gets hard to keep the manual control on the data flow. So eventually a table name would become a variable and some day may be accepted from user input. Therefore, it's better to make your function impenetrable by itself, irrelevant to whatever external matters. Just like prepared statements do.
To fix the two latter vulnerabilities you should whitelist your keys, or, at the very least, format them properly. And it should be done in this very function. Otherwise by no means you can call it safe.
I stumble on your question coming from https://laurent22.github.io/so-injections/ which is a statistics of the php related questions on SO with SQL Injection vulnerabilities in them.
So, no. This is not secure as it is now.
This is what is reported as vulnerable and I am quoting it here for future references.
$sth = $this->prepare("UPDATE $table SET $fieldDetails WHERE $where");
Without a doubt, this is used in a lot of code, in a lot of software. Hopefully this will be useful to others too.
Related
This code works but it seems insecure because of concatenating GET parameters into the query. I'm concatenating because I need a dynamic number of parameters in the WHERE clause that can be of different types (IN, normal comparison condition).
How can I prepare a secure statement from a dynamic number of different type WHERE conditions?
class myclass
{
public function index($where_clause = 1)
{
// db connection (using pdo)
$stm = $this->dbh->query("SELECT COUNT(amount) paid_qs FROM qanda $where_clause");
$ret = $stm->fetch(PDO::FETCH_ASSOC);
// do stuff
}
public function gen_where_clause()
{
$where_clause = '';
if (isset($_GET['c']) || isset($_GET['t']))
{
$where_clause = 'WHERE ';
if (isset($_GET['c']))
{
$where_clause .= 'cat = ' . $_GET['c'];
}
if (isset($_GET['t']))
{
if (isset($_GET['c']))
{
$where_clause .= $where_clause . ' AND '
}
$where_clause .= 'tag IN(' . $_GET['t'] . ')';
}
}
return $this->index($where_clause);
}
}
I'll address this question on three fronts: actual correctness of the code, a solution, and better practices.
The Code
This code actually does not work, as mentioned there are very basic syntax error that even prevents it from actually being run at all. I'll assume this is a simplification error, however even the concatenation is wrong: the statement is duplicated each time (.= and the string itself. either of these will work, both will destroy the query)
$where_clause .= $where_clause . ' AND '
Dynamic Number Of Parameters
The problem of having a dynamic number of parameters is interesting, and depending on the needs can be fairly convoluted, however in this case, a fairly simple param concatenation will allow you to achieve a dynamic number of parameters, as suggested by AbraCadaver .
More exactly, when a condition is added to the statement, separately add the sql to the statement, and the values to an array:
$sql .= 'cat = :cat';
$values[':cat'] = $_GET['c'];
You can then prepare the statement and execute it with the correct parameters.
$stmt = $pdo->prepare($sql);
$stmt->execute($values);
Better Practices
As mentioned, the code presented in this question possibly is not functional at all, so let me highlight a few basic OOP principles that would dramatically enhance this snippet.
Dependency Injection
The db connection should be injected through the constructor, not recreated every time you execute a query (as it will, if you connect in the index method). Notice that $pdo is a private property. It should not be public, accessible by other objects. If these objects need a database connection, inject the same pdo instance in their constructor as well.
class myclass
{
private $pdo;
public function __construct(PDO $pdo) { $this->pdo = $pdo; }
}
The flow
One of these methods should be private, called by the other (public one) that would receive in arguments everything that is needed to run the functions. In this case, there does not seem to be any arguments involved, everything comes from $_GET.
We can adapt index so that it accepts both the sql and the values for the query, but these three lines could easily be transferred to the other method.
private function index($sql, $values)
{
$stmt = $this->pdo->prepare($sql);
$stmt->execute($values);
return $stmt->fetchAll();
}
Then the public gen_where_clause (I believe that is wrongly named... it really generates the values, not the clauses) that can be safely used, that will generate a dynamic number of parameters, protecting you from sql injection.
public function gen_where_clause()
{
$sql = "SELECT COUNT(amount) AS paid_qs FROM qanda ";
$values = [];
if (isset($_GET['c']) || isset($_GET['t']))
{
$sql .= ' WHERE ';
if (isset($_GET['c']))
{
$sql .= ' cat = :cat ';
$values[':cat'] = $_GET['c'];
}
// etc.
}
return $this->index($sql, $values);
}
Filtering inputs
Escaping values is not needed, for sql injection protection that is, when using parameterized queries. However, sanitizing your inputs is always a correct idea. Sanitize it outside of the function, then pass it as an argument to the "search" function, decoupling the function from the superglobal $_GET. Defining the arguments for filtering is out of the rather large scope of this post, consult the documentation.
// global code
// create $pdo normally
$instance = new myclass($pdo);
$inputs = filter_input_array(INPUT_GET, $args);
$results = $instance->gen_search_clause($inputs);
This question already has answers here:
Insert query on page load, inserts twice?
(3 answers)
Closed 2 years ago.
I usually don't post detailed versions of my code; however, in this case it may be necessary to figure out the problem. I have a class method that I cannot stop from executing twice. Is there any particular information I am missing on MySQLi prepared statements? I have read similar questions that asks the same to no avail.
I previously asked a question about using eval for dynamic queries in prepared statements as far as it's convention and best practices. I was told to use call_user_func_array() and it worked perfectly; however, I failed to notice that the statement executed twice each time, even with the old eval() code. So I put together a snippet of my ACTUAL code which should pretty much explain itself through my comments
function insert($table, $query)
{
/**
*
* This code assumes you have a MySQLi connection stored in variable $db
* USAGE: insert(table, array('field' => 'value');
*
**/
// Sets the beginning of the strings for the prepared statements
$fields = $values = "(";
$types = "";
$params = array();
foreach($query as $key => $val)
{
// array keys = fields, and array values = values;
$fields.= $key;
// concatenate the question marks for statement
$values.= "?";
// concatenate the type chars
$types.= is_string($val) ? "s" : (is_int($val) ? "i" : (is_double($val) ? "d" : "b"));
// pass variables to array params by reference for call_user_func_array();
$params[] = &$query[$key];
if($val == end($query))
{
$fields .= ")";
$values .= ")";
array_unshift($params, $types);
}
else
{
$fields .= ", ";
$values .= ", ";
}
}
$str = "INSERT INTO {$table} {$fields} VALUES {$values}";
if($stmt = $db->prepare($str))
{
call_user_func_array(array($stmt, 'bind_param'), $params);
/**
*
* This is where I am pulling my hair out of my head and being 3
* nothces away from banging my own head into the screen and
* being without a computer at all.
*
* I have tried everything I can think of. I gotta be missing
* something
*
* IT JUST KEEPS SENDING 2 ROWS DANG IT!
*
**/
/////////////////////
$stmt->execute();//// <---Help is needed here
/////////////////////
//-- Close connection;
$stmt->close();
}
else
{
//-- Send a nice readable error msg
die("<center><h3>FAULTY QUERY STRING</h3><h4>Please check query string</h4><p>{$str}</p>");
}
}
Changed code format from OOP to regular function for testing without having to create a class.
old question, but people might still stumble upon it, I did.
I think the mysqli_query function is mainly for retrieving data, I had the same problem, and fixed it by using mysqli_real_query on insert and update queries.
I would like your opinion about my code. Is it secure enough against any injections.
Thanks for your replies.
class Product extends DB {
public function __construct() {
$db = $this->DB();
if(isset($_POST['productName'])) {
foreach ($_POST as $key => $value) {
if (ini_get('magic_quotes_gpc'))
$_POST[$key] = stripslashes($_POST[$key]);
$_POST[$key] = htmlspecialchars(strip_tags($_POST[$key]));
}
$this->AddProduct();
}
}
public function AddProduct() {
$sSQL = "INSERT INTO ".PREFIX."product (productName, productPrice)
VALUES (:productName,:productPrice)";
$query = $this->db->prepare($sSQL);
$query->execute(array(
":productName" => $_POST['productName'],
":productPrice" => $_POST['productPrice']
));
}
}
Using query parameters is enough to make it secure against SQL injection vulnerability.
The code that calls htmlspecialchars and strip_tags is not relevant to SQL injection. It might be called for to prevent Cross-Site Scripting vulnerabilities, but that's a separate issue. I don't recommend doing those steps as you insert data into the database. Just filter against XSS vulnerabilities when you output to HTML. Otherwise, you get literal & sequences stored in your database, and that's premature. You aren't necessarily going to use the data to display in HTML every time. Just encode it when you output, not when you input.
I never bother with compensating for the possible magic_quotes_gpc. Test for it when you deploy your app, and abort the deployment. It's not valid for any PHP instance to set magic_quotes_gpc in 2014.
Because I find PDO executions extremely hard to remember and find myself looking back at previous projects or other websites just to remember how to select rows from a database, I decided that I would try and create my own functions that contain the PDO executions and just plug in the data I need. It seemed a lot simpler than it actually is though...
So far I have already created a connect function successfully, but now when it comes to create a select function I'm stumped for multiple reasons.
For starters there could be a variating amount of args that can be passed into the function and secondly I can't figure out what I should pass to the function and in which order.
So far the function looks like this. To keep me sane, I've added the "id" part to it so I can see what exactly I need to accomplish in the final outcome, and will be replaced by variables accordingly when I work out how to do it.
function sql_select($conn, **what to put here**) {
try {
$stmt = $conn->prepare('SELECT * FROM myTable WHERE id = :id');
$stmt->execute(array('id' => $id));
$result = $stmt->fetchAll();
if ( count($result) ) {
foreach($result as $row) {
print_r($row);
}
} else {
return "No rows returned.";
}
} catch(PDOException $e) {
echo 'ERROR: ' . $e->getMessage();
}
}
So far what I've established that the function will need to do is
Connect to the database (using another function to generate the $conn variable, already done)
Select the table
Specify the column
Supply the input to match
Allow for possible args such as ORDER by 'id' DESC
Lastly from this I would need to create a function to insert, update and delete rows from the database.
Or, is there a better way to do this rather than functions?
If anyone could help me accomplish my ambitions to simply simplify PDO executions it would be greatly appreciated. Thanks in advance!
First of all, I have no idea where did you get 10 lines
$stmt = $conn->prepare('SELECT * FROM myTable WHERE id = ?');
$stmt->execute(array($id));
$result = $stmt->fetchAll();
is ALL the code you need, and it's actually three lines, which results with a regular PHP array that you can use wherever you wish. Without the need of any PDO code. Without the need of old mysql code.
Lastly from this I would need to create a function to insert, update and delete rows from the database.
DON'T ever do it.
Please read my explanations here and here based on perfect examples of what you'll end up if continue this way.
accomplish my ambitions to simply simplify PDO executions
That's indeed a great ambition. However, only few succeeded in a real real simplification, but most resulted with actually more complex code. For starter you can try code from the first linked answer. Having a class consists of several such functions will indeed improve your experience with PDO.
. . . and find myself looking back at previous projects or other
websites just to remember how to select rows from a database . . .
FYI, we all do that.
You had a problem with the PDO API and now you have two problems. My best and strongest suggestion is this: If you want a simpler/different database API, do not roll your own. Search http://packagist.org for an ORM or a DBAL that looks good and use it instead of PDO.
Other people have already done this work for you. Use their work and focus instead on whatever awesome thing is unique to your app. Work smart, not hard and all that.
Writting a wrapper, should start form connecting the DB, and all the possible method could be wrapped. Passing connection to the query method, doesn't look good.
A very rough example would be the code bellow, I strongly do not suggest this mixture, but it will give you the direction.
You connection should be made either from the constructor, or from another method called in the constructor, You can use something like this:
public function __construct($driver = NULL, $dbname = NULL, $host = NULL, $user = NULL, $pass = NULL, $port = NULL) {
$driver = $driver ?: $this->_driver;
$dbname = $dbname ?: $this->_dbname;
$host = $host ?: $this->_host;
$user = $user ?: $this->_user;
$pass = $pass ?: $this->_password;
$port = $port ?: $this->_port;
try {
$this->_dbh = new PDO("$driver:host=$host;port=$port;dbname=$dbname", $user, $pass);
$this->_dbh->exec("set names utf8");
} catch(PDOException $e) {
echo $e->getMessage();
}
}
So you can either pass connection credentials when you instantiate your wrapper or use default ones.
Now, you can make a method that just recieves the query. It's more OK to write the whole query, than just pass tables and columns. It will not make a whole ORM, but will just make the code harder to read.
In my first times dealing with PDO, I wanted everything to be dynamically, so what I achieved, later I realized is immature style of coding, but let's show it
public function query($sql, $unset = null) {
$sth = $this->_dbh->prepare($sql);
if($unset != null) {
if(is_array($unset)) {
foreach ($unset as $val) {
unset($_REQUEST[$val]);
}
}
unset($_REQUEST[$unset]);
}
foreach ($_REQUEST as $key => $value) {
if(is_int($value)) {
$param = PDO::PARAM_INT;
} elseif(is_bool($value)) {
$param = PDO::PARAM_BOOL;
} elseif(is_null($value)) {
$param = PDO::PARAM_NULL;
} elseif(is_string($value)) {
$param = PDO::PARAM_STR;
} else {
$param = FALSE;
}
$sth->bindValue(":$key", $value, $param);
}
$sth->execute();
$result = $sth->fetchAll();
return $result;
}
So what all of these spaghetti does?
First I though I would want all of my post values to be send as params, so if I have
input name='user'
input name='password'
I can do $res = $db->query("SELECT id FROM users WHERE username = :user AND password = :password");
And tada! I have fetched result of this query, $res is now an array containing the result.
Later I found, that if I have
input name='user'
input name='password'
input name='age'
In the same form, but the query remains with :user and :password and I submit the form, the called query will give mismatch in bound params, because the foreach against the $_REQUEST array will bind 3 params, but in the query I use 2.
So, I set the code in the beginning of the method, where I can provide what to exclude. Calling the method like $res = $db->query("SELECT id FROM users WHERE username = :user AND password = :password", 'age'); gave me the possibility to do it.
It works, but still is no good.
Better have a query() method that recieves 2 things:
The SQL string with the param names
The params as array.
So you can use the foreach() logic with bindValue, but not on the superglobal array, but on the passed on.
Then, you can wrap the fetch methods
public function fetch($res, $mode = null)
You should not directly return the fetch from the query, as it might be UPDATE, INSERT or DELETE.
Just pass the $res variable to the fetch() method, and a mode like PDO::FETCH_ASSOC. You can use default value where it would be fetch assoc, and if you pass something else, to use it.
Don't try to be so abstract, as I started to be. It will make you fill cracks lately.
Hum... IMHO I don't think you should try to wrap PDO in functions, because they're already "wrapped" in methods. In fact, going from OOP to procedural seems a step back (or at least a step in the wrong direction). PDO is a good library and has a lot of methods and features that you will surely lose if you wrap them in simple reusable functions.
One of those features is the BeginTransaction/Rollback (see more here)
Regardless, In a OOP point of view you can decorate the PDO object itself, adding some simple methods.
Here's an example based on your function
Note: THIS CODE IS UNTESTED!!!!
class MyPdo
{
public function __construct($conn)
{
$this->conn = $conn;
}
public function pdo()
{
return $this->conn;
}
public function selectAllById($table, $id = null)
{
$query = 'SELECT * FROM :table';
$params = array('table'=>$table);
if (!is_null($id)) {
$query .= ' WHERE id = :id';
$params['id'] = $id;
}
$r = $this->conn->prepare($query)
->execute($params)
->fetchAll();
//More stuff here to manipulate $r (results)
return $r;
}
public function __call($name, $params)
{
call_user_func_array(array($this->conn, $name), $params);
}
}
Note: THIS CODE IS UNTESTED!!!!
ORM
Another option is using an ORM, which would let you interact with your models/entities directly without bothering with creating/destroying connections, inserting/deleting, etc... Doctrine2 or Propel are good bets for PHP.
Howeveran ORM is a lot more complex than using PDO directly.
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...