I'm creating a PDO class to use on my projects, but since I'm new to it I'm not being able to bind parameters to a prepared sql statement, with not error whatsoever. Here's the function that is ment to do it :
# ::bindParam
public static function bind()
{
# get function arguments
$args = func_get_args();
# check for any arguments passed
if (count($args) < 1)
{
return false;
}
foreach ($args as $params)
{
# named variables for convenience
$parameter = $params[0];
$variable = $params[1];
$data_type = isset($params[2]) ? $params[2] : PDO::PARAM_STR;
$length = isset($params[3]) ? $params[3] : null;
# bind param to query
Database::$statement->bindParam($parameter, $variable, $data_type, $length) or die('error');
}
}
and a prepared sql statement :
SELECT * FROM `users` WHERE `email` = :email AND `password` = :password LIMIT 1
Can someone point me in the right direction? The query produces no errors at this point. Note that I am assuming the problem is here, although it might not, since I'm only using bindParam() and prepare().
edit - trigger code
$email = $_POST['email'];
$password = $_POST['password'];
$password = hash('sha256', $password);
$this->db->prepare('SELECT * FROM `users` WHERE `email` = :email AND `password` = :password LIMIT 1');
$this->db->bind(
array(':email', $email),
array(':password', $password)
);
$status = $this->db->execute();
if ($status)
{
$result = $this->db->fetch('assoc');
$this->template->user = $result;
}
else
{
$this->template->user = false;
}
As #YourCommonSense already mentioned, raw PDO interface is a little bit clearer, however the problem is probably due to the use of function PDOStatement::bindParam() instead of PDOStatement::bindValue().
The difference between those two is that, the first one takes a variable reference, which is constantly overwritten in your foreach loop, while the last one takes the actual value of the variable.
If you're looking for some more friendly database connection interface, why won't you try Doctrine DBAL?
Just get rid of this function, PDO already has it
$email = $_POST['email'];
$password = $_POST['password'];
$password = hash('sha256', $password);
$this->db->prepare('SELECT * FROM `users` WHERE `email` = :email AND `password` = :password LIMIT 1');
$stmt = $this->db->execute(array(':email'=> $email,':password' => $password));
$this->template->user = $this->db->fetch();
That's all code you need (assuming your class' execute is a regular PDO execute)
Or, to make it in raw PDO:
$email = $_POST['email'];
$password = $_POST['password'];
$password = hash('sha256', $password);
$sql = 'SELECT * FROM users WHERE email = ? AND password = ? LIMIT 1';
$stmt = $this->db->prepare($sql);
$stmt->execute(array($email, $password));
$this->template->user = $stmt->fetch();
So, it seems your class require more code than raw PDO. Are you certainly sure you need this class at all?
Related
I need to change the first if statement into a PDO statement but I'm not sure how to go about it. Please can someone help?
When users submit a form I want their email address to be pulled from the users table on the database into this page on the website, using the numbered $id they are assigned when they sign up.
$table = 'suggestions';
$id = (isset($_SESSION['u_id']) ? $_SESSION['u_id'] : null);
if ( NULL !== $id) {
$sql = mysqli_query($conn, "SELECT email FROM users WHERE u_id='$id'");
$fetch = mysqli_fetch_assoc($sql);
$email = $fetch['email'];
}
$email;
$optionOne = '';
$optionTwo = '';
$suggestions = selectAll($table);
if (isset($_POST['new-suggestion'])) {
global $conn;
$id;
$email;
$optionOne = $_POST['optionOne'];
$optionTwo = $_POST['optionTwo'];
$sql = "INSERT INTO $table (user_id, email, option_1, option_2) VALUES (?, ?, ?, ?)";
if (!empty($optionOne) && !empty($optionTwo)) {
$stmt = $conn->prepare($sql);
$stmt->bind_param('ssss', $id, $email, $optionOne, $optionTwo);
$stmt->execute();
} else {
echo "All options must be entered";
}
}
Make a connection
Firstly you need to replace your mysqli connection with a PDO one (or at least add the PDO connection alongside the mysqli one!).
// Define database connection parameters
$db_host = "127.0.0.1";
$db_name = "name_of_database";
$db_user = "user_name";
$db_pass = "user_password";
// Create a connection to the MySQL database using PDO
$pdo = new pdo(
"mysql:host={$db_host};dbname={$db_name}",
$db_user,
$db_pass,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => FALSE
]
);
Updating your code
Prepared statements with mysqli and PDO
It's almost always better to use prepared statements when putting variable data into an SQL query. Not only is it safer (if the data comes from any sort of user generated input) but it also makes it easier to read, and easier to run multiple times with different values.
Prepared query with mysqli:
$sql = "SELECT column1, column2 FROM table WHERE column3 = ? AND column4 = ?";
$query = $mysqli->prepare($sql);
$query->bind_param("si", $string_condition, $int_condition);
$query->execute();
$query->store_result();
$query->bind_result($column1, $column2);
$query->fetch();
echo "Column1: {$column1}<br>";
echo "Column2: {$column2}";
Prepared query with PDO:
$sql = "SELECT column1, column2 FROM table WHERE column3 = ? AND column4 = ?";
$query = $pdo->prepare($sql);
$query->execute([$string_condition, $int_condition]);
$row = $query->fetchObject();
# $row = $query->fetch(); // Alternative to get indexed and/or associative array
echo "Column1: {$row->column1}<br>";
echo "Column2: {$row->column2}";
Updated code
// Using the NULL coalescing operator here is shorter than a ternary
$id = $_SESSION['u_id'] ?? NULL;
if($id) {
$sql = "SELECT email FROM users WHERE u_id = ?";
$query = $pdo->prepare($sql); // Prepare the query
$query->execute([$id]); // Bind the parameter and execute the query
$email = $query->fetchColumn(); // Return the value from the database
}
// Putting "$email" on a line by itself does nothing for your code. The only
// thing it does is generate a "Notice" if it hasn't been defined earlier in
// the code. Best use:
// - The ternary operator: $email = (isset($email)) ? $email : "";
// - The NULL coalescing operator: $email = $email ?? "";
// - OR initialize it earlier in code, before the first `if`, like: $email = "";
// N.B. Instead of "" you could use NULL or FALSE as well. Basically in this case
// anything that equates to BOOL(FALSE); so we can use them in `if` statements
// so the following (2 commented lines and 1 uncommented) are effectively
// interchangeable.
$email = $email ?? "";
# $email = $email ?? FALSE;
# $email = $email ?? NULL;
// Presumably you will also want to change this function to PDO and prepared statements?
// Although it doesn't actually do anything in the code provided?
$suggestions = selectAll($table);
// Same as with email, we're just going to use the NULL coalescing operator.
// Note: in this case you had used the third option from above - I've just
// changed it so there is less bloat.
$optionOne = $_POST['optionOne'] ?? "";
$optionTwo = $_POST['optionTwo'] ?? "";
$newSuggestion = $_POST['new-suggestion'] ?? "";
// There's no point nesting `if` statements like this when there doesn't appear to be any
// additional code executed based on the out come of each statement? Just put it into one.
// We now don't need to use empty etc. because an empty, false, or null string all.
// equate to FALSE.
if($newSuggestion && $id && $email && $optionOne && $optionTwo) {
// Not sure why you've made the the table name a variable UNLESS you have multiple tables
// with exactly the same columns etc. and need to place in different ones at different
// times. Which seems unlikely so I've just put the table name inline.
$sql = "INSERT INTO suggestions (user_id, email, option_1, option_2) VALUES (?, ?, ?, ?)";
$query = $pdo->prepare($sql);
$query->execute([$id, $email, $optionOne, $optionTwo]);
}
else{
echo "All options must be entered";
}
Without comments
$id = $_SESSION['u_id'] ?? NULL;
if($id) {
$sql = "SELECT email FROM users WHERE u_id = ?";
$query = $pdo->prepare($sql);
$query->execute([$id]);
$email = $query->fetchColumn();
}
$email = $email ?? "";
$suggestions = selectAll($table);
$optionOne = $_POST['optionOne'] ?? "";
$optionTwo = $_POST['optionTwo'] ?? "";
$newSuggestion = $_POST['new-suggestion'] ?? "";
if($newSuggestion && $id && $email && $optionOne && $optionTwo) {
$sql = "INSERT INTO suggestions (user_id, email, option_1, option_2) VALUES (?, ?, ?, ?)";
$query = $pdo->prepare($sql);
$query->execute([$id, $email, $optionOne, $optionTwo]);
}
else{
echo "All options must be entered";
}
2 days ago, a hacker got into a Admin Account. He told us that login.php is vulnerable.
But I can't find out how as I escaped the inputs:
$salt = '78sdjs86d2h';
$username = mysqli_real_escape_string($DB_H, addslashes($_POST['username']));
$password = mysqli_real_escape_string($DB_H, addslashes($_POST['password']));
$hash1 = hash('sha256', $password . $salt);
$hash = strtoupper($hash1);
$check = mysqli_query($DB_H, "SELECT * FROM players WHERE Name='$username' && Password = '$hash'");
if(mysqli_num_rows($check) != 0)
Unless you are using some peculiar encoding, the code you posted, although it makes very little sense, is invulnerable to SQL injection. It will rather don't let a honest user to login, but there is no way to hack it through SQL injection.
The vulnerability were of the other kind, XSS for example.
Its better to use prepare statements to avoid sql injection. For example
$check = mysqli_query($DB_H, "SELECT * FROM players WHERE Name='$username' && Password = '$hash'")
use it like this
$check = $DB_H->prepare("SELECT * FROM players WHERE Name=? && Password = ?")
$check->bind_param('ss',$username,$hash);
$check->execute();
Test with this:
$sHost = 'localhost';
$sDb = 'test';
$sUser = 'user';
$sPassword = 'password';
$oDb = new PDO("mysql:host={$sHost};dbname={$sDb}", $sUser, $sPassword);
$salt = '78sdjs86d2h';
$username = $_POST['username'];
$password = $_POST['password'];
$hash1 = hash('sha256', $password . $salt);
$hash = strtoupper($hash1);
$sSql = 'SELECT * FROM players WHERE Name = :username AND Password = :password';
$oStmt = $oDb->prepare($sSql);
$oStmt->bindParam(':username', $username, PDO::PARAM_STR);
$oStmt->bindParam(':password', $hash, PDO::PARAM_STR);
if($oStmt->execute()){
$oRow = $oStmt->fetch(PDO::FETCH_OBJ);
if(false === $oRow){
echo 'User or password not valid';
} else {
echo 'Uer and password valid!!!';
}
} else {
echo 'Error';
}
Instead of these mysqli functions go for using PDO statements. Here is reference
PHP PDO Documentation
I have a Javascript script that sends an Ajax request via POST to the following PHP script. $db is passed from up above, but I know that part works. I'm getting a 500 Internal Server Error from this. I am unable to see the server logs, so I can't get more detailed information than this. Anyone know what I can do to fix it?
function register($db) {
$user = $_POST['username'];
$pass = $_POST['password'];
$query = "SELECT username FROM users WHERE username='$user'";
$result = mysqli_query($db, $query);
if (mysqli_fetch_array($result)[0] == $user) {
echo "taken";
}
else {
$query = "INSERT INTO users (username, password) VALUES ('$user', '$pass')";
if (mysqli_query($db, $query)) {
echo "success";
}
}
}
This is more of a corollary than an actual answer, but it is far too long for the format of a comment.
For the sake of a followup to #MarcB's comment, you should change your code to be invulnerable to SQL injection:
$user = $_POST['username'];
$pass = $_POST['password'];
$link = new mysqli($host, $username, $password, $database);
if(!$link)
{
die("Unable to connect to MySQL");
}
$stmt = $link->prepare("SELECT username FROM users WHERE username=?");
$stmt->bind_param("s", $user);
$stmt->bind_result($testUsername);
$stmt->execute();
$stmt->store_result();
$stmt->fetch();
if($stmt->num_rows > 0)
{
echo "taken";
}
else
{
$stmt = $link->prepare("INSERT INTO users (username, password) VALUES(?, ?)");
$stmt->bind_param("ss", $user, $pass);
$stmt->execute();
echo "success";
}
I believe you have a parse error which is fixed in later versions of PHP. The following line of code is not valid <= PHP 5.4:
if (mysqli_fetch_array($result)[0] == $user) {
In order to fix this, you need to change that line to the following:
$row = mysqli_fetch_array($result);
if ($row[0] == $user) {
Also, your code also has a pretty severe SQL injection vulnerability. You need to sanitise your variables before using them in queries. For integers and floats, I recommend you do the following:
$myInt = (isset($_POST['myInt']) ? (int)$_POST['myInt'] : null);
$myFloat = (isset($_POST['myFloat']) ? (float)$_POST['myFloat'] : null);
For strings, you should use mysqli_real_escape_string() as follows:
$myStr = (isset($_POST['myStr']) ? mysqli_real_escape_string($_POST['myStr']) : null);
In your case, your $user and $pass variables need to be sanitised as the string is above. The reason for forcing the types of numbers you're receiving via $_POST is that it entirely negates any possibility of someone passing through a value which is not valid. Anything which is not 0, 1, 2, 3, 4, etc that is cast as (int) will become 0.
I am trying to use a function to get the password of the user based on the username that is entered. However I can not seem to get the result to be a string. How to i transfer it to a string? I just want the value that is stored in the password column.
here is the function
public function get_password($username) {
global $pdo;
$query = $pdo->prepare("SELECT `password` FROM `users` WHERE `username` = ?");
$query->bindValue(1, $username);
$query->execute();
$query->fetch(PDO::FETCH_ASSOC);
return $query;
}
i want this because i keep getting a error saying that a parameter that uses the result from this function expects string, array given, so i assume the result from the function needs to be a string not an array
EDIT: I tried adding $query['password'] but it returned an error that said Cannot use object of type PDOStatement as array
Use this:
public function get_password($username) {
global $pdo;
$query = $pdo->prepare("SELECT `password` FROM `users` WHERE `username` = ?");
$query->bindValue(1, $username);
$query->execute();
$password = "";
while($row = $query->fetch(PDO::FETCH_ASSOC))
{
$password = $row['password'];
}
return $password;
}
try this
while($row = $query->fetch(PDO::FETCH_ASSOC))
{
$password = $row['password'];
}
Continuing from this topic where we explained most problems with PDO How to successfully rewrite old mysql-php code with deprecated mysql_* functions? now about understanding prepared statements... So in order to get remove mysql_* strings there are some examples so my question for all and other users may this find helpfull which solution is the best ... so example of old "made up* code:
in config.php:
$db = new dbConn('127.0.0.1', 'root', 'pass', 'people', 'login');
in login.php
$db->selectDb("login");
$query = mysql_query("SELECT * FROM account WHERE id='".$_session["id"]."' LIMIT 1");
$result = mysql_fetch_array($query);
$_session["id"] is defined when login actually, so now we have several options to do so:
In config.php:
$db_people = new PDO('mysql:host=127.0.0.1;dbname=people;charset=UTF-8', 'root', 'pass');
$db_login = new PDO('mysql:host=127.0.0.1;dbname=login;charset=UTF-8', 'root', 'pass');
And in login.php 1):
$stmt = $db_login->prepare("SELECT * FROM account WHERE id=? LIMIT 1");
$stmt->execute(array($_session["id"]));
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
Or this one is better when exclude query? Or the previous one is better?
And in login.php 2):
$query = "SELECT * FROM account WHERE id=? LIMIT 1";
$parameters = array($_session["id"]);
$statement = $db_login->prepare($query);
$statement->execute($parameters);
$results = $statement->fetch(PDO::FETCH_ASSOC);
And this login form:
public function login($user, $password)
{
global $web, $db;
if (!empty($user) && !empty($password))
{
$user = $web->esc($user);
$password = $web->doHash($user, $password);
$db->selectDb('login');
$qw = mysql_query("SELECT * FROM account WHERE username='".$user."' AND pass_hash='".$password."'");
if (mysql_num_rows($qw) > 0)
{
$result = mysql_fetch_array($qw);
$_session['name'] = $result['username'];
$_session['id'] = $result['id'];
return true;
}
else
return false;
}
else
return false;
}
Transfered into this form:
public function login($user, $password)
{
global $web, $db_login;
if (!empty($user) && !empty($password))
{
$user = $web->esc($user);
$password = $web->doHash($user, $password);
$stmt = $db_login->prepare("SELECT * FROM account WHERE username=? AND pass_hash=?");
$stmt->execute(array($user, $password));
$rows = $stmt->rowCount();
if ($rows > 0)
{
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
$_session['name'] = $result['username'];
$_session['id'] = $result['id'];
return true;
}
else
return false;
}
else
return false;
}
Is it ok or again do separate query or maybe do it in complete different way? Thank you all.
Also when there is multiple stmt should I use different name for it? For example I use stmt once and make a result1 after I do stmt second with result2 should I choose different name also for stmt variable or only result name is ok to be different?
OK so solution login.php 1) seems to be ok simple and no rush.
Also the login page seems to be working fine and therefore it should be according to every rules and ok :)