PDO SQL injection test on search query - php

I'm pretty new to using PDO so I'm not sure if I have it down correctly, however with the following test I'm able to do some injection which I would like to bypass.
In my models class I have some shortcut methods. One of them is called return_all($table,$order,$direction) which simply returns all rows from a table:
public function return_all($table,$order = false, $direction = false) {
try {
if($order == false) {
$order = "create_date";
}
if($direction != false && !in_array($direction,array("ASC","DESC"))) {
$direction = "DESC";
}
$sql = "SELECT * FROM ".mysql_real_escape_string($table)." ORDER BY :order ".$direction;
$query = $this->pdo->prepare($sql);
$query->execute(array("order" => $order));
$query->setFetchMode(PDO::FETCH_ASSOC);
$results = $query->fetchAll();
} catch (PDOException $e) {
set_debug($e->getMessage(), true);
return false;
}
return $results;
}
This works fine, except, if I pass the following as $table into the method:
$table = "table_name; INSERT INTO `users` (`id`,`username`) VALUES (UUID(),'asd');";
Now it's unlikely that someone will ever be able to change the $table value as it's hard-coded into my controller functions, but, i'm a little concerned that I'm still able to do some injection even when I use PDO. What's more surprising is that the mysql_real_escape_string() did absolutely nothing, the SQL still ran and created a new user in the users array.
I also tried to make the table name a bound parameter but got a sql error I assume due to the `` PDO adds around the table name.
Is there a better way to accomplish my code below?

You have already solved your problem with direction.
if($direction != false && !in_array($direction,array("ASC","DESC"))) {
$direction = "DESC";
}
Use the same technique for table names
$allowed_tables = array('table1', 'table2');//Array of allowed tables to sanatise query
if (in_array($table, $allowed_tables)) {
$sql = "SELECT * FROM ".$table." ORDER BY :order ".$direction;
}

Related

Database abstraction layer ontop of mysqli

when trying to build robust database code (table locking, transactions, etc) i am always annoyed by the mass of code that needs to be done.
For example a transaction out of two prepared statements where i want to delete a user and update something about him in an "actions" table:
Lock Table users, actions
Start a transaction (autocommit false)
Make a prepared statement for the deletion of a user
Check if statement is != false (cause it could have already failed at 3.
Bind param
Check errorState != "00000" on the statement (can also have failed at binding params)
execute statement
Check errorState != "00000" on the statement (can also have failed at executing)
get Result of statement
Close statement
Make a new prepared statement for update actions
Check if statement != false
bind params
check statement's errorState
execute
check statement's errorState
get result
close statement
check overall transaction state, if valid commit, if not valid rollback
unlock tables
set autocommit back to true
This is how i do it (maybe im doing it wrong?). And if i do it that way its a lot of work and annoying. So i thought automateing that stuff.
What i want is something like this:
$DB->startTransaction();
$DB->query($query);
$DB->query($query2);
$DB->query($query3);
$DB->endTransaction();
And internally the database abstraction layer ontop of mysqli will take care of table locking, prepared statements and transactions itself. Shouldn't we be able to automate this?
This is one of my attempts:
public function query($query, $table, $params = null) {
if($params == null) {
$this->connection->query("LOCK TABLES $table WRITE");
$query = str_replace("!", $table, $query);
$result = $this->connection->query($query);
$this->connection->query("UNLOCK TABLES");
return $result;
}
else {
if (!$this->checkParams($query, $params)) {
return false;
}
$this->connection->query("LOCK TABLES $table WRITE");
$query = str_replace("!", $table, $query);
$stmt = $this->connection->prepare($query);
if ($stmt != false) {
$typesString = "";
foreach ($params as $param) {
if (is_numeric($param)) {
$typesString .= "i";
} else if (is_double($param)) {
$typesString .= "d";
} else {
$typesString .= "s";
}
}
$finalParamArray = array($typesString);
$finalParamArray = array_merge($finalParamArray, $params);
call_user_func_array(array($stmt, "bind_param"), $this->ref($finalParamArray));
$this->checkStatement($stmt);
$stmt->execute();
$this->checkStatement($stmt);
$result = $stmt->get_result();
$stmt->close();
$this->connection->query("UNLOCK TABLES");
return $result;
}
$this->query("UNLOCK TABLES");
return false;
}
}
This would be callable like this:
$DB->query("DELETE FROM ! WHERE userID =?", "Users", array($userID));
I am however not feeling confident about this. I googled a bit and didn't find something like i want. So my question now is: Is something like i want actually possible (well it should be)? Am i doing it wrong?
EDIT:
I also have 2 other attempts of doing this, which look MUCH MORE complicated (300+ lines of code). I can post them as well, if you want. I am still however not satisfied with them and not confident if this is actually correct!
You are right there should be an easier way of doing this, and you are also correct to say that we need an abstraction layer on top of mysqli. It is not designed to be used on its own.
You do not need so many steps. In particular, you do not need to check the return code of each method. That should already eliminate 6 or more of your steps. You do not need to close a statement either.
There's no need to specify the type when binding. Just use string type all the time. Other types come in handy very rarely, almost never.
Some time ago I posted an example of what an abstraction layer on top of mysqli could look like.
class DBClass extends mysqli {
public function __construct(
$host = null,
$username = null,
$passwd = null,
$dbname = null,
$port = null,
$socket = null
) {
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
parent::__construct($host, $username, $passwd, $dbname, $port, $socket);
$this->set_charset('utf8mb4');
}
public function safeQuery(string $sql, array $params = []): ?array {
$stmt = $this->prepare($sql);
if ($params) {
$stmt->bind_param(str_repeat("s", count($params)), ...$params);
}
$stmt->execute();
if ($result = $stmt->get_result()) {
return $result->fetch_all(MYSQLI_BOTH);
}
return null;
}
}
This is far from perfect, but it shows the main idea. You can wrap a prepared statement in one single method. Simple prepare/bind/execute/get_result. Nothing more. It works with and without parameters.
In the constructor the 3 mandatory steps to opening a connection: switching error reporting, creating instance of mysqli and setting the correct charset.
If you want transactions, then you can use mysqli's begin_transaction() and commit(). They are simple enough and do not require abstraction.
I do not know why you feel you need to lock tables, but again this is a simple SQL statement and doesn't need to be abstracted.
$db = new DBClass('localhost', 'user', 'pass', 'test');
$db->safeQuery('LOCK TABLES users WRITE');
$db->begin_transaction();
$db->safeQuery('DELETE FROM users WHERE userID =?', [$userID]);
$db->safeQuery('DELETE FROM otherTable WHERE userID =?', [$userID2]);
$db->commit();
$db->safeQuery('UNLOCK TABLES');

Converting into PDO

Good evening everyone!
I need your help again. Please bear with me because I am very new to this. Hoping for your understanding. So, I am having a project on oop and pdo. I am having quite hard time converting this into pdo.. Here is my code..
bookReserve.php
while($row=mysql_fetch_array($sql))
{
$oldstock=$row['quantity'];
}
$newstock = $oldstock-$quantity;
Here's what i've done
while($row = $code->fetchAll())
{
$oldstock=$row['quantity'];
}
$newstock = $oldstock-$quantity;
Is that even correct?
And for the oop part, after this while loop I have a query to execute.
$sql="update books set no_copies = '$newstock' where book_title = '$book_title'";
Here's what I've done trying to convert it into pdo
public function bookreserve2($q)
{
$q = "UPDATE books SET quantity WHERE = $newstock where book_title = '$book_title'";
$stmt = $this->con->prepare($q);
$stmt->execute(array(':newstock'=>$newstock));
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result;
}
Again, Is that even the correct converted query?
and how would I call $newstock?
P.S. my oop class and pdo is placed in a separate file. Thought this might help.
Thanks
You are not including your query parameters in your function, and your query has syntax errors (extra WHERE) and you are directly inserting your values not using placeholders.
It should look something like -
public function bookreserve2($newstock,$book_title)
{
$q = "UPDATE books SET quantity = :newstock WHERE book_title = :book_title";
$stmt = $this->con->prepare($q);
$stmt->execute(array(':newstock'=>$newstock,':booktitle'=>$book_title));
if($stmt){
return true;
}
else {
return false;
}
}

Converting this function to PDO

I'm working on convert this on PDO
function SystemConfig($str)
{
$tmp = mysql_query("SELECT ".$str." FROM server_status LIMIT 1") or die(mysql_error());
$tmp = mysql_fetch_assoc($tmp);
return $tmp[$str];
}
I tried this :
function SystemConfig($str)
{
global $bdd;
$tmp = $bdd->prepare("SELECT ? FROM server_status LIMIT 1");
$tmp->bindValue(1, $str, PDO::PARAM_INT);
$tmp->execute();
$tmp_res = $tmp->fetch(PDO::FETCH_ASSOC);
return $tmp_res[$str];
}
?>
But it's return 'users_online' and not the value (10000 on the database) (PS : SystemConfig('users_online');)
Someone can help me ?
Sincerly,
function SystemConfig($str)
{
static $row;
global $bdd;
if (!$row)
{
$stm = $bdd->query("SELECT * FROM server_status");
$row = $stm->fetch();
}
return $row[$str];
}
but better reorganize your table, because table with sole row is a nonsense
function SystemConfig($str)
{
global $bdd;
$stm = $bdd->prepare("SELECT value FROM server_status WHERE param = ?");
$stm->execute(array($str))
return $stm->fetchColumn();
}
You can only bind value parameters with PDO. You cannot bind column names, table names, or anything else.
You'd still have to manually construct this query by concatenating strings. To make cracker's life harder use a whitelisting approach:
if(!in_array($str, array('users_online', 'users_offline', 'free_memory')) die('...')
// rest of the function.
(But please, stop using the deprecated mysql_* functions. Upgrade to mysqli instead. Or use PDO, but without parameter binding for this one single query)

Protecting from SQL injection

I know the below code is open to injection but I am unsure what the best way to resolve it. Would it be best to repeat the $new_id line or is there something more that should be done?
Controller:
public function ajax_update_product_youtube()
{
if($_POST)
{
$id = $_POST['id'];
$new_id = mysql_real_escape_string(trim($_POST['new_id']));
$table = SITE_REF.'_ps_products';
if($new_id != "")
{
$this->Ps_products_model->update_product_youtube($table, $id, $new_id);
}
}
}
Model:
public function update_product_youtube($table, $id, $new_id)
{
$table = $this->_table_products;
$this->db->query("
UPDATE $table SET $table.youtube='$new_id' WHERE $table.id='$id'
");
}
Use CodeIgniter's Active Record (Query Builder as of 3.0), or use query bindings.
Active Record:
$this->db->where('id', $id)->update($table, array('youtube' => $new_id));
Query Bindings:
$this->db->query("UPDATE {$table} SET youtube = ? WHERE id = ?", array($new_id, $id));
You should also:
Never trust user input. Verify and sanitize before it ever reaches a DB query.
Not make the table name dynamic like this. While nothing technically "wrong" with doing that, it gives yourself more work. Don't repeat yourself, keep things simple.

Empty MySQL query result in PHP

Here's the problematic PHP function:
//Get data associated with $criteria from db
function getUserData($criteria, $value) {
//obtain user data from db based on $criteria=$value
global $pdo;
//echo $criteria . " " . $value;
try {
$sql = 'SELECT id, first, last, email, userid FROM users WHERE :criteria= :value';
//var_dump($sql);
$st = $pdo->prepare($sql);
$st->bindValue(':criteria', $criteria);
$st->bindValue(':value', $value);
$st->execute();
}
catch (PDOException $ex) {
$error = "Failed to obtain user data.";
$errorDetails = $ex->getMessage();
include 'error.html.php';
exit();
}
$row = $st->fetch();
//var_dump($row);
if ($row)
{
$userdata = array();
$userdata['id'] = $row['id'];
$userdata['first'] = $row['first'];
$userdata['last'] = $row['last'];
$userdata['email'] = $row['email'];
$userdata['userid'] = $row['userid'];
return $userdata;
}
return FALSE;
}
I use this function to return a whole row of data associated with specific column in it.
When used at it's current state, with a call like that getUserData("email", "John_Stewart_2013"), it returns false, meaning an empty result, while the same query runs fine in MySQL CLI.
If I, on the other hand, substitute the query string $sql with :
$sql = "SELECT id, first, last, email, userid FROM users WHERE $criteria='$value'";
And comment out the bindValue calls, Every thing runs fine in PHP, and the query returns as desired.
But the problem is, those function arguments are user-submitted form data, meaning the solution is vulnerable to SQL Injection.
What's wrong here in the first query form?
You can't use bindValue with column names I'm afraid.
If you think about what a prepared statement is, this should become more obvious. Basically, when you prepare a statement with the database server, it creates an execution plan for the query beforehand, rather than generating it at the time of running the query. This makes it not only faster but more secure, as it knows where it's going, and the datatypes that it will be using and which are going to be input.
If the column/table names were bindable in any way, it would not be able to generate this execution plan, making the whole prepared statement idea somewhat redundant.
The best way would be to use a hybrid query like so:
$sql = "SELECT id, first, last, email, userid FROM users WHERE $criteria = :value";
I'm going to hope that the $criteria column isn't entirely free form from the client anyway. If it is, you'd be best limiting it to a specific set of allowed options. A simplistic way to do would be to build an array of allowed columns, and check if it's valid with in_array, like so:
$allowed_columns = array('email', 'telephone', 'somethingelse');
if (!in_array($criteria, $allowed_columns))
{
$error = "The column name passed was not allowed.";
$errorDetails = $ex->getMessage();
include 'error.html.php';
exit;
}

Categories