I just wonder what are the best practices to secure a restricted page with PHP.
It has to be really safe.
I guess using the $_SESSION vars is the normal / most secure way.
Here is what I do to log in :
I use a separate sql table with the username and an encrypted password + SALT
SALT is randomly generated and stored in the db (is this a good practice?) at the registration
I use filter_var and PDO on $_POST login and password to avoid sql injection
I check the number of failed logins and set the account inactive after 3 failed attempts
I do not use any cookie
I use SSL
Here is my code if the login is successful, it looks so simple that I'm wondering if it is secure enough :
<?php
// login + password successful
session_start();
$_SESSION['islogged'] = true;
?>
// on each restricted page
<?php
session_start();
if(!$_SESSION['islogged']){
header('Location: unauthorized.php');
exit;
}
// here start my restricted content
?>
I wonder i.e. if using the header function this way is safe enough and 100% reliable?
For the php session security, I already found this interesting content :
What are the risks of PHP sessions?
Is there something else I must be careful of?
Thank you a lot for your suggestions !
Related
New to security and wondering how secure this type of login is? I am not protecting any bank/financial data by any means but trying to secure somewhat sensitive data that shouldn't be exposed to the general public. I only require a password - no official logins are done.
This is in a file called access.php which houses a password input field.
<?php
session_start();
if (!isset($_SESSION['loggedIn'])) {
$_SESSION['loggedIn'] = false;
}
// sha256() password
$password = '13d249f2cb4127b40cfa757866850278793f814ded3c587fe5889e889a7a9f6c';
if (isset($_POST['password'])) {
if (hash('sha256',$_POST['password']) == $password) {
$_SESSION['loggedIn'] = true;
} else {
die ('That is the incorrect password - Please leave now');
}
}
if (!$_SESSION['loggedIn']):
?>
Then my index.php requires access.php at page load. Should access live outside the public directory? Am I missing anything else I should be considering?
New to security and wondering how secure this type of login is?
SHA-256: You're using the entirely wrong tool for the job. Use password_hash() and password_verify():
How to safely store a password
Cryptography terms explained for non-experts
Additionally, SHA-256 is vulnerable to length-extension attacks.
Using == to compare hashes has two vulnerabilities:
Timing attacks
Magic hash comparison (the more pressing concern)
So, to answer your question: Not very. The problem your code is trying to solve is well-known among security experts, and they've gone out of their way to make it simple for others to solve it. That's why password_hash()and password_verify() exist. Use them.
That said, welcome to software security. If you need some additional resources to aid your self-education, check out this application security reading list on Github.
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
This is my login page.
include 'config.php';
if (($_POST['username']!=$user) || ($_POST['password']!=$pass))
{
// login deny.
// process usaully terminate at the end of block
}else{
// login grant.
}
$user and $pass value declared in config.php.
Is there any chance that I can login without knowing password and username ?
Like cast $_POST[] to int.
The answer to your question is yes, a user can get through your login without knowing the password. The real question is how hard can you make it for him to do that, and/or how long will it take him to brute force/guess it.
A possible way of preventing brute forcing is to track the number of requests and employing rate-limiting techniques that many API servers use to stop excessive attempts to login. This could use sessions, or better yet an internal log by the client's IP address of the number of attempted requests, and after say 10 failed requests, you lock that IP address out for 24 hours.
A quick-fix solution
Are you going to post your credentials for every page view? You should be only be posting data once, validating it and saving the result to a session variable that you'll then check to determine if the user is logged in.
Here's a short example of a basic, badly secured login system (you should be hashing your passwords!!):
config.php
session_start(); // let's get this puppy rolling
$user = 'example';
$pass = 'anUnsafePasswordHere'; // hash me please!!!
yourpage.php
include 'config.php';
if(!isset($_SESSION['is_logged_in']) || $_SESSION['is_logged_in'] !== true) {
// strict comparison for your login check failed, redirect to login page
header("Location: login.php");
exit;
}
// display regular content here, they are logged in!
Your login page will obviously have a form and will post somewhere, so let's replicate that quickly:
login.php
include 'config.php';
if(isset($_POST['login'])) {
if(
!empty($_POST['user']) // check that the vars exist before trying to
&& !empty($_POST['pass']) // use them in your comparison
&& $_POST['user'] === $user // strict check here, content and var type
&& $_POST['pass'] === $pass
) {
// login succeeded! clear failed validation errors, set success and redirect
unset($_SESSION['login_errors']);
$_SESSION['is_logged_in'] = true;
header("Location: yourpage.php");
exit;
}
// login failed, let's save an error message to the session and redirect back
// to the login form
$_SESSION['login_errors'] = 'Login failed, please try again!';
header("Location: login.php");
exit;
}
?>
<form action="login.php" method="post">
<?php if(!empty($_SESSION['login_errors'])) echo '<p>' . $_SESSION['login_errors'] . '</p>'; ?>
<input type="text" name="user" placeholder="Username">
<input type="password" name="pass" placeholder="Password">
<button type="submit" name="login">Login!</button>
</form>
If you use a basic structure like this for your login system you shouldn't go too far wrong. The idea is that whenever the session variable representing your "logged in" state isn't present, you're forcing a redirect to the login page.
The login page posts to itself, where the credentials are checked and if it's all OK that logged in state session variable is set, and you're send back to the main page - where it does pass the validation check at the top of the page this time.
Obviously to log out, you will just call unset($_SESSION['is_logged_in']); or session_destroy() then redirect back to the login page.
Security notes
Now for security reasons you should definitely implement some hashing.
If you only want one account to access your script and you think using a database purely for this is a little excessive, you can store your credentials in the source code, however you should always store your passwords as hashed representations of what they are so that anyone who happens to look over your shoulder or somehow obtain access to your source code won't immediately see your password.
A couple of old classic (but now phased out due to more secure alternatives) methods of quick, built into PHP hashing techniques are using md5() or sha1() to give a hashed representation of your password. Hashing is a sort of one-way encryption, and it takes a machine a long time to brute-force what a hash represents, so any security is better than none. I suggest you use bcrypt, but a baseline level of security for your application (better than nothing!) would be to use md5() to turn your secretpassword into a hash like 2034f6e32958647fdff75d265b455ebf, then store that in your source code.
All you need to change in your login file is the line that compares the password you've posted with the one in the source code:
if(md5($_POST['pass']) === $user)
To take hashing to a modern best-practice level, you should be looking at using something like bcrypt to store your passwords securely.
I need to know how secure is my user authentication code that I am using in my php applications.
This is my login check function
// Is Login
//*********************************************************************************
public function isLogin()
{
$validation = new Validation();
if(!$validation->isEmpty($_SESSION["AdminId"]) && !$validation->isEmpty($_SESSION["AdminUsername"]) && !$validation->isEmpty($_SESSION["AdminName"]))
{
return true;
}
else
{
return false;
}
}
I have a authenticate file which i call from top of every user account's page which is as under
if (!$admin->isLogin())
{
header("Location: index.php?type=warning&msg=" .urlencode(ADMIN_INVALID_LOGIN));
exit();
}
The session values for example Adminusername is the actual username of the admin, adminname is the alphabetical name of the admin and adminid is the record id from mysql table such as $_SESSION["Adminusername"] = administrator though i am storing this value after encypting it.
I need to know is this a secure method to just store the values and check for them or I need to have some kind of advance functionality to make it more secure such as salt or time check etc.
I would appreciate your suggestions and feedbacks. If possible, your authenticate code / class.
Thanks in advance.
Amardeep Singh
use session regenerate id to get a new ID in every request, so u can prevent session hijacking .. read this manual : http://php.net/manual/en/function.session-regenerate-id.php
I am storing this value after encypting it
I don't understand... Why do you crypt your AdministratorName?
As you surely know, the user cannot manipulate his session as he wants, because the session is on the serverSide and your code decide what to write into session-data.
I think, salting or timechecking do not raise your security-level.
Because HTTP is stateless, each session is identified by a id, which ist mostly saved in a cookie on the client side. Each of your request to this server contains this SID, because it's the only way your server could identify a visitor.
If you use HTTP-Transport, your data (end also your SID) is sent through the internet without encryption. So a hacker could read your SessionID and take over your Session (which contains logged in User-Data). To prevent this, you can force HTTPS connection for logged in users.
If you have the possibility to switch all your pages to https-only, do it. If you must switch between http and https (for example https only if user is loggedin) it becomes really difficult to guarante security!
Scenario:
After a user has logged in, a session variable is set confirming their login.
At the top of every page, login session variable is confirmed valid
If it's not, they're booted out.
No persistent cookies are used, only session
Question:
Is this a strong enough security measure by itself, or should I
Set two session variables to validate eachother and/or
Implement database/hash validation
...?
========
(Incidentally, while I was researching this question, this wiki is a fantastic read.)
It is enough to store just user login (or user id) in the session.
To prevent session fixation/hijacking everything you need is just to implement simple algorythm (pseudocode):
if (!isset($_SESSION['hash']) {
$_SESSION['hash'] = md5(!empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'no ua');
} else if ($_SESSION['hash'] != md5(!empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'no ua')) {
session_regenerate_id();
$_SESSION = array();
$_SESSION['hash'] = md5(!empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'no ua');
}
You could move the hash calculation into some function to prevent of duplication, i've just shown a sketch of possible protection.
This is how I implemented this kind of protection in my kohana session class:
abstract class Session extends Kohana_Session
{
public function read($id = null)
{
parent::read($id);
$hash = $this->calculateHash();
$sessionHash = $this->get('session_fixation');
if (!$sessionHash) {
$this->set('session_fixation', $hash);
} elseif ($sessionHash != $hash) {
$this->regenerate();
$_SESSION = array();
$this->set('session_fixation', $hash);
}
}
private function calculateHash()
{
$ip = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';
$ua = !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'no ua';
$charset = !empty($_SERVER['HTTP_ACCEPT_CHARSET']) ? $_SERVER['HTTP_ACCEPT_CHARSET'] : 'no charset';
$ip = substr($ip, 0, strrpos($ip, '.') - 1);
return md5($ua . $ip . $charset);
}
}
Don't try to write your own session scheme, PHP will do it better.
yes you can add more information to your $_SESSION to help prevent session hijacking
for example I generate a fingerprint by combining a secret phrase or random data with the user agent and the session_id() and hash it all. To hijack a session the user would need to figure out a valid session_id, and the hash of the fingerprint. it will look like this. This is a good read
$_SESSION['fingerprint'] = md5('somethingSecret' . $_SERVER['HTTP_USER_AGENT']. session_id());
then you would validate the session like
$check_print = md5('somethingSecret' . $_SERVER['HTTP_USER_AGENT']. session_id());
if($check_print != $_SESSION['fingerprint'] || $_SESSION['authenticated']){
//invalid session
}
As of 15 November, the two answers I have received do not address my question, which was
"Is this [a single session variable] a strong enough security measure by itself?"
This question says yes, but there seems to be some dissension. Here is a summary of the various results:
1) A single session variable is not enough security since a session can be hijacked fairly easily.
2) Since this can occur, no session is truly safe, but it can be made safer with the addition of a fingerprint. This ensures a unique, repeat-able check each time a session needs validation. #zerkms recommends a hash of User-Agent and a few others (refer to his code).
3) Salting the fingerprint is mostly useless since it obscures the data but is replicated on every client machine, therefore losing its unique-ness.
4) A database solution is useless since it is a client-side problem.
Not the definitive answer I was looking for, but I suppose it will have to do, for lack of anything better.
Reading that has helped/confused me further:
Session hijacking and PHP
Is HTTPS the only defense against Session Hijacking in an open network?
There is nothing you can do, except use HTTPS.
It doesn't matter how many cookies you add or what data you hash; it can all be sniffed and sent back to the server.
If you're going to force a user to use a single UA throughout the life of their request, that can help: you don't need any special hashing business, because you're hashing it into $_SESSION which neither the user nor the hijacker can access directly, so why bother hashing it? Might as well just store $_SESSION["reportedUA"] = $_SERVER["HTTP_USER_AGENT"] on log-in and then check reportedUA on each request.
That, too, is trivial to hijack, once you realise it's happening, as you need only sniff the reported UA when you sniff the session cookie, and start using that.
What next? IP address? Session hijacking might be happening from behind a NAT, in which case you're screwed. Your users might be using dial-up, in which case they're screwed.
This problem has no solution: there is no way. There couldn't be a way. If a hacker can see your session cookies, then they can mess you up, because there's no additional information or challenge related to something only the user knows (i.e. password) that's sent with each request.
The only way to make the session secure is to secure the entire session.
Is this a strong enough security measure by itself,
Set two session variables to validate eachother and/or
Implement database/hash validation
No, and the reason is this: Anything that your valid user can send to your server for authentication (Session ID, cookies, some hashed string, anything!) can be sniffed by others if it's not encrypted. Even if the server processes the data with md5 hashing, salt, double-session-variable checks, id or whatever, and stores that information, it is easily reproduced by the server when it receives the spoofed data again from some other source.
As many people have suggested, SSL is the only way to prevent this type of evesdropping.
It has occurred to me that, were the server to generate a new session id for each request, and allow the browser to reply with it only once, there could theoretically be only one hijacker request or post before the server and the authorized browser knew about it. Still unacceptable, though, 'cause one is enough to do serious damage.
Hey what about this:
Create a single-use GUID and random salt and encrypt it with a shared password using PHP - this is sent as the session id or a cookie.
The client receives the cookie, decrypts it with the shared password using javascript (there are many enc/dec utilities available)
Set the current cookie or session id to the GUID.
That would ensure that nobody could hijack the session unless they knew the password, which is never sent over the network.
SSL seems much easier, and is more secure still.
EDIT: Ok, it's been done - nevermind ;-)