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;
}
Related
So I'm making my own blog scripts using MYSQL and PHP.
I had the whole 'writing the blog to a database' thing working perfectly, until I realised that if you tried to write a blog with speech marks, this would prevent the INSERT statement from working (obviously - the speechmarks were ending the SQL statement).
So I tried to use real_escape_string, and now the INSERT doesn't work even if you exclude quotes.
I tried using:
sqlstate
in order to find out the issue, and it returned "42000" - which, after googling for a little bit, refers to a syntax error, which doesn't make much sense as there is no syntax error before the use of real_escape_string.
Also, I'm now getting this error:
Call to a member function close() on a non-object in /postarticle.php on line 37
Which refers to the close() call in the ELSE statement.
Please may you help? Been going round in circles for a while. Here is my code:
<?php
$host = 'CENSORED';
$user = 'CENSORED';
$pass = 'CENSORED';
$db = 'CENSORED';
$connection = new mysqli($host,$user,$pass,$db);
$_SESSION["article"] = $_POST["article"];
$date_of_blog = getdate();
$article = ($_SESSION["article"]);
$sql1 = "SELECT * FROM `Blogs`";
$res1 = $connection->query($sql1);
$newrows = $res1->num_rows + 1;
$sql2 = "INSERT INTO Blogs(BlogID, Blog_Contents, D_O_B) VALUES ('$newrows','$article','$date_of_blog')";
$sql2 = $connection->real_escape_string($sql2);
$res2 = $connection->query($sql2);
if ($res2->num_rows == $newrows)
{
$res->close();
$connection->close();
header( 'Location: adminpanel.php' );
}
else
{
echo ($connection->sqlstate);
$connection->close();
$res->close();
}
exit();
?>
Also, on a side note, the getdate() call that I've got has never worked. In the database every blog post comes up as:
0000:00:00 00:00:00
EDIT:
Issue is now solved. Find the functional code below:
<?php
$host = 'CENSORED';
$user = 'CENSORED';
$pass = 'CENSORED';
$db = 'CENSORED';
$connection = new mysqli($host,$user,$pass,$db);
$_SESSION["article"] = $_POST["article"];
$article = ($_SESSION["article"]);
$article = $connection->real_escape_string($article);
$sql1 = "SELECT * FROM `Blogs`";
$res1 = $connection->query($sql1);
$newrows = $res1->num_rows + 1;
$sql2 = "INSERT INTO Blogs(BlogID, Blog_Contents, D_O_B) VALUES (\"$newrows\",\"$article\",CURDATE())";
$res2 = $connection->query($sql2);
if ($res2 != false)
{
header( 'Location: adminpanel.php' );
}
else
{
echo ($connection->sqlstate);
}
$connection->close();
$res->close();
exit();
?>
I'm very sorry if these questions are basic and annoy the professionals around here; I've tried to follow the guidelines and I've googled for a while etc. I just haven't found any solutions that match my issue(s).
Thankyou for your time.
There are a number issues with the code as originally posted. Chiefly, the cause of the two issues you initially identified is a misuse of mysqli::real_escape_string(). It needs to be called on each variable individually which appears in the code. So instead of calling it on the whole statement, it must be called multiple times for multiple variables like:
$article = $connection->real_escape_string($connection);
The failure of the query due to incorrect quoting (due to real_escape_string()) is the reason for the error message calling close().
As ascertained in the comments, you are using num_rows + 1 to validate that one new row has been inserted based on the previous number of rows returned. This is problematic for a few reasons. Mainly, it exposes a race condition wherein a row may be inserted from two sessions at once and one or both will fail because the expected value for $newrows doesn't match. Really BlogID should be an auto_increment column in your database. That eliminates the need for any logic around it whatsoever. You don't even need to include it in the INSERT because it will be automatically incremented.
That also completely eliminates the need for the first SELECT statement.
Substituting MySQL's native NOW() function for the date value, you can simplify the statement to:
INSERT INTO Blogs (Blog_Contents, D_O_B) VALUES ('$article', NOW())
To test success or failure of the insert, you just need to verify that its variable is not false.
Putting this together, your code can be reduced as:
if (!isset($_POST['article'])) {
// exit or handle an empty post somehow...
}
$connection = new mysqli($host,$user,$pass,$db);
$_SESSION["article"] = $_POST["article"];
// Escape $article for later use
$article = $connection->real_escape_string($_SESSION["article"]);
// Only an INSERT is needed. $article is already escaped
$sql = "INSERT INTO Blogs (Blog_Contents, D_O_B) VALUES ('$article', NOW())";
// Run the query
$res = $connection->query($sql);
// Test for failure by checking for a false value
if ($res) {
// The connection & resource closure can be omitted
// PHP will handle that automatically and implicitly.
header( 'Location: adminpanel.php' );
// Explictly exit as good practice after redirection
exit();
}
else {
// The INSERT failed. Check the error message
echo $connection->error;
}
This should bring your current code into working order. However, since you're learning this it is an excellent time to begin learning to use prepared statements via prepare()/bind_param()/execute() in MySQLi. This is a recommended best practice to prevent SQL injection, although using real_escape_string() works as long as you use it correctly and never forget.
See How can I prevent SQL injection in PHP for examples.
But it would look like:
// connection already established, etc...
// Prepare the statement using a ? placeholder for article
$stmt = $connection->prepare("INSERT INTO Blogs (Blog_Contents, D_O_B) VALUES (?, NOW())");
if ($stmt) {
// bind in the variable and execute
// Note that real_escape_string() is not needed when using
// the ? placeholder for article
$stmt->bind_param('s', $_SESSION['article']);
$stmt->execute();
// Redirect
header( 'Location: adminpanel.php' );
exit();
}
else {
echo $connection->error;
}
You need to apply the real_escape_string function to the variables not the entire SQL string.
$sql2 = "INSERT INTO Blogs(BlogID, Blog_Contents, D_O_B) VALUES ('".$connection->real_escape_string($newrows)."','".$connection->real_escape_string($article)."','".$connection->real_escape_string($date_of_blog)."')";
The purpose is to remove anything that might be misinterpreted as query functions by MySQL, but there are parts of the query that you obviously want to be interpreted as such.
I have a pretty stupid question, but I can't get it to work immediately.
How do I load only one field of the result array of a query into a session (array) using a single PDO statement?
I commented the missing code below:
public function getPermissions($user_role_id){
if(!isset($user_role_id) || empty($user_role_id)){
$_SESSION['user']['user_permissions'] = '';
}else{
$db = Database::get_database();
$query = "
SELECT
rp.role_id, rp.permission_id
FROM
role_permission_tbl rp
WHERE
rp.role_id = :role_id";
$query_params = array(
':role_id' => $user_role_id
);
try
{
$stmt = $db->prepare($query);
$result = $stmt->execute($query_params);
}
catch(PDOException $ex)
{
die("Failed to run query: " . $ex->getMessage());
}
$row = $stmt->fetchAll();
if($row){
//I only want to retrieve the field "permission_id"
$_SESSION['user']['user_permissions'] = $row;
}else{
$_SESSION['user']['user_permissions'] = '';
}
}
}
Thanks
After seeing your later comments, it looks as though you're wanting to save all permission data in a session variable so that you can look it up by permission ID:
$rows = $stmt->fetchAll();
foreach($rows as $row){
//Add to session, keyed by permission ID.
$_SESSION['user']['user_permissions'][$row['permission_id']] = $row;
}
//Then, if you want to see if said permission ID #21 exists:
if(isset($_SESSION['user']['user_permissions'][21])){
echo 'This user has permissions with ID 21!';
$permissionDetails = $_SESSION['user']['user_permissions'][21];
var_dump($permissionDetails);
}
Like any other "get it to work immediately" this question has contradicting conditions.
Like any other PHP code, it is ten times long than needed.
Like many other SO questions, it can be solved by quick manual lookup.
In case you need your permissions in array
public function getPermissions($user_role_id){
$sql = "SELECT permission_id FROM role_permission_tbl WHERE role_id = ?";
$stm = Database::get_database()->prepare($sql);
return $stm->execute(array($user_role_id))->fetchAll(PDO::FETCH_COLUMN);
}
note that assigning variables inside functions is a very bad practice. So, better call it this way
$_SESSION['user']['user_permissions'] = $user->getPermissions($user_role_id);
isset() is useless in conjunction with empty() as latter covers the former.
both isset() and empty() are useless for the function variable too, as it is always set by design
a verification for this particular input variable can be done, but for the sanely designed application it would be unnecessary.
setting a variable you are going to test with in_array() to an empty string will produce an error.
there is no use for the alias with single table.
PDO methods can be called dramatically shorter way, there is no use for stretching one simple query call to a whole screen of code.
echoing a system error message to a site user is an awful practice.
the very manual page for the fetchAll() contains an exact example for this very question of getting single column out of the query result.
there is no use for testing returned value explicitly, as it already contains either result or empty value (and luckily, fetchAll() will return even empty value of desired type).
Can you try $row = $stmt-> fetch(); instead of $row = $stmt->fetchAll(); if it is fetch only one record from table,
$row["permission_id"];
$q = $db->query(" SELECT username FROM users WHERE userident = '1' ");
echo $q; //error
print_r $q; //prints the query information (SELECT ... etc)
How do I go about getting the specific value of the element I am querying? Say the element under column username and where userident equals '1' contains the value "Patrick"; how do I initialize this string into a variable?
//same query as above
$user = $q;
echo $user; //prints "Patrick"
Sorry if this is something so rudimentary and mundane, but I've never done this outside of a foreach() loop. I'd normally iterate through rows to print specific details. The below works, but the foreach() is unnecessary as far as I understand.
foreach($q as $p) {
$user = $p["username"];
}
echo $print; //this correctly prints "Patrick"
Surely there's a method I missed somewhere?
Using the query method pretty much defeats the purpose of using prepared statements. Plus, I believe for what you're looking for, it isn't quite right.
<?php
if (!isset($_POST['id'])) {
exit;
}
$userId = $_POST['id'];
$db = new PDO(/* Stuff */);
$sql = '
SELECT username
FROM users
WHERE id = :id';
// Get a prepared statement object.
$statement = $db->prepare($sql);
// Bind a parameter to the SQL code.
$statement->bindParam(':id', $userId, PDO::PARAM_INT);
// Actually get the result.
$result = $statement->fetch(PDO::FETCH_ASSOC);
// Close the connection.
$statement->closeCursor();
// Print the result.
print_r($result);
Alternately you can use $statement->fetchAll() to gather more than one result.
Edit: I didn't actually run this code, so you might have to tinker with it to get it working right.
This has been driving me crazy, the issue is I cannot work out How i can get and set the cached data to be displayed within my view.
public function get_something($id, $account_name)
{
$sql = "SELECT one,two,three FROM table WHERE id = ? and account_name = ? ";
$key = md5("SELECT one,two,three FROM table WHERE id = $id and account_name = $account_name ");
$get_result = $this->Core->Core->Memcache->get($key);
if($get_result)
{
// How would I set the Data
}
else
{
$stmt = $this->Core->Database->prepare($sql);
$stmt->bind_param("is", $id, $account_name);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($one, $two, $three);
$stmt->fetch();
//Below is how i set the data
$this->Core->Template->set_data('one', $one);
//Set the Memcache
$this->Core->Memcache->set($key, $stmt, TRUE, 20);
}
So my question is how can I get and set the data from a prepared statement fetch within memcache?
Memcache is a key/value storage system with both the key and the value needing to be serialized. From the php.net page:
Remember that resource variables (i.e. file and connection descriptors) cannot be stored in the cache, because they cannot be adequately represented in serialized state.
It appears your sql statement is looking for three values in a single row. I'm no expert on mysqli, but this is kind of what you want to do:
public function get_something($id, $account_name){
$sql = "SELECT one,two,three FROM table WHERE id = ? and account_name = ? ";
$key = md5("SELECT one,two,three FROM table WHERE id = $id and account_name = $account_name ");
$get_result = $this->Core->Core->Memcache->get($key);
if($get_result){
return $get_result;//#1 just return it, the format is an array like what is being built below
}
else{
$stmt = $this->Core->Database->prepare($sql);
$stmt->bind_param("is", $id, $account_name);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($one, $two, $three);
$stmt->fetch();
//Below is how i set the data
$this->Core->Template->set_data('one', $one);//#2 I don't know what this line does or is for, presumably for something else besides memcache stuff, maybe it acts like return
//Set the Memcache
$array=array();//#3
$array[]=$one;
$array[]=$two;
$array[]=$three;
$this->Core->Memcache->set($key, $array, TRUE, 20);
//this is a function, do you want to return your values somewhere?
}
A few notes, #1 the answer to your question is simple, just return $get_result. It should be an array with three values. #2 I'm not familiar with this line, nor what it does. Is this how your "return" the values to your controller? If so, you'll want to mimick that line where I put the return inside the if #3 This is your problem. You can't save the $stmt variable in memcache, it's a mysqli object, not the data you want. You need to build an array and then save that array. And that should do it for you.
There are other nuances to do, you can loop on the returned values. You should check for mysql not returning anything. But this is the basic starting point to get this going.
Let me know if this works for you.
What's the correct way to code the following
SELECT * FROM table WHERE value = $row['item']
$row['item'] echos correctly, but does not seem to work in the mysql query. Been having this problem for a few days. I've tried .$row['item']. and a few other variations but I must be doing something wrong.
The better more appropriate approach is to use mysqli and prepared statements ie:
$stmt = $mysqli->prepare("SELECT * FROM table WHERE value =?");
$stmt->bind_param("s",$row['item']); // I am assuming row['item'] is a string
$stmt->execute();
If you can't use mysqli or absolutely refuse to you can use this:
$query = "SELECT * FROM table WHERE value = '".mysql_real_escape_string($row['item'])."'";
The answer sort of depends on what is held within the $row['item'] variable. If it's a numeric value, then the query above should be fine. Since it's not working, I assume that the value of that variable is actually a string. In that case, you need to surround the value in quotes so that the database can correctly identify it as a string value (otherwise, it would just be gibberish "commands" that the database can't identify, causing the query to fail).
Regardless of the above, you shouldn't be directly inserting variables into a query under pretty much any circumstances. The reason is that it opens you up to SQL injection if you're not extremely careful. For example, if your $row['item'] variable was wrapped in single quotes in the query, but contained a single quote in its value, then the database would interpret the quote within the variable as the ending quote for the entire parameter, and it would screw up the query. Worse still, a hacker could take advantage of this to end your query entirely, then add a second query of his own making onto it (or they could introduce a UNION query on the end of the original, etc.). At the very least, you should be running something like mysql_real_escape_string() on the variable before using it:
$sql = "SELECT * FROM table WHERE value = " .
mysql_real_escape_string($row['item']);
The best way to get around this and secure your queries is to use prepared statements. These are queries that have placeholders in them instead of concatenated variables. You prepare the query with these placeholders, then you issue additional commands to the database to tell it what values to place in those placeholders. The database then takes care of the tricky issue of sanitizing these variables so that they don't cause any damage. You can use PDO for this:
try {
$dbh = new PDO(DB_DSN,
DB_USER,
DB_PASS,
array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)
);
} catch (PDOException $e) {
echo "Connection failed: " . $e->getMessage();
exit();
}
// create query with a named placeholder
$sql = "SELECT * FROM table WHERE value = :value";
try {
$stmt = $dbh->prepare($sql);
// tell PDO to substitute the value in $row['item']
// for the named parameter specified above.
$stmt->bindValue(":value", $row['item']);
// execute the query and load the results into an array
$stmt->execute();
$records = $stmt->fetchAll();
} catch (PDOException $e) {
echo "Query failed: " . $e->getMessage();
exit();
}
foreach ($records as $record) {
// use db records
}
The way I usually recommend doing it is something like this:
$sql = sprintf("SELECT * FROM table WHERE value = '%s'",
mysql_real_escape_string($row['item']));
$item = mysql_real_escape_string($row['item']);
$mysqlQuery = "SELECT * FROM table WHERE value = '" . $item . "'";
you are missing single quotes
SELECT * FROM table WHERE value = '{$row['item']}'
PHP example