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!
Related
This question already has answers here:
Can a user modify a PHP session?
(3 answers)
Closed 7 years ago.
I am developing my own application which requires user login. All users and passwords (encrypted) are stored in a database. When a user tries to login, then it search in the database the username and password. If all is ok, then I store username in $_SESSION["username"], user role (admin, author etc) in $_SESSION["role"] and user website in $_SESSION["website"] (I need website to be stored because the application is like "multisite" - my applicaton is hosted on client hosting but the administration is on my server).
I read this Can a user alter the value of $_SESSION in PHP? and I don't understand. Is this method secure (of storing data and if user is logged in) in a $_SESSION?
Can the user change the session content? (for example, if user is logged in and $_SESSION["website"] is "example.com", can the user change the session $_SESSION["website"] to "example.org" to ruin another website? If yes, how to avoid or what's the secure alternative of session?).
And please tell me what is session hijacking and how can this affect my site and also, how to make session_id dinamically to change?
Thank you so much!
$_SESSION is saved in the server, so the user cannot modify it ( Except the case of session hijacking)
Session() is server side as #kd0807 noted. For more info on Session Hijacking && Fixation:
http://phpsec.org/projects/guide/4.html
http://php.net/manual/en/session.security.php
Side note...
With the amount of variables I recommend an array for session['user'].
example....
$_SESSION['user'] => Array(
'userid'=> '123',
'username'=> 'some_joe',
'role' => 'customer', // user, customer, admin, moderator etc.
'website' => 'http://www.example.com'
);
// reading variables from array
$userid = $_SESSION['user']['userid'];
$username = $_SESSION['user']['username'];
// etc. etc.
Here are 3 very simplified functions I use.
// determine if session has started
Function HasSessionStarted() {
$result = false; // default to false
// Check if session has started
IF ((session_status() == PHP_SESSION_NONE) || (session_id() == '')) {
$result = true;
}
return $result;
}
// Set a session value
Function Set_Session($name, $value) {
/* #params value: can be a string or an array */
$new_session = HasSessionStarted(); // Check Session Status
IF ((isset($name)) && (isset($value))) {
IF ($new_session) { session_start(); }
$_SESSION[$name] = $value;
IF ($new_session) { session_write_close(); }
}
}
Function Unset_Session($name) {
$new_session = HasSessionStarted(); // Check Session Status
IF (isset($_SESSION[$name])) {
IF ($new_session) { session_start(); }
unset($_SESSION[$name]);
IF ($new_session) { session_write_close(); }
}
}
There is a cookie stored in the browser usually PHPSESSID which identifies which server session the user is using. If a user were able to steal it (this usually happens through XSS vulnerabilities) the user could potentially take control of another users session.
The session data itself is stored on the server and could only be modified if a user were somehow able to upload and execute a malicious script in the server
No, Until and unless the php code itself reveals the PHP session, which can be used to session stealing, and the session could not be changed from the user end until and unless the there is some functionality given by you yourself to change the session from the front end.
`$_SESSION` variables are stored in the server and hence cannot be modified by the user.
One of the best practices is that we must keep changing the session id from our end.
Thats why we use a function called session_regenerate_id().
This function that will replace the current session ID with a new one, and keep the current session information, so the use will not be logged out.
To answer your question in comment:
Whenever you start a session, server will send back a cookie which will contain the session id, the cookie name will be PHPSESSID which is the default name. You can also choose to change it. So this id is what that keeps changing when you use the session_regenerate_id function.
REASON WHY TO USE IT:
This mainly helps in preventing session fixation attacks.In this attack a malicious user will try to fix the session ID (SID) of another user. If he gets successful,the user will get all the access of the original user and will be able to do anything that the legitimate user can do.
So if you regenerate the session id the previous old session id will be no longer valid
You can get more info about session fixation IN OWASP WEBSITE
Can the user change the session content? (for example, if user is logged in and $_SESSION["username"] is "example.com", can the user change the session $_SESSION["username"] to "example.org" to ruin another website?
No. Sessions are stored on your server thus it would be impossible to directly change the session. On the other side, it all depends on the developer and how the interprets the client requests. For example, making
$_SESSION['last_website'] = isset($_SERVER['HTTP_REFERER'])
? $_SERVER['HTTP_REFERER']
: $_SERVER['REQUEST_URI'];
would eventually override the $_SESSION['last_website'].
Yes user cannot change session Variables...
if it hacked it can changed...
you need to get user id to change the $_SESSION["website"] in your current domain and use strict validation...
Otherwise they can hack it....
I am attempting to set a cookie and then check to see if the cookie has been set.
So in one function, I have it make the cookies:
public function makeCookies(){
Cookie::queue('logged_in', $value, 15);
Cookie::queue('user_id', 2);
//return Response::make()->withCookie(Cookie::make('logged_in', $value, 15))->withCookie(Cookie::forever('user_id', 2));
}
And in the other function, I try to check to see if the cookie has been set:
public function checkCookies(){
$this->makeCookies();
if(Cookie::get('logged_in') && Cookie::get('user_id')){
return 'Logged In!';
}
}
However the only way this works is if I add 'return' before $this->makeCookies(); However, I want to be able to get to the conditional below it. Is there any way I can go about doing this? Any help is greatly appreciated.
To understand the Cookie Creation/Read process:
The user's browser sends a request for a page, along with any cookies that it currently has for the site
The site serves up the page, and any cookies you create become a header in your response.
Subsequent requests to your site will send the cookies created in #2.
What you are asking...to be able to read cookies that you create in step #2 in step #1...not possible.
Now, depending on how the Cookie class is created, you could make it so that when the Cookie::queue() is called, that it creates in-memory data that reflects what the cookie "should be" on the next request, but it doesn't truly know whether or not the user's browser will accept cookies, etc.
This is why many sites, after creating a cookie give the user a redirect to a page with something like ?checkCookie=1. This way, on the subsequent request, they can verify that your browser supports cookies...and if the cookie doesn't exist on the ?checkCookie page, they give you an error saying that their site requires cookie support. However, it does require a second round to the server to read cookies from the browser that were created.
UPDATE 2015-04-24 Per #Scopey, Laravel does support in-memory retrieval of cookies via queued(). So, you should be able to do:
public function checkCookies(){
$this->makeCookies();
$loggedIn = Cookie::get('logged_in') ?: Cookie::queued('logged_in');
$userId = Cookie::get('user_id') ?: Cookie::queued('user_id');
if( $loggedIn && $userId ){
return 'Logged In!';
}
}
SECURITY CONCERNS (NOT DIRECTLY ANSWERING THE QUESTION)
Your question was only about the cookies, so that's all I answered. However, now that I'm looking at your code, I feel I would be remiss not to point this out for anyone that happens to be reading this. This may just be a "how to" for yourself and not production code, but that code could be very dangerous if it ever went public.
Make sure you do NOT TRUST a user_id stored in a cookie to determine what user is coming in via cookies. If you rely on that, and I come to your site, I can modify my cookie to any user_id I want and get into other people's accounts.
General Safety Rules:
A cookie should contain a GUID, or similar random string to identify the session. This random string should be sufficiently long (e.g. 32 characters or greater, IMHO) that it is not easy for someone to brute-force their way to hijacking sessions.
The user_id should be stored in the $_SESSION (or laravel's wrapper for session if applicable) so that the user doesn't have any access to the user_id to be able to modify it.
In plain PHP, this something like this for the login page:
session_start();
if( isValidPassword($_POST['username'], $_POST['password']) ) {
$_SESSION['user_id'] = $user->Id;
}
else {
die('invalid login credentials');
}
The session_start() method automatically generates a cookie for the user with that long, random string (so you don't even have to worry about that part.)
On subsequent pages, you just check the session user_id to know who is logged in:
session_start();
if( empty($_SESSION['user_id']) ) {
die('You are not logged in and cannot access this page');
}
Change as needed per Laravel's documentation, which if they have their own session wrapper, I'm sure is well documented on best practices.
Excellent description by #KevinNelson about cookies but Laravel does support fetching back any cookies you have queued in the current request. Try using
Cookie::queued('logged_in');
The catch is, the cookie will only be "queued" during the request that you queued it. You will have to use get like you are for any other requests.
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 ;-)
I'm new to web programing and im trying to find a few good examples / tutorials on how to do a decent job of creating a website that requires users to log on to view any pages beyond the main log in page.
so far i've found 1 or 2 that ive tried but i keep running into the same problem. If i just enter the url of the page i want to see manually i can get in like there was nothing there.
Okay, I'll explain how the basic concept goes and a very simple implementation to get things going.
PHP (and most web applications) rely on RESTful services -- which, to our concern at the moment, means every request is not remotely bound to any other request being made - either that being by the same user or others.
So what does that mean?
This means that for every single request, you need to do your checks. You need to make sure if the user has permissions to execute that page, or less severely even see its contents.
How is this achieved?
By many ways, actually. There are lots of techniques used to enforce authorization on web applications but they would essentially both break down to one of two -- either centralized, or decentralized.
-- Centralized
This means all your actions (and controllers) are being handled through a single file. Say index.php. This file would then include or delegate its tasks to other files (that are not runnable on their own via normal requests) based on request parameters. This is a very popular approach, but not exactly straight forward for new developers. Examples of applications that use this approach would have URLS of the type: index.php?do=register, index.php?do=login, index.php?do=showtopic&topic_id=2, and so forth.
A simple implementation of this technique would go like:
<?php
// index.php
define('RUNNING_APP', true);
// 1. place your auth code here, or...
switch ($_REQUEST['do']) {
case 'register':
// 2. or here
include 'inc/register.php';
break;
case 'do_register':
// 2. and here, and before every include.. and so forth.
include 'inc/do_register.php';
break;
}
?>
<?php
// inc/register.php
defined('RUNNING_APP') or die('Cannot access this script directly'); // make sure to break direct access
?>
<form action="index.php?do=do_register">
<!-- form elements -->
</form>
and so forth.
I've documented where the usual auth code should go.
-- Decentralized
Using this approach, however, your auth code should go at the beginning of every single file. URLS of applications of this sort usually look something like: register.php, login.php, and so forth. The main problem here is that you need to perform all auth logic per file, as stated above, and that may be a hectic job if your files increase in amount. A convenient solution is to have that logic in a single file, and include that file (which would kill the request for unauth personel) before any of your logic. A simple example would be:
<?php
// index.php
include('inc/auth.php');
// index logic
?>
<?php
// register.php
include 'inc/auth.php';
// register logic
?>
<?php
// inc/auth.php
$logged_in = false;
if (!$logged_in) {
die ('You do not have permission to access this page. Please login');
}
?>
When logging in using a form, you should check the username and password in the database. The password should be scrambled (usually done using the MD5 hash algorithm), and stored in the database in the same way. You capture the variables, using something like (use some validation to check if the POST variables are valid):
$username = $_POST['username'];
$passwordHash = md5( $_POST['password'] );
The username and hashed password should be stored in your database. You can then check for a match in the database using:
$res = mysql_query("SELECT * FROM users WHERE username='".$username."' && password='".$password."'");
When a user is found, you use sessions to store the user values, which will allow you to get access to a users information across pages. NOTE: session_start() is usually placed at the top of the page, but I'll place it here for readability.
if ( mysql_num_rows($res) ) {
session_start();
session_regenerate_id(); // regenerate session_id to help prevent session hijacking
$row = mysql_fetch_assoc($res);
$_SESSION['logged_on'] = true;
$_SESSION['username'] = $row['username'];
// add more session variables about the user as needed
}
On every page you want to protect, you add the following to the top of those pages:
session_start();
if ( !isset($_SESSION['logged_on']) ) {
header("Location: login.php"); // user is not logged in, redirect to login page
exit;
}
// page content here
There's HTTP Auth:
http://php.net/manual/en/features.http-auth.php
Or you can roll your own with a login form and session tracking:
http://www.php.net/manual/en/book.session.php.
Http auth means the user gets a pop-up dialog window asking for a username and password, it's less usual than the self-rolled version.
Enjoy!
The sites you mentioned are likely bypassable because the pages past the security check don't save and then check for login status on each page. You need to check that a visitor is logged in before access to a page is granted.
I think most users would expect form input for a login. If you want the user to come back and log in with the same account later after their session expires, you'd need a database to store user information.
When storing user information in a database, you should probably not actually store their password, either. For an example:
name password ...
-----------------------------------------------
Johnny '3858f62230ac3c915f300c664312c63f'
Alice '80338e79d2ca9b9c090ebaaa2ef293c7'
.
.
.
Johnny's password is actually "foobar", but the database stores md5('foobar'). When Johnny tries to log in, he enters his username ('Johnny') and his password ('foobar'). In PHP, you hash the password he entered, and call up his password value from the database, resulting in:
if (md5('foobar') == '3858f62230ac3c915f300c664312c63f')
This conditional is true. You can confirm if he logged in correctly, but you're never storing his actual password.
Alice's password is 'foobaz'. She tries to log in, but accidentally types 'foobar', Johnny's password. this results in:
if(md5('foobar') == '80338e79d2ca9b9c090ebaaa2ef293c7')
Which is false. Again, you don't know what Alice's password is, just that she entered the wrong one.
The downside to this strategy, of course, is that you can't tell the user what their password is when they forget it -- you don't know! You can resolve this by letting a user reset their password (to some semi-random string) instead of strait telling them what their password is.
After I authenticate user login info, i create this session for them:
$_SESSION['username']= $userName;
Then, I redirect them like this:
header('Location:www.domain.com/profile/' . $_SESSION['username'];
I want my website to have a beauty URL, something like: www.domain.com/profile/userName
Thus, in all my redirect links (HTML <a> tag or PHP header() function), I will use:
"www.domain.com/album/" . $_SESSION['username'];
Are there any security loopholes?
Edit:
Do I need to create session id first using session_id()?
So, to check:
if(!isset($_SESSION['id']){
//redirect to login page
}
Normally while using Sessions we also need to be aware of -:
Session Hijacking , Session Fixation
I suggest in your code after user logged in store the username in session variable also store one more unique value such as USER AGENT in a session variable. so that every page the user visit we can check for whether the same USER AGENT and SESSION ID exist this would make it much secure. To make it much more secure do the encryption like MD% on User AGENT so that hackers cant reproduce it.
Quoted from PHP SECURITY GUIDE
<?php
session_start();
if (isset($_SESSION['HTTP_USER_AGENT']))
{
if ($_SESSION['HTTP_USER_AGENT'] != md5($_SERVER['HTTP_USER_AGENT']))
{
/* Prompt for password */
exit;
}
}
else
{
$_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);
}
?>
Refer :
PHP Security Guide on Session
Another Thread on Session security
What are you protecting? What are you doing to verify that they have authorization? Are you protecting their profile and verifying that they have authorization because they have the session key? You don't ever mention checking that they have a session variable.
You won't even need to know the session ID. That is immaterial to storing whether the user has gotten authentication, that's just the mechanism which indicates what session information they should be using.
When the user logs in, you want to store something like
$_SESSION['authed_user'] = true;
And then, on subsequent attempts to edit information you do:
if ($_SESSION['authed_user']) {
// do something authed users can do
}
And naturally, you'll really probably want some sort of levels of authorization. I recommend you consider using something like SimpleAuth...
You need authorization on the page that allows user to edit their profile. If they'll be editing on the http://www.domain.com/profile/[username] page then you need to check if their $_SESSION['username'] is equal to the profile page they are on.
Otherwise anyone would be able to type in the URL (basically guess a profile number or name) and edit it.
But yes, you should first check if they've logged in AT ALL:
if (IsSet($_SESSION['username'])) {
// Logged in
} else {
// Not logged in
}