Is $_SESSION safe from sql injects? - php

I use PDO to access my MySQL database, and want to use IN. But sadly it don't seam to work with prepare, so I wrote this function
function is_numeric_array($array){
if(!is_array($array))
return is_numeric($array);
if(is_array($array))
foreach($array as $int)
if(!is_numeric($int))
return false;
return true;
}
Then used it like this
if(!is_numeric_array($_SESSION['story'])){
die("Error, array contains non-integers");
}
$query = "(";
for($i = 0; $i<count($_SESSION['story']); $i++)
$query .= $_SESSION['story'][$i].(count($_SESSION['story'])-1 != $i ? "," : "");
$query .= ")";
//Collect all data needed
$stories = openConnection() -> query("SELECT * FROM `stories` WHERE `id` IN {$query}") -> fetchAll();
I know it, looks ugly. But I don't want any SQL injects.

You don't really have to test for the input being numeric, because in MySQL, any string e.g. '123abc' in a numeric context (like being compared to an integer column id) implicitly takes only the digits and ignores the rest. A non-numeric string like 'abc' simply has the integer value 0 because there are no leading digits.
The point is, values are safe from SQL injection if you use query parameters. Whether the inputs come from $_SESSION or another source is irrelevant. $_SESSION is neither safe or unsafe with respect to SQL injection, it's how you pass the data to your query that matters.
I would also simplify the code to format the list of parameter placeholders:
$placeholders = implode(',', array_fill(1, count((array)$_SESSION['story']), '?'));
And forget about bindParam(), just pass the array to execute().
//Collect all data needed
$storyQuery = openConnection() -> prepare("SELECT * FROM `stories`
WHERE `id` IN ({$placeholders})");
$storyQuery -> execute((array)$_SESSION['story']);
$story = $storyQuery -> fetchAll();
Re your comment:
In PDO, you can use either named parameters like :id, or you can use positional parameters, which are always ? (but don't mix these two types in a given query, use one or the other).
Passing an array to execute() automatically binds the array elements to the parameters. A simple array (i.e. indexed by integers) is easy to bind to positional parameters.
If you use named parameters, you must pass an associative array where the keys of the array match the parameter names. The array keys may optionally be prefixed with : but it's not required.
If you're new to PDO, it really pays to read the documentation. There are code examples and everything!

$_SESSION is just a way to store data on server over a session. It's not direct related with SQL injection.

Even if it's a cookie or a session , the hash that I store is alphanumeric only, for security purposes. When I'm checking the cookie/session against any type of inject / modification , I use ctype_alnum:
if (ctype_alnum($_SESSION['value']))
// exec code
else
// trigger_error
This way, no matter who is setting the SESSION value (you or the client, if you give him the possibility to), there wont be any case in which non-alphanumeric chars like comma, quotes, double quotes or whatever will be inserted.

Related

Protect Oracle database against SQL Injection

I'm on Symfony and I don't know how protect my database against sql injection. If you have some idea, I will be gratefull.
My function with sql :
public function getResult($$value)
{
$sql = "SELECT SOMETHING FROM SOMETHING smt
WHERE smt.THING = '".$value."'";
return $this->egee->executeQuery($sql);
}
And here is my executeQuery funciton :
public function executeQuery($sql) {
$entityManager = $this->em->getConnection('xxx');
$stmt = $entityManager->prepare($sql);
$stmt->execute();
return $stmt->fetch();
}
I allready try with BindParam, but it's didn't work with Oracle.
With BindParam I have this response :
Error 503 : Service Unavailable
The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.
Here's how you do it ... with any and every database: parameterized queries.
Your SQL string now becomes:
SELECT SOMETHING FROM SOMETHING smt WHERE smt.THING = ?
Notice the ? (which is not in quotes ... this is not a one-character literal string) This indicates a query parameter.
Now, each time you execute the query, you supply an array() containing each of the parameter-values you want to substitute, in order left-to-right. Different values may be used each time the query is executed (without re-preparing it), because these values are not "part of" the query: they are inputs.
No matter what the parameter-value contains, the database engine will never see it as anything other than the numeric or string value that it is. It will never regard it as "part of the SQL." Thus, SQL-injection becomes impossible.
Furthermore, the [binary] value is used directly, instead of being decoded from a character string. So, say, if you want to use quote-marks as part of your string parameter-value, you would not "encode" them with backslashes. (If you provided \", then "a backslash followed by a quote mark" is what SQL would see as the parameter's value ... a perfectly acceptable two-character value.)
Here's a nice write-up: https://www.w3schools.com/php/php_mysql_prepared_statements.asp
The documentation for Doctrine ORM in the Symfony manual shows an example of using a query parameter:
https://symfony.com/doc/current/doctrine.html#querying-with-sql
$sql = '
SELECT * FROM product p
WHERE p.price > :price
ORDER BY p.price ASC
';
$stmt = $conn->prepare($sql);
$stmt->execute(['price' => $price]);
You don't need to use BindParam. Just pass a hash array to execute(), where the hash keys are the named query parameter placeholders you put in your SQL query.

Convert array take string

I would like to know how to convert my array to a string
$formats = $_POST['formats'];
$topics = $_POST['topics'];
for example, if I echo out the above, it just prints array. I want it to display the array as a string so that I could use it below:
$resources = "select * from resources where stage LIKE '%".$stage."%' and formats LIKE '%".$formats."%' and topics LIKE '%".$topics."%'";
I have been suggested to do something like this $formats = $_POST['formats'][0];
but i want to output the entire array as a string such "idea generation, business" would be equivilant to ["idea generation", business"]
Since it isn't possible to determine which database you're using to make that query happen, I'll suggest you to build your query string using prepared statements and paremeterizing your values to a PDO object based on what you may read in PHP.net documentation on the subject.
Binds a PHP variable to a corresponding named or question mark
placeholder in the SQL statement that was used to prepare the
statement. Unlike PDOStatement::bindValue(), the variable is bound as
a reference and will only be evaluated at the time that
PDOStatement::execute() is called.
As you'll see, that way you won't have to bother converting your arrays and variables to string before accessing them, plus, you grant security to your query statements.
So, instead of implodeing a string, you'll have something like this:
<?php
/* Execute a prepared statement by binding PHP variables */
$stage = $_POST['stage'];
$formats = $_POST['formats'];
$topics = $_POST['topics'];
$stmt = $db->prepare('select * from resources where stage LIKE % :stage % and formats LIKE % :formats % and topics LIKE % :topics %');
$stmt->bindParam(':stage', $stage);
$stmt->bindParam(':formats', $formats);
$stmt->bindParam(':topics', $topics);
$stmt->execute();
?>
EDIT: as you updated that you're using MySQLi, it'll be no different.
$stmt = $mysqli_db->prepare('select * from resources where stage LIKE % ? % and formats LIKE % ? % and topics LIKE % ? %');
// assuming all your params are strings
$stmt->bind_param('sss', $stage, $formats, $topics);
$stmt->execute();
As using mysqli, since it's an unbuffered sql query handler, you should store your results if you're looping simultaneous executions with $stmt->store_result();
Any doubts about how to use mysqli and pdo objects, methods and properties can be easily found in php.net documentation (linked above).
Of course, it's just a suggeston of better practices based on your apparent needs, but you can still use the implode function to achieve your string.
Take a look at PHP implode function:
http://php.net/manual/en/function.implode.php
For example, this will turn an array into a string, separating each element with a comma:
$string = implode(',', $array);

UPDATE only provided fields in MySQL table using PHP

I have a user table with and id field and 10 other fields storing user details of various types that the user can change via various web forms. I want to have a PHP script that gets POSTed changed values for some subset of these fields, and UPDATEs only those fields that are received in the POST data. I'm finding this surprisingly difficult to do in a way that doesn't suck. We use mysqli for all database interaction in the rest of this application so mysqli-based solutions are strongly preferred.
The options I've considered and dismissed so far:
1) Run a separate UPDATE query for every field provided in the POST data - yuck, I don't want to hit the database up to 10 times for something that could be done in one query.
2) Have a dictionary mapping field names to the fields' data types, and iteratively construct the query by looping through the provided fields, checking whether they are text fields or not, calling mysqli_real_escape_string on the string fields and otherwise sanitizing the others (e.g. by type checking or sprintf with '%i' placeholders). - Yuck! I could probably safely do things this way if I was careful, but I don't want to make a habit of using this kind of approach because if I'm careless I'll leave myself open to SQL injection. Parameterized queries don't give me the potential to screw up dangerously, but this approach does. My ideal is to never concatenate any data into an SQL query manually and always rely upon parameterized queries; the database libraries of other languages, like Python, let me easily do this.
3) Use a parameterized query - this is my ideal for everything, since as long as I insert all externally-provided data into my query via the bind_param method of a mysqli statement object, I'm immune to SQL injection and don't have to worry about sanitization, but using parameterized queries seems to be impossible here. The trouble is that bind_param requires that the data be passed as variables, since all arguments after the first are passed by reference. I can reasonably elegantly iteratively construct a query with ? placeholders, and while I'm at it construct the string of types that gets passed as the first argument to bind_param ('ssiddsi' etc.), but then there's no way I can see to choose at runtime which of my 10 fields I pass to bind_params (unless I have a switch statement with 10^2 cases).
Is there some PHP language construct I'm missing (something similar to array unpacking) that will allow me to choose at runtime which variables to pass as arguments to bind_param? Or is there some other approach I haven't considered that will let me solve this simple problem cleanly and safely?
You can easily combine 2 and 3 by means of my SafeMySQL library.
The code will look like
$allowed = array('title','url','body','rating','term','type');
$data = $db->filterArray($_POST,$allowed);
$sql = "UPDATE table SET ?u WHERE id=?i";
$db->query($sql, $data, $_POST['id']);
note that $allowed array doesn't make all these fields necessarily updated - it just filters POST fields out. So, even $_POST with only id and url would be correctly updated.
Nevertheless, using prepared statements, although toilsome, also quite possible.
See the code below
public function update($data, $table, $where) {
$data_str = '' ;
foreach ($data as $column => $value) {
//append comma each time after first item
if (!empty($data_str)) $data_str .= ', ' ;
$data_str .= "$column = $value" ;
}
$sql = "UPDATE $table SET $data_str WHERE $where";
mysqli_query($sql) or die(mysqli_error());
return true;
}
$data is an array, in your case it's $_POST.
If you want to be more specific about the data to be saved from $_POST array, you can define an array of allowed columns. For example,
$allowed = array('id', 'username', 'email', 'password');
By doing this, you can filter your $_POST array and pass it to update() function.

PHP/MySQL: Using array elements in WHERE clause using prepared statements

I want to make a "dynamic" WHERE clause in my query based on a array of strings. And I want to run the created query using Mysqi's prepared statements.
My code so far, PHP:
$searchArray = explode(' ', $search);
$searchNumber = count($searchArray);
$searchStr = "tags.tag LIKE ? ";
for($i=1; $i<=$searchNumber-1 ;$i++){
$searchStr .= "OR tags.tag LIKE ? ";
}
My query:
SELECT tag FROM tags WHERE $searchStr;
More PHP:
$stmt -> bind_param(str_repeat('s', count($searchArray)));
Now this obviously gives me an error since the bind_param part only contains half the details it need.
How should I proceed?
Are there any other (better) way of doing this?
Is it secure?
Regarding the security part of the question, prepared statements with placeholders are as secure as the validation mechanism involved in filling these placeholders with values up. In the case of mysqli prepared statements, the documentation says:
The markers are legal only in certain places in SQL statements. For example, they are allowed in the VALUES() list of an INSERT statement (to specify column values for a row), or in a comparison with a column in a WHERE clause to specify a comparison value.
However, they are not allowed for identifiers (such as table or column names), in the select list that names the columns to be returned by a SELECT statement, or to specify both operands of a binary operator such as the = equal sign. The latter restriction is necessary because it would be impossible to determine the parameter type. It's not allowed to compare marker with NULL by ? IS NULL too. In general, parameters are legal only in Data Manipulation Language (DML) statements, and not in Data Definition Language (DDL) statements.
This clearly excludes any possibility of modifying the general semantic of the query, which makes it much harder (but not impossible) to divert it from its original intent.
Regarding the dynamic part of your query, you could use str_repeat in the query condition building part, instead of doing a loop:
$searchStr = 'WHERE tags.tag LIKE ?' .
str_repeat($searchNumber - 1, ' OR tags.tag LIKE ?');
For the bind_param call, you should use call_user_func_array like so:
$bindArray[0] = str_repeat('s', $searchNumber);
array_walk($searchArray,function($k,&$v) use (&$bindArray) {$bindArray[] = &$v;});
call_user_func_array(array($stmt,'bind_param'), $bindArray);
Hopefully the above snippet should bind every value of the $bindArray with its corresponding placeholder in the query.
Addenum:
However, you should be wary of two things:
call_user_func_array expects an integer indexed array for its second parameter. I am not sure how it would behave with a dictionary.
mysqli_stmt_bind_param requires its parameters to be passed by reference.
For the first point, you only need to make sure that $bindArray uses integer indices, which is the case in the code above (or alternatively check that call_user_func_array doesn't choke on the array you're providing it).
For the second point, it will only be a problem if you intend to modify the data within $bindArray after calling bind_param (ie. through the call_user_func_array function), and before executing the query.
If you wish to do so - for instance by running the same query several times with different parameters' values in the same script, then you will have to use the same array ( $bindArray) for the following query execution, and update the array entries using the same keys. Copying another array over won't work, unless done by hand:
foreach($bindArray as $k => $v)
$bindArray[$k] = some_new_value();
or
foreach($bindArray as &$v)
$v = some_new_value();
The above would work because it would not break the references on the array entries that bind_param bound with the statement when it was called earlier. Likewise, the following should work because it does not change the references which have been set earlier up.
array_walk($bindArray, function($k,&$v){$v = some_new_value();});
A prepared statement needs to have a well-defined number of arguments; it can't have any element of dynamic functionality. That means you'll have to generate the specific statement that you need and prepare just that.
What you can do – in case your code actually gets called multiple times during the existence of the database connection - is make cache of those prepared statements, and index them by the number of arguments that you're taking. This would mean that the second time you call the function with three arguments, you already have the statement done. But as prepared statements don't survive the disconnect anyway, this really only makes sense if you do multiple queries in the same script run. (I'm deliberately leaving out persistent connections, because that opens up an entirely different can of worms.)
By the way, I'm not an MySQL expert, but would it not make a difference to not have the where conditions joined,but rather writing WHERE tags in (tag1, tag2, tag3, tag4)?
Solved it by the help of an answer found here.
$query = "SELECT * FROM tags WHERE tags.tag LIKE CONCAT('%',?,'%')" . str_repeat(" OR tags.tag LIKE CONCAT('%',?,'%')", $searchNumber - 1)
$stmt = $mysqli -> prepare($query);
$bind_names[] = str_repeat('s', $searchNumber);
for ($i = 0; $i < count($searchArray); $i++){
$bind_name = 'bind'.$i; //generate a name for variable bind1, bind2, bind3...
$$bind_name = $searchArray[$i]; //create a variable with this name and put value in it
$bind_names[] = & $$bind_name; //put a link to this variable in array
}
call_user_func_array(array($stmt, 'bind_param'), &$bind_names);
$stmt -> execute();

how to cast bigint to prevent sql injection in php?

i am using php and running sql queries on a mysql server.
in order to prevent sql injections, i am using mysql_real_escape_string.
i am also using (int) for numbers casting, in the following manner:
$desired_age = 12;
$query = "select id from users where (age > ".(int)$desired_age.")";
$result = mysql_query($query);
that work.
But, when the variable contains larger numbers, casting them fails since they are larger than int.
$user_id = 5633847511239487;
$query = "select age from users where (id = ".(int)$user_id.")";
$result = mysql_query($query);
// this will not produce the desired result,
// since the user_id is actually being cast to int
Is there another way to cast large number (like BIGINT), except for the use of mysql_real_escape_string, when is comes to sql injection prevention?
If you are generating the user ID yourself there is no need to cast it for MySQL since there is no chance of SQL injection or other string issues.
If it is a user submitted value then use filter_var() (or is_numeric()) to verify it is a number and not a string.
You could use something like:
preg_replace('/[^0-9]/','',$user_id);
to replace all non numeric symbols in your string.
But there actually is no need to do so, simply use mysql_real_escape_string() as your integer value will be converted to a string anyway once $query is built.
Validate input. Don't just simply escape it, validate it, if it's a number. There're couple of PHP functions which do the trick, like is_numeric() - Finds whether a variable is a number or a numeric string
http://www.php.net/is_numeric
Use server-side prepared, parametrized statements (and thus remove the need for xyz_real_escape_string()) and/or treat the id as a string. The MySQL server has built-in rules for string<->number conversions and if you should decide to change to type/structure of the id field you don't have to change the php code as well. Unless you have concrete needs for (micro-)optimization there's usually no need to let the code make this kind of assumptions about the structure and value range of an id field in the database.
$pdo = new PDO('mysql:...');
$pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$stmt = $pdo->prepare('SELECT age FROM users WHERE id=?');
$stmt->execute(array('5633847511239487'));
After some research I've come to such setup
private function escapeInt($value)
{
if (is_float($value))
{
return number_format($value, 0, '.', ''); // may lose precision on big numbers
}
elseif(preg_match('/^-?[0-9]+$/', $value))
{
return (string)$value;
}
else
{
$this->error("Invalid value");
}
}
Separate case for the floats because $i = 184467440737095; become float on a 32-bit system and thus will crumble to scientific notation when cast to string.
And simple regexp for the rest
You can even multiply the variable by *1, you can check it min and max values you can accept (for age bigint is not an option at all... so why even allow numbers more than values you are prepared for?
And there is also PDO with its query preparing.

Categories