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.
Related
I have made a login system where the inserted_id and inserted_password are sent to the login.inc.php via the XMLHttpRequest. I'm not sure if my php script is secure. I need some securing advice for my script.
..............................................................................
login.inc.php:
<?php
session_start();
$conn = mysqli_connect("localhost", "root", "", "users");
$params = json_decode(file_get_contents('php://input'), true);
$inserted_id = $params['inserted_id'];
$inserted_password = $params['inserted_password'];
$stmt = mysqli_stmt_init($conn);
if (mysqli_stmt_prepare($stmt, "SELECT * FROM user WHERE account_name=? OR email=?;")) {
mysqli_stmt_bind_param($stmt, "ss", $inserted_id, $inserted_id);
mysqli_stmt_execute($stmt);
$row = mysqli_fetch_assoc(mysqli_stmt_get_result($stmt));
if ($row == null) {
echo ("DOESNT EXISTS");
} else {
if (password_verify($inserted_password, $row['password'])) {
$_SESSION['user_id'] = $row['id'];
echo("SUCCESS");
} else {
echo("PASSWORD_FAIL");
}
}
}
?>
signup.inc.php:
<?php
$conn = mysqli_connect("localhost", "root", "", "users");
$params = json_decode(file_get_contents('php://input'), true);
$inserted_first_name = $params['first_name'];
$inserted_last_name = $params['last_name'];
$inserted_dob = $params['dob'];
$inserted_email = $params['email'];
$inserted_account_name = $params['account_name'];
$inserted_password = $params['password'];
$stmt = mysqli_stmt_init($conn);
if (mysqli_stmt_prepare($stmt, "SELECT * FROM user WHERE email=?;")) {
mysqli_stmt_bind_param($stmt, "s", $inserted_email);
mysqli_stmt_execute($stmt);
if (mysqli_num_rows(mysqli_stmt_get_result($stmt)) > 0) {
echo("EMAIL_TAKEN");
} else {
$hashed_password = password_hash($inserted_password, PASSWORD_DEFAULT);
$created_id = rand(111111111, 999999999);
$stmt = mysqli_stmt_init($conn);
if (mysqli_stmt_prepare($stmt, "INSERT INTO user(id, first_name, last_name, dob, email, account_name, password) VALUES (?, ?, ?, ?, ?, ?, ?);")) {
mysqli_stmt_bind_param($stmt, "issssss", $created_id, $inserted_first_name, $inserted_last_name, $inserted_dob, $inserted_email, $inserted_account_name, $hashed_password);
$result = mysqli_stmt_execute($stmt);
echo ($result ? "SUCCESS" : "FAIL");
}
}
mysqli_stmt_close($stmt);
}
?>
Security is an entire field of study, and there's no easy way to measure whether or not something is "secure" or not secure. Security often involves not only code, but software releases, organizational changes, etc.
Here are the first things I noticed right off the bat in your posted code:
Using $_GET to pull in a password means the password was not POST'd, and therefore may exist in logs (client, server, and even some ISPs)
your mysqli_connect call is using root as a username. Don't use root in production code. Make an other user.
your root mysql user .... has no password?!?!?!
Using SELECT * FROM may return more rows than anticipated, especially because you have no LIMIT argument. Depending on database size, and how things get entered into the database, this could be abused.
WHERE account_name=? OR email=? - what if I make an account name that's someone else's email? Or enter the account name of another user in my "email" field? Your code might give me access to their profile (or lock me out of mine)
The code is not wrapped in a function, which means someone who can edit other PHP files (such as extensions) - which might be included'd after this php file - might be able to see the $inserted_password variable, which reveals the password!
I have been trying to make a reset password system where my code is as follows:
<?php ob_start();
include "includes/header.php";
include "includes/db.php";
//session_start();
include "includes/navigation.php";
if(isset($_GET['reset']) && isset($_GET['token'])) {
$token = $_GET['token'];
$stmt = mysqli_prepare($connection, "SELECT username, user_email, token FROM users WHERE token=?");
mysqli_stmt_bind_param($stmt, "s", $token);
mysqli_stmt_execute($stmt);
mysqli_stmt_bind_result($stmt, $username, $user_email, $token);
mysqli_stmt_fetch($stmt);
if(!empty($username)) {
if(isset($_POST['password']) && isset($_POST['confirmPassword']) && $_POST['password'] == $_POST['confirmPassword']) {
$password = $_POST['password'];
$hashedPassword = password_hash($password, PASSWORD_BCRYPT, array('cost'=>12));
echo $hashedPassword;
echo $user_email;
$stmt = mysqli_prepare($connection, "UPDATE users SET token= '', user_password= '{$hashedPassword}' WHERE user_email= ?");
mysqli_stmt_bind_param($stmt, "s", $user_email);
mysqli_stmt_execute($stmt);
}
} else {
header("Location: index.php");
}
} else {
header("Location: index.php");
}
?>
Now the case is that the first query is working well. The token is inserted successfully in the database to the account requesting to the reset password and it successfully got the data from the database.
The issue comes when the new password and its confirmation are inserted by the user. Although the password is hashed successfully, there is a problem inserting it into the database.
It got me the "mysqli_stmt_bind_param() expects parameter 1 to be mysqli_stmt, boolean given". For sure the problem is not in the connection as the connection already worked in the above query. I have been going around checking what is wrong with the statementquery but I cannot recognize any mistake.
Appreciate your help.
Thanks
I have amended your code and documented as best where possible where you have went wrong, and why my corrections should work.
<?php ob_start();
include "includes/header.php";
include "includes/db.php";
//session_start();
include "includes/navigation.php";
if(isset($_GET['reset']) && isset($_GET['token'])) {
$token = $_GET['token'];
//I have amended your query, you do not need to grab the token if the condition is that it is the same as something you already have, as $_GET['token'] AND $token will always be the same.
$query = "SELECT username, user_email FROM users WHERE token = ?");
$stmt = $connection->prepare($query);
$stmt->bind_param("s", $_GET['token']);
$stmt->execute();
$stmt->bind_result($username, $user_email);
$stmt->fetch();
$stmt->close(); // You did not close your query connection.
// To use a new query you must first close the previous one.
if(!empty($username)) {
if(isset($_POST['password']) && isset($_POST['confirmPassword']) && $_POST['password'] == $_POST['confirmPassword']) {
$password = $_POST['password'];
$hashedPassword = password_hash($password, PASSWORD_BCRYPT, array('cost'=>12));
echo $hashedPassword;
echo $user_email;
// Previously you tried to insert a PHP variable into a query, which is not possible
// in (and defeats the point of) prepared statements. Rather than put in $variable
// replace them with '?' . (not including quotation marks)
$query = "UPDATE users SET token = null, user_password = ? WHERE user_email = ?";
$stmt = $connection->prepare($query);
$stmt->bind_param("ss", $hashed_password, $user_email);
$stmt->execute();
$stmt->close();
}
} else {
header("Location: index.php");
}
} else {
header("Location: index.php");
}
Additionally, your output buffering at the beginning of the document can most likely be avoided if you place your db.php before your header, and also the code I've provided before your header. (This is just based on some errors I've seen in other's code.)
The error you described in your original post "mysqli_stmt_bind_param() expects parameter 1 to be mysqli_stmt" means that most likely there is an error in the query syntax, which is the main element I have amended.
Here's a well known example how to prevent sqlinjection. There are two files like login.php and profile.php But it doesn't do anything with ether entering a correct login and pass or incorrect data. Doesn't echo about any case. SQL server goes by MAMP.
Here's the code:
<?php
$con = new mysqli("localhost", "root", "root", "phpsec");
if ($con->connect_error){
echo $con->connect_error;
}
if (isset($_POST["submit"])) {
$pre_stmt = $con->prepare("SELECT * FROM userinfo WHERE email = ? AND pass = ?");
$pre_stmt->bind_param("ss",$_POST["email"],$_POST["pass"]);
$pre_stmt->execute();
$result = $pre_stmt->get_result();
if($result->num_rows > 0){
$row = $result->fetch_assoc();
header("location:profile.php?email=".$row["email"]);
} else{
echo "login fail";
}
}
?>
and profile.php:
<?phpecho "Welcome ".$_GET["email"]; ?>
What and where did I do wrong?
you can prevent your page from sql injection using prepared statement in php
try this code in while performing sql statements.
<?php
// prepare and bind
$stmt = $conn->prepare("INSERT INTO example(firstname, lastname, email) VALUES (?, ?,
?)");
$stmt->bind_param("sss", $firstname, $lastname, $email);
// set parameters and execute
$firstname = "parth";
$lastname = "gandhi";
$email = "gandhi#example.com";
$stmt->execute();
?>
here in bind_param method in first parameter s is stands for string. if you want to use integer there you can use "i" and for decimal "d".
thanks for reading my solution.
Making a login form and this is my first time using prepared statements. My issue is the num_rows keeps returning 0, despite entering the correct email and password that matches the email and password of my table. I tested that the connection works and the SQL statement works also, its just the num_rows is always 0.
PHP(without php tags and connection code):
$email = $_POST['email'];
$password = md5($_POST['password']);
if(!($stmt = $con->prepare("SELECT `email`, `password` FROM users WHERE `email` = ? AND `password` = ?")))
{
echo "Prepare failed: (" . $con->errno . ")" . $con->error;
}
else
{
echo " Query read \n";
$stmt->bind_param('ss', $email, $password);
$stmt->execute();
$stmt->store_result();
$num_of_rows = $stmt->num_rows;
$stmt->bind_result($email, $password);
echo $num_of_rows;
if($num_of_rows == 1) //To check if the row exists
{
echo "Exists";
if($stmt->fetch()) //fetching the contents of the row
{
echo "Exists";
$_SESSION['loggedin'] = true;
$_SESSION['message'] = "logged in";
$_SESSION['email'] = $email;
echo "Success!";
exit();
}
}
else
{
echo "Error";
}
}
Hopefully I've just forgotten something, but either way I am stumped.
Thanks in advance!
The value returned by num_rows may not be a valid count of rows returned until all of the rows are retrieved. That's the case for a mysqli_result. The documentation makes it appear that the num_rows function of a mysqli_stmt should be available immediately after a store_result.
Seems like the most reasonable explanation for the behavior is that the query did not return a row.
Documentation:
http://php.net/manual/en/mysqli-result.num-rows.php
http://php.net/manual/en/mysqli-stmt.num-rows.php
Why do we need to use num_rows at all? That just seems like a lot of unneeded clutter. We could just do the fetch. If it returns TRUE, we know there was at least one row returned. If it's FALSE, then zero rows were returned. No need to muck with num_rows.
If we are going to use store_result, its a good pattern to follow that with a free_result once we're done with the resultset
Also, do not use MD5 for password hash. And there's no need to return the password hash from the database, we can omit that from the SELECT list.
https://security.stackexchange.com/questions/19906/is-md5-considered-insecure
as mentioned ditch out, my_num_rows, and store_result, below works for me.
$email = $_POST['email'];
$password = $_POST['password'];
$arr = array();
$stmt = $db->prepare("SELECT email, password FROM users where email = :email
and password = :password");
$stmt->bindParam(":email", $password);
$stmt->bindParam(":password", $password);
$stmt->execute();
$arr = $stmt->fetchAll();
if(!$arr) exit('No rows');
print_r($arr);
$stmt = null;
You also want to fetch the results, like this:
$stmt->bind_param('ss', $email, $password);
$stmt->execute();
$stmt->store_result();
$stmt->fetch();
$num_of_rows = $stmt->num_rows;
$stmt->bind_result($email, $password);
echo $num_of_rows;
I have managed to write a php script that checks if a username already exists in the database and only adds a new user if it does not already exist.
This is my php script:
<?php
require "init.php";
if(isset($_POST['username']) && isset($_POST['forename']) && isset($_POST['surname']) && isset($_POST['password'])){
$username = $_POST['username'];
$forename = $_POST['forename'];
$username = $_POST['surname'];
$password = $_POST['password'];
$stmt = "SELECT username FROM users WHERE username = ?";
$result = $dbcon -> prepare($stmt);
$result->bind_param('s', $username);
$result->execute();
$result->bind_result($username);
if($result->fetch()){
echo "Can't add new user as it already exists!";
}
else{
$stmt_two = "INSERT INTO users (username, forename, surname, password)
VALUES(?, ?, ?, ?)";
$result_two = $dbcon -> prepare($stmt_two);
$result_two->bind_param('ssss', $username, $forename, $surname, $password);
$result_two->execute();
$result_two->close();
echo json_encode("Success");
}
}
?>
I believe the records are not being inserted or being inserted intermittently due to the fact that I have more than one prepared statement. If I just do the INSERT INTO statement on its' own with the SELECT FROM statement - the records are added almost instantly.
Why is this and what is wrong with my code?
Thanks
Just as I have said in the comments, don't over complicate and just check the number of rows found. No need to fetch anything. You're just checking if that user exists anyway.
$stmt = "SELECT username FROM users WHERE username = ?";
$result = $dbcon->prepare($stmt);
$result->bind_param('s', $username);
$result->execute();
$result->store_result();
if($result->num_rows() > 0) { // if it exists
} else {
// make your insertions
}
And another note:
isset can take multiple arguments:
if(isset($_POST['username'], $_POST['forename'], $_POST['surname'], $_POST['password'])) {
// and so on
}
Edit: Another flavor (using COUNT() of MySQL):
$stmt = "SELECT COUNT(username) FROM users WHERE username = ?";
$result = $dbcon->prepare($stmt);
$result->bind_param('s', $username);
$result->execute();
$result->bind_result($count);
$result->fetch();
if($count > 0) { // exists
} else {
// do something else
}