Since I switched to using hashed passwords in my demo website, I needed to provide a way reset forgotten passwords.
I've got the pages and php setup to send an email with a url+key that the user will need in order to setup new password. The email contains the url to click on and the link brings the user to the reset password page.
The part I'm having a bit of trouble with is pulling the key information from the url so I can use it to compare to the key generated from the reset password submission. The database correctly inserts the key when the forgot password form is submitted.
I'm assuming I need to use parse_url to extract this information from the link but I'm not quite sure how to pull only the key information from the link.
I think PHP_URL_QUERY gets me to the correct part of the URL. From here I'm not quite sure what to do next since PHP_URL_QUERY gives me all of this:
key='.$2y$10$q53hrjswTfXnkxg8QeJysezbBi91t4yJcbV9bH3addOSiotr6kE1
when what I really want is this to do the comparison:
'.$2y$10$q53hrjswTfXnkxg8QeJysezbBi91t4yJcbV9bH3addOSiotr6kE1
Here's the code I've got right now....
<?php
require_once '../php/connect.php';
function URL() {
$purl = 'http';
if ($_SERVER["SERVER_PORT"] == "80") {
$purl .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
}
return $purl;
}
$key = parse_url($purl, PHP_URL_QUERY)
$qry = mysqli_query($link, "SELECT id FROM planner WHERE rkey = '$key'");
$array = mysqli_fetch_array($qry);
}
}
I think I just need a way to strip the key= part from the rest, but I'm not sure that is quite right.
Any assistance is greatly appreciated.
Got this working with the following....this is just a start. Thanks again everyone.
<?php
require_once '../php/connect.php';
$sub = htmlspecialchars($_GET["key"]);
$ser = "'.";
$rkey = str_replace($ser,'',$sub);
$qry = mysqli_query($link, "SELECT id FROM planner WHERE rkey = '$rkey'");
$row = mysqli_num_rows($qry);
echo $row;
?>
Related
I have writen the following script. Everything works in my application, except the validation keeps returning to login. But I have read a lot about my issue, and everything seems right, but of course there should be something wrong otherwise it would work properly.
In my case a user logs in, a token is stored in the database and in a cookie.
For the creation of the token I use:
bin2hex(openssl_random_pseudo_bytes(16));
What I did next is setup a page that first checks if the cookie token and token in the database match. To be sure I first echo them both and both give the same token. I did it like this:
include 'mydatabase.php';
$cookie_name = "My_cookiename";
$result = mysql_query("SELECT * FROM users WHERE token='{$_COOKIE[$cookie_name]}'");
while($row = mysql_fetch_array($result)) {
echo $row['token'];
echo $_COOKIE[$cookie_name];
}
Ok so I am sure at this point the cookie token and database token match.
Now I want to compare them with an if/else. And here I am going wrong, because I can't get it to work. What I have now is this:
$result = mysql_query("SELECT * FROM users WHERE token='{$_COOKIE[$cookie_name]}'");
while($row = mysql_fetch_array($result)) {
if ($row['token'] != $_COOKIE[$cookie_name]) {
header('Location:myloginpage.php'); exit(); } else { // MY PAGE CONTENT IF MATCH }
I think there is something wrong with the line:
if ($row['token'] != $_COOKIE[$cookie_name])
Any help would be great, because I am really stuck at this point.
As the comments on your question have said you are checking things needlessly. The mysql query itself does the token checking for you
include 'mydatabase.php';
$cookie_name = "My_cookiename";
$result = mysql_query("SELECT * FROM users WHERE token='{$_COOKIE[$cookie_name]}'");
if (mysql_num_rows($results) != 1) {
header('Location:myloginpage.php');
exit();
}
// Content for your page goes here, no need for an else because of exit
I think I just solved it :D
I was having the same issue, I took the & sign out of my random token generator. when I surrounded the cookie string with htmlentities() I noticed the & signs were replaced with &, because strings usually read & as code. once I removed & from the tokens, it worked. Hope this helps.
Something weird is happening with the following code. Instead of completely redirecting. It loads the page of the redirect into the login page and mixes things up.
Q1: How do i make a complete redirect.
- session start is the first line
- There's nothing being output before header.
- As for spaces, I'm not sure what will count as a space in the below script.
Q2: How do i preg_replace a string to only allow both lower cases and uppercases and 0 - 9 numbers and again how do i preg replace emaail to allow the '#' charecter and alphanumerics.
Q3: What's the best way to check if the user trying to login matches exactly the registered user?
Q4: What danger can a hacker do with my session variables?
PHP CODE
<?php
session_start();
require_once 'db_conx.php';
$email = preg_replace ('#[^A-Z, 0-9 ]#i', '', $_POST['email']);
$pwd = preg_replace ('#[^A-Z, 0-9 ]#i', '', $_POST['pwd']);
if ($uname == '' || $pwd == ''){
echo '<span style="color:#F00">Please fill in all login details.</span>';
} else {
$Result = mysql_query("SELECT * FROM users WHERE uemail = '$uname' && pwd = '$pwd'")
or die (mysql_error());
while($row = mysql_fetch_array($Result)){
$_SESSION['Sname'] = $row['firstname'];
$_SESSION['Slname'] = $row['lastname'];
$_SESSION['SUid'] = $row['uid'];
$_SESSION['Semail'] = $row['uemail'];
$_SESSION['Suid'] = $row['uid'];
$_SESSION['Szip'] = $row['zip'];
}
if (mysql_num_rows($Result) > 0){
header ('Location: ../user.php');
} else {
echo '<span style="color:#F00">Your account details do not match, please check your details and try again or try to recover your account if you forgot your password</span>';
}
}
?>
Thanks.
Q1
instead of using header( 'Location...)
you can use
echo '<meta http-equiv="refresh" content="0; URL= http://something.com">';
EDIT
i believe you can also use the following. the die should allow for the redirect, but in my experience it doesn't always get along with jquery.
header('Location: http://something.com');
die();
This is especially useful if you are using event.preventDefault(); in jquery on the same page, which will almost always cause header location redirects to be ignored. this method is also appropriate when you are using get requests to include a php page in your index file, causing a url like http://somesite.com/index.php?page=home
EDIT the above information was wrong it wasn't working for me because php had already sent the headers. i'm an idiot.
instead of this meta refresh, you could do this which should produce the desired result.
echo '<script type="text/javascript">window.location = "yoururlhere"</script>';die;
Q2
$step1 = preg_replace('#[^A-Z, 0-9 ]#i', $_POST["variable"]);
$step2 = strtolower($step1);
echo $step2;
Q3
This is a tough question to answer, but basically you want to hash there password, then check if it matches the password in the db. heres a brief pseudocode.
$username = $db->real_escape_string(strip_tags($_POST["username"]));
$password = hash('sha512', $salt.$_POST["Password"});
$db->query("SELECT * FROM `usertable` WHERE `Username`='$username' AND `Password`='$password' AND Username IS NOT NULL AND Username != '' LIMIT 1");
$result = $db->get();
if(!$result){
//the query returned a null result, so the username or password was incorrect.
}else{
//set user session and log them in.
}
Q4
I'm no expert, but it all depends on the architecture of your application and how you are setting sessions and cookies.in my opinion look into using formkeys and preventing xss, rfi, sql injection and lfi, then worry about session variables. the experience gained tackling the aforementioned problems will give you confidence and a broader understanding when attempting to secure your user sessions.
further information can be obtained from php.net/manual/en/session.security.php and stackoverflow.com/questions/328/php-session-security
thanks to the suggestions of DanFromGermany who improved on this answer.
Here is the first question and I need your help.
I transfer form data from first page using header location method in php to second page.
On the second page I accept the data using get.
Now here the url of 2nd page, after the data is sent (i.e. form is submitted)
http://mydomain.com/site1/form1_conf.php?id=123
When user is on second page, the data on second page is being displayed according the id number from the mysql database.
Now the problem is that when the user is on second page and he changes the number (for ex. 123 to say 78) the data of id=78, from the database is displayed, which is no good.
How can I stop that?
Please Note: I can't use post, nor can I use sessions.
EDITE:
php code on first page, to transfer to second page:
// after all validations are okay
$insert = //insert into database
$result = mysql_query($insert);
if($result)
{
echo("<br>Input data is succeed");
$lastInsertedId = mysql_insert_id();
header('Location:form1_conf.php?id='.$lastInsertedId); //THIS IS THE IMPORTANT LINE
}
else
{
$message = "The data cannot be inserted.";
$message .= "<br />" . mysql_error();
}
Your problem is not with the URLs: to a power user changing cookies or POST-variables is as trivial as editing GET-variables for a regular user. You'll need some way to 'sign' the requests as being valid.
Easiest to do this is with a "pre-shared key", which you use with one-way hashes to validate requests.
Redirector:
$newURL = '/newpage?id='.$id.'&hash='.sha1('mypresharedkey'.$id);
header('HTTP/1.1 303 See other');
header('Location: '.$newURL);
die;
The other page:
$idToShow = $_GET['id'];
$hash = sha1('mypresharedkey'.$id);
if($hash != $_GET['hash'])
die("Tssss, don't play with the address bar!");
else
RenderThePage();
This ensures end users can only access pages they've been allowed to by the submit.
For your specific code:
...all prior code
$lastInsertedId = mysql_insert_id();
$timestamp = time();
header('Location:form1_conf.php?'.http_build_query([
'id' => $lastInsertedId,
'time' => $timestamp,
'hash' => sha1('some-generated-key'.$timestamp.$lastInsertedId)
]);
In the other page, including a timebomb if you want (otherwise just comment it out):
$id = $_GET['id'];
$time = $_GET['time'];
if($_GET['hash'] != sha1('some-generated-key'.$time.$id))
die('URL was tampered with');
if(time() - $time > 300)
die('URL was only valid for 5 minutes');
You need to track the user and the id that they have in your database to make sure that they haven't changed the number. So when you get the information via the GET you make sure that it is legit.
Users can change the id or even attempt to go directly to that page via the url. So you need some sort of server-side check to verify that it is ok.
You could complicate this "cheating" a bit, if you didn't pass the ID number directly, but somehow encrypted it.
Let's say, you define a salt:
define(SALT, 'long weird salt with special characters etc.');
Here comes the first part you want:
$lastInsertedId = mysql_insert_id();
$querytag = base64_encode($lastInsertedId); // just to make it less readable
$checksum = md5($querytag . SALT); // and make a hash
header('Location:form1_conf.php?id=' . $querytag . '&checksum=' . $checksum);
At the beginning of form1_conf.php, you put this:
$encodedId = $_GET['id'];
$oldChecksum = $_GET['checksum'];
$newChecksum = md5($encodedId . SALT);
$id = base64_decode($encodedId);
if($newChecksum != $oldChecksum) {
die('You Are Cheating!');
}
... do something with the $id ...
The point is that since you add SALT to the hash, some user can't simply use md5 on a changed ID, because he's missing the SALT you used.
It'd be even better if the salt wasn't the same every time.
You should never trust the url because there is always a way to manipulate the data.
So you should do validation after retreiving the data. If the result does not fit you: for example the loggedin user with the ID = 1 requests the settings page from the userid = 3 you do not show the result.
<?php
$userID = $_GET['id'];
if($userID != $expectedResult)
{
//show errormessage, redirect or show the page with the users data
}
?>
I have a form for updating user data. It posts to this page:
<?php
//Update user table
session_start();
include 'sql_connect_R.inc.php';
$id = mysql_real_escape_string($_POST['userID']);
$password = mysql_real_escape_string($_POST['user_passwrd']);
$salt = time();
$hash = sha1($password . $salt);
mysql_query("UPDATE users SET user_passwrd = '$hash', stamp = '$salt', pending = 'yes'
WHERE userID = '$id'");
mysql_close($con);
?>
(I have edited out the things not pertinent to this question)
I believe what is happening is when the 'stamp' field is being populated with the $salt it is getting a different value than when the $hash is being calculated. Therefore, when a user signs in and is checked here:
$qry="SELECT * FROM users WHERE userlogin = '$login' AND user_passwrd = sha1(CONCAT('$password', stamp))";
$result=mysql_query($qry);
$row = mysql_fetch_assoc($result);
$num = mysql_num_rows($result);
When I echo $num it returns a value of 0.
I'm wondering if there is a way to ensure that the value of $salt remains the same when it is being used in $hash and then when it is updating the field 'stamp'.
Can anyone help me with this or point me in the right direction? Thanks in advance.
Cheers
More ideas so I've changed my comment into an answer...
It's worth noting that you're using PHP's SHA1 function when storing but mysql's when retrieving. They should be the same but that's the first place I'd look to debug this. try using mysql's sha function to store the hash or retrieve the record based on login, read the salt and hash it in PHP to compare
How are you storing the timestamp? Is it possible that it's being transformed/rounded/clipped/treated as a date string in some way? Just for a sanity check, take the string you're feeding into the sha1 function in both steps and check they're identical.
Further to your comment, can you post the schema for the relevant fields in the table?
Thank you for all comments. I want to report that I've 'solved' the problem. I had made a change in the name of the password input field late one night and neglected to change the $_POST value. What this did, of course, was not supply the $password value to the $hash. Though I'm embarrassed about this, I think it is important for me to share my oversight to exemplify how important it is to check ALL places where errors can occur. I failed to double-check everything and made incorrect assumptions about the nature of the problem. The code worked fine, it was the loose screw in front of the keyboard that caused the problems. Cheers
You're doing your queries incorrectly. You need to concatenate the variables in the string and NOT use single quotes. Use the quote to the left of your 1 key ``. This is the way that most MySQL read queries. example:
<?php
//Update user table
session_start();
include 'sql_connect_R.inc.php';
$id = mysql_real_escape_string($_POST['userID']);
$password = mysql_real_escape_string($_POST['user_passwrd']);
$salt = time();
$hash = sha1($password . $salt);
mysql_query("UPDATE `users` SET `user_passwrd` = '".$hash."', `stamp` = '".$salt."', `pending` = 'yes' WHERE `userID` = '".$id."'");
mysql_close($con);
?>
$qry="SELECT * FROM `users` WHERE `userlogin` = '".$login."' AND `user_passwrd` = '".sha1(CONCAT($password, stamp))".'";
$result=mysql_query($qry);
$row = mysql_fetch_assoc($result);
$num = mysql_num_rows($result);
This little change should help. Sometimes the db can be a little touchy. I hope this helps.
I had an inefficient piece of code for resetting passwords based on a user entering either their username or their email address. The PHP script branched depending on the identifier used. I collapsed it into one which now works if the user enters their username but not if they enter their email address. Here is the salient code:
$identifier = isset($_POST["username"])?"username":"email";
$ident = isset($_POST["username"])?trim(mysqli_real_escape_string($mysqli,(check_chars_username($_POST["username"])))):trim(mysqli_real_escape_string($mysqli, (check_chars_email($_POST["email"]))));
//create and issue the query
$sql = "SELECT * FROM aromaMaster WHERE $identifier = '$ident'";
$sql_res =mysqli_query($mysqli, $sql) or die(mysqli_error($mysqli));
if(mysqli_num_rows($sql_res) == 0) {
//wrong login info
header("Location: password_reset_form.html/error=$ident");
exit();
}
$info = mysqli_fetch_array($sql_res);
$userid = $info["id"];
$username = stripslashes($info["username"]);
$email = stripslashes($info["email"]);
I have checked and doubled checked that the email form field is called email and it is. It's got me scratching my head. Particularly interesting is the header redirect. When I enter an email address and am redirected, the variable $ident appears empty.
As you've noted in your comment, you have to check for the username variable of the $_POST array to be empty.
It's also a good idea to check if the variable is even there in the first place in addition to and before you test against it being blank.
$identifier =
(isset($_POST["username"]) && !empty($_POST["username"])) ? "username":"email";
When you're sending your form across, all of the text input fields will come through, even if they're blank. Blank is not the same as empty. That's the reason the first part of the ternary operator is always true in your initial code.