I have a basic site I'm using to test my programming method and I want to get a semi-decent secure way of keeping people logged in. Here is the code for register.php.
$username = $_POST["username"]; //username stored plaintext
$passhashed = crypt($_POST["password"], $username); //hashed password salted with username
$rnum = rand(1000,9999); //assign random 4-digit number
$authkey = crypt($rnum, $passhashed); //unique authentication key per user, hashed and salted with hashed password
//insert into SQL
When they log-in, $username, $passhashed, and $authkey is stored in $_SESSION data.
At the top of every single page I have the following snippet of code:
if(isset($_SESSION["username"])
&& isset($_SESSION["password"])
&& isset($_SESSION["authkey"])) {
$verifyuser = $db->prepare("
SELECT *
FROM users
WHERE username = :user
AND password = :password
AND authkey = :authkey
");
$verifyuser->execute(array(
':user' => $_SESSION["username"],
':password' => $_SESSION["password"],
':authkey' => $_SESSION["authkey"]));
if($verifyuser->rowCount() != 1) {
unset($_SESSION["username"]);
unset($_SESSION["password"]);
unset($_SESSION["authkey"]);
}
}
Basically on any given page, it performs a check that each piece store in $_SESSION clears with SQL, and if not (if any of the checks fail, will give a rowCount of not 1), it drops the session.
I'll be the first to admit I'm not too familiar with contemporary security measures to evade session hijacking (in fact, I only have a loose command of how it is even done). That being said, how is this for a beginner programmer? What can I do different to make it more secure? Assign a second authentication key at login, temporarily store it in SQL and make the same checks (new key per login)?
The crypt function is somewhat out-of-date. You'd be better off using bcrypt, which is provided in PHP using password_hash and password_verify. Additionally, using those functions, the salt (what you call $authkey) is integrated into the string, so you don't need to store it separately.
I notice you're storing the username and password in $_SESSION. $_SESSION cannot be directly modified by the client, so you might be better off just storing the user's ID there.
As you mentioned, I too have a basic understanding of session hijacking.
However I think if these 3 were hijacked, this still may not prevent account hijacking, although does make it harder.
When reading preventing session hijacking, I saw an example as simple as - If the current IP doesn't match the session, log the user out.
<?php
if($_SESSION['ip'] != $_SERVER['REMOTE_ADDR'])
{
session_destroy();
}
?>
Typical: I can no longer find said website...
Some links that may help you:
Preventing session hijacking
Proper session hijacking prevention in PHP
Related
I currently have a PHP Login System which logins by authenticating the Organisation Code the user enters, thus the database queried will be different.
includes.php
<?php
mysql_connect("mysql.example.com", $dbconn, "MySecurePassword");
mysql_select_db($dbconn);
?>
login.php
// $org is the Organisation Code, will be set when user clicks Login
$dbconn = $org;
include "includes.php";
// Omitted the $userid & $pw variables, assume there is no error, and that MySQL Injection is prevented already
$query = "SELECT * FROM `Login` WHERE `userid`=TRIM('$userid') AND `password`=TRIM('$pw' )";
$result = mysql_query($query);
if(mysql_num_rows($result)>0){
session_start();
$_SESSION['logged_in'] = $username;
header("Location: loggedinpage.php");
}
loggedinpage.php
<?php
session_start();
// As there is no fixed database, I've omitted the DB Connection
define('DS', TRUE); // used to protect includes
define('USERNAME', $_SESSION['logged_in']);
define('SELF', $_SERVER['PHP_SELF'] );
// Checks if user is logged in
if (!USERNAME) {
header("Location: login.php");
}
?>
Security Measure Taken
Passwords are hashed using SHA-512 and not stored in plaintext.
MySQL injection is prevented using mysql_real_escape_string()
I've omitted some code for ease to read, may I know if this way of checking if user is logged in is secure? If not, how can I improve it?
Thanks in advance!
Updated question to reflect the updates in comments
Assuming that your query works as intended and only returns a row when the match is exactly correct (e.g. no weird fuzzy matching through collate rules, but pure bin comparison), the authentication part is pretty much fine.
(You have been warned about SQL injection plenty, you're on your own there.)
Your security then boils down to this:
$_SESSION['logged_in'] = $username;
and the subsequent:
define('USERNAME', $_SESSION['logged_in']);
if (!USERNAME) {
header("Location: login.php");
}
And I suppose your question is about this part.
Then the answer is: the session part is fine, the blocking is not.
That's how sessions are used, yes, and they're reasonably safe by default; a user won't be able to somehow set the $_SESSION['logged_in'] value themselves, the value can only be set by your server, and presumably you're doing so only on successful authentication. Do read up about session hijacking, this is the only real vulnerability to the whole scheme.
The real problem is:
if (!USERNAME) {
header("Location: login.php");
}
Setting a header does not terminate the current page. If you're outputting sensitive information after this line, it will be sent to the client! You need to explicitly exit after setting the header.
Having said all this, we cannot tell you whether your system is "secure" because there may be any number of facepalm backdoors you have created which we're not seeing. In general I'd start with the following:
stop using mysql, use PDO or mysqli
bind your parameters, don't mysql_real_escape_string them; there are security pitfalls there
use password_hash password hashing, not SHA; especially if you're only doing a single SHA pass
becareful SQL Injection :
If you type in password field :
''=''
The password's rule will be true, because Password = TRIM(''='') is true. You have to control the password's string :
Minimum length
No white space (thanks to Trim function)
And you don't have to store a password like this, you must make a password's hash
Good day. I'm writing a social network and I need to make a very protected auth form. I decided to post data through ajax and save a login into cookies. BUT login can be wrote in hacker's cookies easily. Then I remembered about user hash, which can be generated from PHP every time when user logins and write in DB and then compare. BUT login must be allowed in different devices at the same time. How to make a secure auth?
If you want a secure login, you have to think about https.
I would use the PHP super global $_SESSION to store your session status, user email etc.
Example using PDO in PHP:
function login($email, $password)
{
$stmt = $pdo->prepare("SELECT password FROM users WHERE email = ? LIMIT 1");
$stmt->execute(array($email));
$pw = $stmt->fetchColumn();
if(password_verify($password, $pw)) { //password is correct
$_SESSION['logged_in'] = true;
$_SESSION['email'] = $email;
return true;
} else return false;
}
You should definetely use prepared statements since you take user input that might be dangerous. And don't forget about the password_hash and password_verify functions, so the passwords are encrypted safely. Store the encrypted passwords in your database and check if the encrypted user input mathes the encrypted password. DON'T USE md5, it's not safe.
Follow this link to learn more about $_SESSION 's
Conclusion
use $_SESSIONs
password_hash and password_verify instead of md5
use prepared statements when querying your database
Hope this helps a little bit.
I primarily develop HTML/CSS web-pages, and I'm working on a webpage where the users need to have a page password protected from prying eyes. The page would just be for posting non-confidential information, such as private-member events, and scheduling. I know the basics of PHP, and would like to use that, but I'm concerned about safety. The page will have multiple users, but it only needs one password which would be used by all the users. It's also a fairly low-traffic site, so for the situation it doesn't need to be 100% secure, but I would like it to be as secure as possible without too much hassle.
So far I have a login-page that comes up when the user tries to access the member-page, with a password input field, which posts the result to a page called (example name) verifypassword.php
This file looks something like this:
$password = ("mypass");
$passresult = $_POST["password"];
$passresult = strip_tags($passresult);
$passresult = htmlspecialchars($passresult);
if ($passresult != $password) {
die("Invalid password.");
}
elseif ($passresult == &password) {
setcookie("mycookie");
header("location: member-page.php");
}
else {
die("Unknown Error")
}
Then, at the top of the member page, I have some lines of PHP code as follows:
$userloggedin = $_COOKIE["mycookie"];
if (!isset ($userloggedin)) {
die("Please log in to view this page");
}
The files and values themselves are hidden via the die function if the user isn't logged in, but the password and cookie are still being transferred across the server.
I've tried to read up on salting and hashing a password value, but unfamiliar with this kind of thing. How should I be doing this? Are there any tutorials or resources I can read? I tried looking on Google, php.net, and of course here on stackoverflow, but I couldn't find anything that dealt with passwords other than creating a database to store multiple user-generated passwords, which isn't what I need.
I'm currently using WAMPP.
The top line of your code, if you want to follow best practice, should look like this:
$hash = '$2y$10$zRg5l/v9gzD/aICnp/GUlu/rFv/0ZNvxX/A5v86zjepZmuRWWL6IG';
Notice that we're storing a hash instead of the password in plain text. These hashes are generated in the following manner:
password_hash("test", PASSWORD_DEFAULT);
Why are we doing this? Because if your database (or code, in this case) is accessed somehow, then you don't want your passwords to also be stolen. The built in password handling functions mitigate this as much as possible.
In terms of checking the password, you have to make your peace that the user will have to send the password over the internet one way or another! If this is a big concern for you, you can use SSL to mitigate this - it is best practice to always use SSL for at least authentication. This means that if someone intercepts the connection between your user and your website, they will only be able to see encrypted data. Anyway, you would check it as follows when it arrives:
// Assuming single password:
if ( password_verify( $_POST['password'], $hash ) ) {
// correct!
// the plain text in $_POST['password'] is the same as the plain text used to generate $hash
}
Okay, so, next thing. Cookies are sent between the browser and the server as a header. These can be set arbitrarily by the client. So if you rely on a cookie such as $_COOKIE['mycookie'] to authenticate users, then someone could just send a manually-crafted Cookie header to imitate the effect of being logged in. The solution to this particular problem is to use sessions. At the top of every script, you run session_start() which sets its own cookie. This cookie does not contain any information, just a randomly generated unique ID. PHP stores information and associates it to that ID (by means of a file in the temp folder) - but at no point is the client itself able to see what that information actually is - or change it.
To add information to the session you put it in the $_SESSION superglobal as follows:
$_SESSION['logged_in'] = password_verify( $_POST['password'], $hash );
password_verify will return true if the password matched or false otherwise, so you can rely on this to set the boolean properly.
So you can rewrite your code as follows for login.php:
session_start();
$hash = '$2y$10$zRg5l/v9gzD/aICnp/GUlu/rFv/0ZNvxX/A5v86zjepZmuRWWL6IG';
if ( isset($_POST['password']) ) {
// Assuming single password:
if ( password_verify( $_POST['password'], $hash ) ) {
// correct!
header('Location: /member-page.php');
}
}
// display login form
and at the top of the members page:
session_start();
if (empty($_SESSION['logged_in'])) { // checks if it's set and if it's false
die("Please log in to view this page.");
header('Location: /login.php');
}
n.b. I rewrote my answer because I realised it didn't answer many of your questions very well :)
You probably shouldn't be using Cookies to do this since they can be forged on the client side. A session variable will probably work a little better, but if you want to try and keep it simple I would probably MD5 the password with some salt and store it in the cookie, then check that against your MD5ed password + salt when the tries to access the page again.
So off the top of my head something like this:
<?
$password = ("mypass");
$salt = "makeUpASaltHere";
$passresult = $_POST["password"];
$passresult = strip_tags($passresult);
$passresult = htmlspecialchars($passresult);
if ($passresult != $password) {
die("Invalid password.");
}
elseif ($passresult == &password) {
setcookie("mycookie",md5($password.$salt));
header("location: member-page.php");
}
else {
die("Unknown Error")
}
?>
Put this in a separate file and just use require() to include it at the top of all your member pages.
<?
$password = ("mypass");
$salt = "makeUpASaltHere";
$userloggedin = $_COOKIE["mycookie"];
if ($userloggedin == md5($password.$salt)) {
die("Please log in to view this page");
}
?>
Still not as good as using session variables, but at least someone just couldn't just create "mycookie" out of no where and get in. Also it has the advantage that if you ever were to change the password it would automatically log out everyone that was already logged in.
I found the following code in a previous question on SO. In following code, if the username and password supplied by the user is correct, the user_id and username is stored in session to keep it logged. My question is, why there is need to keep user_id in the session? Isnt only one thing (for example, username) enough to store in session?
If the remember is enabled, then a cookie is set, only with username. Now my question is, Is Only username cookie enough? Can't anyone just edit or add the cookie in the browser and log in the system?
Thanks for your replies.
<?
public function login($username, $pass, $remember) {
// check username and password with db
$result = $conn->query("select * from login where
username='".$username."' and
password=sha1('".$pass."')");
if (!$result) {
throw new depException('Incorrect username and password combination. Please try again.');
}
if ($result->num_rows>0) {
$row = $result->fetch_assoc();
$_SESSION['user_id'] = $row[user_id];
$_SESSION['username'] = $username;
// start rememberMe
$cookie_name = 'db_auth';
$cookie_time = (3600 * 24 * 30);*/ // 30 days
// check to see if user checked box
if ($remember) {
setcookie ($cookie_name, 'username='.$username, time()+$cookie_time);
}
// If all goes well redirect user to their homepage.
header('Location: http://localhost/v6/home/index.php');
} else {
throw new depException('Could not log you in.');
}
}
?>
THIS CODE IS NOT SECURE! (Sorry for the caps, but its for the emphasis). The SQL statement is susceptible to SQL injection. Also storing the username in the cookie is a bad idea because anyone can forge the cookie to gain authentication.
My answer to the question if this is secure: no.
You need to sanitize your code. What happens if someone enters 'test OR 1=1 ' as username?
I do not really know where to start. This code is really unsafe.
You should sanitize with
mysql_real_escape_string() (or mysqli function, or even better: use PDO for any database connection and use prepared statements) the
username and the password and be sure
that $remember is either a boolean
or an integer.
The sha1 is something like broken,
so i'd suggest using md5 instead.
Cookies can be rewritten by the user
that could add username=admin to
the cookie and login as admin.
Your code is not secure.
Your data is open to SQL injection via the initial query, where depending on the access level of the database user, you could have anyone logging in. You need to sanitise your input.
Secondly, the access to the website via the cookie, and the username in it related to the access level and privilege they get? If so in it's current form the session can be easily hijacked.
Here's A Code I use To Make Sure Everything Is Safe .. It may not be the safest but I also use other measures to verify a safe login. But this code will protect u against SQL injections.
function secure($data) {
$data = trim(htmlentities(strip_tags($data)));
if (get_magic_quotes_gpc())
$data = stripslashes($data);
$data = mysql_real_escape_string($data);
return $data;
}
It's usage
secure($username);
for example
foreach($_POST as $key => $value) {
$get[$key] = secure($value);
}
This tells PHP for each of the POST values secure it.
You Can also use it for post by using $_GET instead of $_POST but lets face it .. it would be really stupid to have your login using GET commands
I have a classifieds website which I am creating a login system for...
In the code below, a form with "username" and "password" has been submitted to. Also a "remember_me" feature is available (Code is not tested yet):
else if($row['password']===$pass){
session_start();
$_SESSION['logged_in'] = '1';
$remember_me = isset($_POST['remember']) ? $_POST['remember'] : '0';
if($remember_me=='1'){
$text = "SECRET_TEXT_AND_NUMBERS_HERE";
$username= $row['username'];
$salt1 = sha1($row['alt_username']);
$salt2 = sha1($text);
$cookie_value = $salt1.':'.$username.':'.sha1($row['alt_username'].$salt2.$salt1);
setcookie("s_b", $cookie_value, time()+60*60*24*100, "/");
}
}
Now, is this code a good start for a login page?
Also, an important follow-up question to all this, if users want to stay logged in, do I then set a $_SESSION variable like the one in the code, and just check if that is set in the beginning of all pages on the site?
if(isset($_SESSION['logged_in'])) // Then user is logged in already
or do I check to see if the cookie created in the login page is set instead of checking the session?
logging in is about security; security is always more difficult then it seems.
There are a couple of things that you could improve in your code. first:
the security for your password is in the strength of the hasing algorithm. You choose to use sha1 (better than md5, but could be improved by using sha256 or bCrypt if you use PHP version >= 5.3)
First
The salt you use is supposed to be a random value, stored alongside the hashed result.
in other words, the value to store in your database is:
$salt = [some random string of predifend lenght]; // Let's say 16 characters in length
$storedValue = $salt . sha256($salt . $password);
you check the password:
if ($row['username'] == $_POST['username'] && substr($row['$storedValue'], 16) == sha256(substr($row['$storedValue'], 0, 16) . $_POST['password'])) {
// login ok
} else {
// login fail
}
(better yet)
Use a proven library for the password hashing stuff, take a look at: Portable PHP password hashing framework and try to use the CRYPT_BLOWFISH algorithm if at all popssible.
Second
You should only store the session key in the session cookie. all other information is stored on the server.
The session cookie is already send out by PHP's session_start() function call, so you do not have to worry about this anymore.
if you want to check the sessions lifetime, you should store this information in the session array:
$_SESSION['lastActivity'] = time()+60*60*24*100;
Third
The remember me token is a 'password equivalent' so you should only store a hash of the token in your database, just treat it as a password, only this 'password' is not typed by the user, but read from the cookie.
The whole point of a hash is that its non-reversible, so it's not really adding any value the way you've used for the remember me function. Stop pretending it does anything useful, and use a random token for the remember me (and log this in the database against the username) then, if you get a client presenting a remember me cookie without an authenticated session, you know where to look to find out who it is.
(this also allows a sensible approach to be applied where the user keeps moving to different machines - you might say keep the last 2 values - and flag when they try to remember me from a 3rd machine).
A 100 day timeout is rather long - maybe 30 days (with an automatic refresh might be more appropriate depending on the level of risk.