PHP PDO Security - php

Im trying to work with PDO for the first time and I'm just wanting to know how secure what I'm doing is, I'm also new to PHP.
I have a query that when a user is passed ot my page, the page takes a variable using GET and then runs.
With PHP I've always used mysql_real_escape to sanitize my variables.
Can anybody see security flaws with this?
// Get USER ID of person
$userID = $_GET['userID'];
// Get persons
$sql = "SELECT * FROM persons WHERE id =$userID";
$q = $conn->query($sql) or die($conn->error());
while($r = $q->fetch(PDO::FETCH_LAZY)){
echo '<div class="mis-per">';
echo '<span class="date-submitted">' . $r['date_submitted'] . '</span>';
// MORE STUF
echo '</div>';
}

Don't use query, use prepare:
http://php.net/manual/de/pdo.prepare.php
$userID = $_GET['userID'];
$sql = "SELECT * FROM persons WHERE id = :userid";
$q = $conn->prepare($sql)
$q->execute(array(':userid' => $userID ));
while($r = $q->fetch(PDO::FETCH_ASSOC)){
echo '<div class="mis-per">';
echo '<span class="date-submitted">' . $r['date_submitted'] . '</span>';
// MORE STUF
echo '</div>';
}
The SQL statement can contain zero or more named (:name) or question mark (?) parameter markers for which real values will be substituted when the statement is executed.

With anything you use, it's about how you use it rather than what you use. I'd argue that PDO itself is very safe as long as you use it properly.
$sql = "SELECT * FROM persons WHERE id =$userID";
That's bad *. Better :
$sql = "SELECT * FROM persons WHERE id = " . $conn->quote($userID);
Better :
$q = $conn->prepare('SELECT * FROM persons WHERE id = ?')->execute(array($userID));
* This is bad, and that's because if $userID is "1 OR 1", the query becomes SELECT * FROM persons WHERE id =1 OR 1 which will always return all values in the persons table.

As the comments say: Atm there is no security whatsoever against SQLI. PDO offers you (if the database driver supports it (mysql does)) Prepared Statements. Think of it like a query-template that is compiled/passed to the dbms and later filled with values.
here is an example of usage:
$sql = 'SELECT name, colour, calories
FROM fruit
WHERE calories < :calories AND colour = :colour';
//Prepare the Query
$sth = $dbh->prepare($sql);
//Execute the query with values (so no tainted things can happen)
$sth->execute(array(':calories' => 150, ':colour' => 'red'));
$red = $sth->fetchAll();

Adjust as follows (you can use either :userId or simply ? as Tom van der Woerdt suggests, even if I think the first one gives more clearness, especially when there are more than just one parameter):
$sql = "SELECT * FROM persons WHERE id =:userID";
$q = $conn->prepare( $sql );
$q->bindValue( ":userID", $userID, PDO::PARAM_INT ); // or PDO::PARAM_STR, it depends
$q->execute();
$r = $st->fetch();
...
...

Related

getting single cell from database using PDO

I am trying to get a piece of data from my database but would like to only get one cell using the PDO statement if this is possible.
Below is a screenshot of the table
The table name is called heating
I am trying to get the data from column called 'garage' and row id = 3
I have tried many ways but keep failing. The following is what I have so far but only returns the column name garage for some reason.
I am using the following which gives me the name garage
$room = 'garage';
require_once "connect.php";
$sql = 'SELECT :name FROM heating WHERE id = 3';
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':name', $room);
$stmt->execute();
$sw = $stmt->fetch();
echo $sw[0];
If I do the following I gives me the correct outcome but I would like to replace garage with a variable
$sql = 'SELECT garage FROM heating WHERE id = 3';
$stmt = $pdo->prepare($sql);
$stmt->execute();
$sw = $stmt->fetch();
echo $sw[0];
You can create a white list of your column names and use it to select the right column. You can check the column against a white list with the help of in_array. The third parameter is very important as it checks that string is a string. You can only then safely concatenate the SQL with your PHP variables using PHP concatenation operator. For the good measure, the column names should be enclosed in backticks `, in case any of your column names is a reserved word or contains special characters.
$whiteListOfHeating = [
'keyName',
'den',
'WC1',
'hallway',
'garage'
];
$room = 'garage';
if (in_array($room, $whiteListOfHeating, true)) {
$sql = 'SELECT `'.$room.'` FROM heating WHERE id = 3';
$stmt = $pdo->prepare($sql);
// ...
} else {
echo 'Invalid column name specified!';
}
Sometimes simplest solutions are best.
require_once "connect.php";
$room = 'garage';
$sql = 'SELECT * FROM heating WHERE id = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([3]);
$sw = $stmt->fetch();
echo $sw[$room];
Besides, every time you need such a functionality, in means that most likely your database structure is wrong. A room should be a row, not column
require_once "connect.php";
$room = 'garage';
$sql = 'SELECT value FROM heating_room WHERE heating_id=3 and room = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([$room]);
$sw = $stmt->fetchColumn();
echo $sw;
will make it straight

How to solve Illegal string offset ['id'] in php?

I'm sorry if this is a duplicate question but I don't know how to solve my problem. Every time I try to correct my error I fail. My code is:
if (isset($_GET["comment"])) {$id = $_GET["comment"];}
$query = "SELECT * FROM posts WHERE id = {$id['$id']};";
$get_comment = mysqli_query($con, $query);
Can anybody correct the code to not show an error anymore and tell me what did I wrong?
Try this:
$id = isset($_GET['comment']) ? $_GET['comment'] : 0;
$query = "SELECT * FROM `posts` WHERE `id` = " . intval($id);
The use of intval will protect you from SQL injection in this particular case. Ideally, you should learn PDO as it is extremely powerful and makes prepared statements much easier to handle to prevent all injections.
An example using PDO might look like:
$id = isset($_GET['comment']) ? $_GET['comment'] : 0;
$query = $pdo->prepare("SELECT * FROM `posts` WHERE `id` = :id");
$query->execute(array("id"=>$id));
$result = $query->fetch(PDO::FETCH_ASSOC); // for a single row
// $results = $query->fetchAll(PDO::FETCH_ASSOC); // for multiple rows
var_dump($result);
First of all you should prevent injestion.
if (isset($_GET["comment"])){
$id = (int)$_GET["comment"];
}
Notice, $id contanis int.
$query = "SELECT * FROM posts WHERE id = {$id}";
Assuming your $id is an integer and you only want to make the query if it is set, here's how you could do it using prepared statements, which protect you from MYSQL injection attacks:
if (isset($_GET["comment"])) {
$id = $_GET["comment"];
$stmt = mysqli_prepare($con, "SELECT * FROM posts WHERE id = ?");
mysqli_stmt_bind_param($stmt, 'i', $id);
mysqli_stmt_execute($stmt);
mysqli_stmt_bind_result($stmt, $get_comment);
while (mysqli_stmt_fetch($stmt)) {
// use $get_comment
}
mysqli_stmt_close($stmt);
}
Most of these functions return a boolean indicating whether they were successful or not, so you might want to check their return values.
This approach looks a lot more heavy duty and is arguably overkill for a simple case of a statement containing a single integer but it's a good practice to get into.
You might want to look at the object-oriented style of mysqli which you might find a little cleaner-looking, or alternatively consider using PDO.

Unable to concatenate sql in pdo statement [duplicate]

This question already has answers here:
How to include a PHP variable inside a MySQL statement
(5 answers)
Closed 2 years ago.
I currently have a Get varible
$name = $_GET['user'];
and I am trying to add it to my sql statement like so:
$sql = "SELECT * FROM uc_users WHERE user_name = ". $name;
and run
$result = $pdo -> query($sql);
I get an invalid column name. But that doesn't make sense because if I manually put the request like so
$sql = "SELECT * FROM uc_users WHERE user_name = 'jeff'";
I get the column data, just not when I enter it as a get variable. What am I doing wrong. I am relatively new to pdo.
Update:
Now I have the following:
$name = $_GET['user'];
and
$sql = "SELECT * FROM uc_users WHERE user_name = :name";
//run the query and save the data to the $bio variable
$result = $pdo -> query($sql);
$result->bindParam( ":name", $name, PDO::PARAM_STR );
$result->execute();
but I am getting
> SQLSTATE[42000]: Syntax error or access violation: 1064 You have an
> error in your SQL syntax; check the manual that corresponds to your
> MySQL server version for the right syntax to use near ':name' at line
> 1
For your query with the variable to work like the one without the variable, you need to put quotes around the variable, so change your query to this:
$sql = "SELECT * FROM uc_users WHERE user_name = '$name'";
However, this is vulnerable to SQL injection, so what you really want is to use a placeholder, like this:
$sql = "SELECT * FROM uc_users WHERE user_name = :name";
And then prepare it as you have:
$result = $pdo->prepare( $sql );
Next, bind the parameter:
$result->bindParam( ":name", $name, PDO::PARAM_STR );
And lastly, execute it:
$result->execute();
I find this best for my taste while preventing SQL injection:
Edit: As pointed out by #YourCommonSense you should use a safe connection as per these guidelines
// $conn = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);
$sql = 'SELECT * FROM uc_users WHERE user_name = ?';
$stmt = $conn->prepare($sql);
$stmt->bind_param('s', $name);
$stmt->execute();
$result = $stmt->get_result();
$stmt->close();
// perhaps you'll need these as well
$count = $result->num_rows;
$row = $result->fetch_assoc();
/* you can also use it for multiple rows results like this
while ($row = $result->fetch_assoc()) {
// code here...
} */
BTW, if you had more parameters e.g.
$sql = 'SELECT * FROM table WHERE id_user = ? AND date = ? AND location = ?'
where first ? is integer and second ? and third ? are string/date/... you would bind them with
$stmt->bind_param('iss', $id_user, $date, $location);
/*
* i - corresponding variable has type integer
* d - corresponding variable has type double
* s - corresponding variable has type string
* b - corresponding variable is a blob and will be sent in packets
*/
Source: php.net
EDIT:
Beware! You cannot concatenate $variables inside bind_param
Instead you concatenate before:
$full_name = $family_name . ' ' . $given_name;
$stmt->bind_param('s', $full_name);
Try this .You didn't put sigle quote against variable.
$sql = "SELECT * FROM uc_users WHERE user_name = '". $name."'";
Note: Try to use Binding method.This is not valid way of fetching data.
$sql = "SELECT * FROM 'uc_users' WHERE user_name = '". $name."' ";

Possible to have PHP MYSQL query ignore empty variable in WHERE clause?

Not sure how I can do this. Basically I have variables that are populated with a combobox and then passed on to form the filters for a MQSQL query via the where clause. What I need to do is allow the combo box to be left empty by the user and then have that variable ignored in the where clause. Is this possible?
i.e., from this code. Assume that the combobox that populates $value1 is left empty, is there any way to have this ignored and only the 2nd filter applied.
$query = "SELECT * FROM moth_sightings WHERE user_id = '$username' AND location = '$value1' AND english_name = $value2 ";
$result = mysql_query($query) or die(mysql_error());
$r = mysql_numrows($result);
Thanks for any help.
C
Use
$where = "WHERE user_id = '$username'";
if(!empty($value1)){
$where .= "and location = '$value1'";
}
if(!empty($value2 )){
$where .= "and english_name= '$value2 '";
}
$query = "SELECT * FROM moth_sightings $where";
$result = mysql_query($query) or die(mysql_error());
$r = mysql_numrows($result);
Several other answers mention the risk of SQL injection, and a couple explicitly mention using prepared statements, but none of them explicitly show how you might do that, which might be a big ask for a beginner.
My current preferred method of solving this problem uses a MySQL "IF" statement to check whether the parameter in question is null/empty/0 (depending on type). If it is empty, then it compares the field value against itself ( WHERE field1=field1 always returns true). If the parameter is not empty/null/zero, the field value is compared against the parameter.
So here's an example using MySQLi prepared statements (assuming $mysqli is an already-instantiated mysqli object):
$sql = "SELECT *
FROM moth_sightings
WHERE user_id = ?
AND location = IF(? = '', location, ?)
AND english_name = ?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param('ssss', $username, $value1, $value1, $value2);
$stmt->execute();
(I'm assuming that $value2 is a string based on the field name, despite the lack of quotes in OP's example SQL.)
There is no way in MySQLi to bind the same parameter to multiple placeholders within the statement, so we have to explicitly bind $value1 twice. The advantage that MySQLi has in this case is the explicit typing of the parameter - if we pass in $value1 as a string, we know that we need to compare it against the empty string ''. If $value1 were an integer value, we could explicitly declare that like so:
$stmt->bind_param('siis', $username, $value1, $value1, $value2);
and compare it against 0 instead.
Here is a PDO example using named parameters, because I think they result in much more readable code with less counting:
$sql = "SELECT *
FROM moth_sightings
WHERE user_id = :user_id
AND location = IF(:location_id = '', location, :location_id)
AND english_name = :name";
$stmt = $pdo->prepare($sql);
$params = [
':user_id' => $username,
':location_id' => $value1,
':name' => $value2
];
$stmt->execute($params);
Note that with PDO named parameters, we can refer to :location_id multiple times in the query while only having to bind it once.
if ( isset($value1) )
$query = "SELECT * FROM moth_sightings WHERE user_id = '$username' AND location = '$value1' AND english_name = $value2 ";
else
$query = "SELECT * FROM moth_sightings WHERE user_id = '$username' AND english_name = $value2 ";
But, you can also make a function to return the query based on the inputs you have.
And also don't forget to escape your $values before generating the query.
1.) don't use the simply mysql php extension, use either the advanced mysqli extension or the much safer PDO / MDB2 wrappers.
2.) don't specify the full statement like that (apart from that you dont even encode and escape the values given...). Instead use something like this:
sprintf("SELECT * FROM moth_sightings WHERE 1=1 AND %s", ...);
Then fill that raw query using an array holding all values you actually get from your form:
$clause=array(
'user_id="'.$username.'"',
'location="'.$value1.'"',
'english_name="'.$value2.'"'
);
You can manipulate this array in any way, for example testing for empty values or whatever. Now just implode the array to complete the raw question from above:
sprintf("SELECT * FROM moth_sightings WHERE 1=1 AND %s",
implode(' AND ', $clause) );
Big advantage: even if the clause array is completely empty the query syntax is valid.
First, please read about SQL Injections.
Second, $r = mysql_numrows($result) should be $r = mysql_num_rows($result);
You can use IF in MySQL, something like this:
SELECT * FROM moth_sightings WHERE user_id = '$username' AND IF('$value1'!='',location = '$value1',1) AND IF('$value2'!='',english_name = '$value2',1); -- BUT PLEASE READ ABOUT SQL Injections. Your code is not safe.
Sure,
$sql = "";
if(!empty($value1))
$sql = "AND location = '{$value1}' ";
if(!empty($value2))
$sql .= "AND english_name = '{$value2}'";
$query = "SELECT * FROM moth_sightings WHERE user_id = '$username' {$sql} ";
$result = mysql_query($query) or die(mysql_error());
$r = mysql_numrows($result);
Be aware of sql injection and deprecation of mysql_*, use mysqli or PDO instead
I thought of two other ways to solve this:
SELECT * FROM moth_sightings
WHERE
user_id = '$username'
AND location = '%$value1%'
AND english_name = $value2 ";
This will return results only for this user_id, where the location field contains $value1. If $value1 is empty, this will still return all rows for this user_id, blank or not.
OR
SELECT * FROM moth_sightings
WHERE
user_id = '$username'
AND (location = '$value1' OR location IS NULL OR location = '')
AND english_name = $value2 ";
This will give you all rows for this user_id that have $value1 for location or have blank values.

Ignore particular WHERE criteria

I want to execute a parameterized query to perform a search by user-supplied parameters. There are quite a few parameters and not all of them are going to be supplied all the time. How can I make a standard query that specifies all possible parameters, but ignore some of these parameters if the user didn't choose a meaningful parameter value?
Here's an imaginary example to illustrate what I'm going for
$sql = 'SELECT * FROM people WHERE first_name = :first_name AND last_name = :last_name AND age = :age AND sex = :sex';
$query = $db->prepare($sql);
$query->execute(array(':first_name' => 'John', ':age' => '27');
Obviously, this will not work because the number of provided parameters does not match the number of expected parameters. Do I have to craft the query every time with only the specified parameters being included in the WHERE clause, or is there a way to get some of these parameters to be ignored or always return true when checked?
SELECT * FROM people
WHERE (first_name = :first_name or :first_name is null)
AND (last_name = :last_name or :last_name is null)
AND (age = :age or :age is null)
AND (sex = :sex or :sex is null)
When passing parameters, supply null for the ones you don't need.
Note that to be able to run a query this way, emulation mode for PDO have to be turned ON
First, start by changing your $sql string to simply:
$sql = 'SELECT * FROM people WHERE 1 = 1';
The WHERE 1 = 1 will allow you to not include any additional parameters...
Next, selectively concatenate to your $sql string any additional parameter that has a meaningful value:
$sql .= ' AND first_name = :first_name'
$sql .= ' AND age = :age'
Your $sql string now only contains the parameters that you plan on providing, so you can proceed as before:
$query = $db->prepare($sql);
$query->execute(array(':first_name' => 'John', ':age' => '27');
If you can't solve your problem by changing your query... There are several libraries that help with assembling queries. I've used Zend_Db_Select in the past but every framework likely has something similar:
$select = new Zend_Db_Select;
$select->from('people');
if (!empty($lastName)) {
$select->where('lastname = ?', $lastname);
}
$select->order('lastname desc')->limit(10);
echo $select; // SELECT * FROM people WHERE lastname = '...' ORDER BY lastname desc LIMIT 10
I've tested the solution given by #juergen but it gives a PDOException since number of bound variables does not match. The following (not so elegant) code works regardless of no of parameters:
function searchPeople( $inputArr )
{
$allowed = array(':first_name'=>'first_name', ':last_name'=>'last_name', ':age'=>'age', ':sex'=>'sex');
$sql = 'SELECT * FROM sf_guard_user WHERE 1 = 1';
foreach($allowed AS $key => $val)
{
if( array_key_exists( $key, $inputArr ) ){
$sql .= ' AND '. $val .' = '. $key;
}
}
$query = $db->prepare( $sql );
$query->execute( $inputArr );
return $query->fetchAll();
}
Usage:
$result = searchPeople(array(':first_name' => 'John', ':age' => '27'));

Categories