I am new to class development in PHP, however I am still quite confused by the concept of sessions.
Question 1: Can sessions be changed by client manipulations? If not can I set static sessions and use them without validation?
Question 2: How should I be managing my user accounts?
I do use SALT however, a code is generated during registration and inserted into DB where it's used for login reference. Any corrections with explanation would be much appreciated, as well anything about sessions being modified by client.
class user {
private $username = '';
private $password = '';
private $salt = '';
public $prefix = 'rhs_';
function __construct () {
$this->username = '';
$this->password = '';
$this->salt = '';
session_start();
}
public function login ($username, $password) {
$mysql_conn = Database::obtain();
$username = $mysql_conn->escape($username);
$sql = 'SELECT `password`, `salt`, `first_name`, `last_name`, `permission` FROM `accounts` WHERE `username`="'.$username.'"';
$row = $mysql_conn->query_first($sql);
if(!empty($row['password'])) {
$encrypted = md5(md5($mysql_conn->escape($password)).$row['salt']);
if ($encrypted == $row['password']) {
$_SESSION[$this->prefix.'username'] = $username;
$_SESSION[$this->prefix.'password'] = $password;
$_SESSION[$this->prefix.'name'] = $row['first_name'].' '.$row['last_name'];
$_SESSION[$this->prefix.'permission'] = $row['permission'];
header('location: ?page=cpanel');
} else {
return false;
}
} else {
return false;
}
}
A session is a file on your server where variables can be written and saved. Each session file corresponds to one active visitor to your site. PHP automatically deletes files that haven't been read from or written to for ~24 minutes.
Sessions are linked to users by a cookie. When a user browses to your page where you use sessions, PHP checks to see if a specially named cookie was sent with the request, containing their session identifier.
If the cookie exists, that identifier tells PHP which session file to open and read to populate $_SESSION.
If no cookie exists, a new identifier is generated and sent as a cookie to the user, and a new empty session file is created.
Since the sessions are files on your server, your users cannot modify them.
I am new to class development in PHP
Even if you are experienced programmer making an unsafe authentication system is easy as pie. You should be using OpenID(or systems like that like for example facebook connect) instead. They have security-experts as employees. I created a little library you can use for this. You can see a demo at http://westerveld.name/php-openid//
Can sessions be changed by client
manipulations? If not can I set static
sessions and use them without
validation?
It can not be changed by clients, but a users session could be stolen by hackers. You need to prevent session-fixation => session_regenerate_id
How should I be managing my user
accounts?
You probably should not do this, because the change you make a mistake is BIG. But below are some quick tips:
You should use phpass to store your password in the database. Because I did notice you are not using key stretching. You should perform a benchmark, and if you can generate hashes quickly, then you are definitely not securing your passwords safely. Bcrypt is a very good for hashing(used by phpass. You should use that library), because it is Moore's law proof.
Protect your users against CSRF.
You should read the OWASP top 10.
I also have created a little authentication library just for the fun of it. And I think it is pretty safe although for example logout.php is still vulnerable to CSRF although this is not really a big problem(and the fix is very easy).
Related
i'm trying to a build a secure sessionID.
Usually i store the UserID as the $_SESSION['session'];, now im not too sure if it is really secure to have the userID as the sessionID. so what i have done is tested this theory although now i cannot access the users information and after i kill the session the sessions are still active and available?
CODE to check login if true or false:
if(password_verify($userPass, $user['userPasswd']))
{
session_start();
$UID = str_replace($user['text'].$user['text1'], '', $user['uniqID']);
$_SESSION['SESSION'] = sha1(md5(sha1($UID)));
return true;
} else {
return false;
}
Logout Script: EDIT fixed using $_SESSION = array();
public function userLogout()
{
session_destroy();
unset($_SESSION['SESSION']);
return true;
}
Script to access users table information (username, email etc):
$userID = $_SESSION['SESSION'];
$stmt = $userClass->runQuery("SELECT * FROM users WHERE uniqID=:userID");
$stmt->execute(array(":userID"=>$userID));
$user = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($_SESSION['SESSION']); //Prints out session even if not logged in
print $user['Username']; //Prints out nothing
i'm not sure if i missed a step or if hashing a session is even necessary, maybe i am doing something incorrectly. Essentially i am trying to secure the userID via a hash instead of having it displaying the users actual ID. Using the hash i would like to match and gain access to the users column.
ADDITIONAL NOTE: if i change the session to get the actual userID eg: 1
$userID = 1; //i did set the $_SESSION var to the userID to check if logout works
$stmt = $userClass->runQuery("SELECT * FROM users WHERE userID=:userID");
$stmt->execute(array(":userID"=>$userID));
$user = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($_SESSION['SESSION']); //prints out 1 //still does not destroy session after userLogout() is initiated
print $user['Username']; //Prints username which is correct
been boggled by this for hours, maybe a different set of eyes and experience might help x_x.
(this is a comment, but its a bit long)
As ADyson says, this is very confused coding. Neither your code nor your narrative explain what you are trying to achieve here. What is the threat model? What is your definition of "secure"? $_SESSION['SESSION'] is not the session id.
If you store a static map between the (effectively random, but not random enough) identifier stored in the session and the username, then all you've done is limit the performance and scalability of the system - I cannot see how it adds any value for security.
There is a usage model where you might want a warrant-proof user database, where you would hash usernames - but this is not it.
There are models for protecting the session data from other subscribers on a shared host (with poor partitioning). This is not that either.
There are models for ensuring that session data is protected in backups....and, nope, this isn't very good for that either.
sha1(md5(sha1($UID)));
This is silly.
Take some time to understand how the default session handler actually works before you start trying to improve it by throwing code at it (hint: there are things in there which are not suitable for every application, but if you want to improve them, use a custom handler - not wrapping the default handler in additional code). But before you write any code, you need to get your head around exactly what you are trying to achieve.
You need to follow such way:
session_start();
// Unset all of the session variables.
$_SESSION = array();
// Finally, destroy the session.
session_destroy();
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 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
This is a complement of PHP Sessions across sub domains
I tried what is indicated on that question, and I see that the issue wasn't given.
So I need to have sessions across sub-domains (www.example.com to forum.example.com)
What I did on www.example.com is
session_name("a_name");
session_set_cookie_params(0, '/', '.example.com');
session_start();
echo session_id();
$_SESSION['test'] = 123;
On forum.example.com
session_name("a_name");
session_set_cookie_params(0, '/', '.example.com');
session_start();
echo session_id();
print_r($_SESSION);
The session_id are exactly the same, but the $_SESSION doesn't output anything.
How to make forum.example.com output 123 ?
I tried session.cookie_domain = .example.com but doesn't change anything
When I go on forum.example.com it destroys the www.example.com sessions, and it does the same on the other way, like if it detects that it comes from another sub-domain and erases everything for security.
The 2 sub-domains are on the same Debian server
Another thing that I noticed is that without session_name and session_set_cookie_params it still has exactly the same session_id, when I set session.cookie_domain
Thank You
Ok, I've thought about this for a while and I think I've got it.
First things first: since you are getting the same session id from both servers, we can rule out any cookie-related issues. Clearly, you are successfully creating a cookie named a_name (though I'd recommend only alphanumeric characters for that cookie name) on www.example.com, and successfully reading that a_name cookie on forum.example.com. But, like you said, you aren't getting any data from forum.example.com. The session.cookie_lifetime = 0 is not an issue: that just means that the session cookie remains until the browser is closed.
We should delve into PHP's session handling a bit further. The session id you are reading out with session_id() refers to a file on your server. Typically, that file is present in /tmp/sess_$session_id. The contents of that file are your $_SESSION array, serialized. (Keep in mind that the data is not serialized the same way that serialize() in PHP does... but that's not important right now.).
I think this is a file permission-related issue:
/tmp/sess_$session_id file is set with www.example.com's user and group.
forum.example.com attempts to open /tmp/sess_$session_id, but doesn't have the proper permissions.
As a result, you get an empty result when trying to print_r($_SESSION);
Solution:
Check your server's configuration file to make sure that www.example.com and forum.example.com are running as THE SAME USER AND GROUP. That is critical! For Apache, find your *.conf file:
User youruser
Group yourgroup
For nginx, find nginx.conf:
user youruser yourgroup;
If changing the server config files is not an option, then you should make sure that the users running the two sites are in the same group.
You can verify that this is the problem by first loading www.example.com and then sudo ls -ltc sess_* in your server's shell, via SSH (find the sess_ ending in your $session_id). Next, load forum.example.com and then sudo ls -ltc sess_* again, to see the user and/or group change.
For this answer I've made a few assumptions:
The user has to enter there credentials at least once on each domain (any other way would be a serious security issue)
You have access to either a database or file space outside the web root.
sub-domain, domain or any other name will be referenced as "site"
The aim is to have a common file (physical file or serialized in database) accessible from each site/domain.
For my example I will be using a database, since it's the idea I'm putting across, not database/file access techniques, I will have unnecessary lines removed, IE: How to connect to the database.
If this concept is what you were after, or if anyone else want me to fill in the blanks for completeness, just leave a comment. On with the code.
I would take a completely different approach.
From what I gather from your question, and the related post you linked to, you are trying to share a session using a common session name.
Each site has it's own session id.
Each site has it's own authentication cookie ( $_COOKIE['userid'] or $_COOKIE['userhash'] ).
Individual sessions are created, and a common cookie is stored on each site.
Using a custom session handler each site reads the same data. class MySessionHandler implements SessionHandlerInterface
My after thought was an even simpler approach, a class that acts like a session handler, reading / writing to a common file. Since php's session handler doesn't save the data until the script has ended.
Original idea - Won't go into details, it's just for reference.
class MySessionHandler implements SessionHandlerInterface {
private $savePath;
public function read($id) {
$id = some_user_authentication_function();
$hash = $_COOKIE['_h'];
$result = mysql_query("SELECT sess_data FROM login_table WHERE user_id = {$id} AND hash = {$hash}");
return $result['sess_data'];
}
public function write($id, $data) {
$id = some_user_authentication_function();
$hash = $_COOKIE['_h'];
$result = mysql_query("UPDATE login_table SET sess_data = {$data} WHERE user_id = {$id} AND hash = {$hash}");
return ($result === false) ? false : true;
}
}
$handler = new MySessionHandler();
session_set_save_handler($handler, true);
session_start();
class customSessionHandler
{
private $hash;
private $id;
private $sess_db;
public function __construct($db) {
$this->hash = $_COOKIE['hash'];
$this->id = some_user_authentication_function($this->hash);
$this->sess_db = $db;
}
public function get($key) {
$query =
"SELECT value ".
"FROM ".$this->sess_db.
"WHERE user_id = {$id} ".
" AND hash = {$hash} ".
" AND key = {$key}";
$result = mysql_query($query);
return $result['key'];
}
public function set($key, $val) {
$query =
"REPLACE INTO ".$this->sess_db.
"SET {$key} = {$val} ".
"WHERE user_id = {$id} ".
" AND hash = {$hash}";
return (mysql_query($query) === false) ? false : true;
}
}
$handler = new customSessionHandler('sess_data');
session_start();
As stated at the beginning, any code that isn't essential to explaining the concept has been removed.
Things that might not be obvious to everyone:
- $key and $val need to be sanitized before sending to the database. (prevent injection attacks)
- The hash gets sent to your login functions, so the hash can be used to clear the session data when needed, can also be used in the authentication of the user.
- mysql prepared statements would be ideal here, so you can prepare the two queries in the constructor, then you just reuse the statement on every call. Then put the connection close code in the destructor.
After thought
There would be much greater security if each site had it's own hash.
Then if you detect a security anomaly, you can just block or re-request the credentials from the one site, without compromising the hash for the network of sites.
To implement this would be as easy as setting up another table containing:
- user_id
- site_name (example.com)
- hash
- timeout
- re-authenticate
and modifying the session_data table, so instead of accessing the $key => $val pair by hash, you access it by user_id.
Thanks for reading, hopefully it will be of use to someone.
Which one is the better way to handle login in PHP?
#1 PHP.net
$email = $_POST['email'];
$password = $_POST['password'];
if($user->connection($email,$password)){ // user logging validation
session_start(); //start the session
$_SESSION['user_logged'] = true; // user logged in
header('location : control_panel.php'); // go to control panel
}
else { // go back to logging page
header('location : logging.php?' . $user->error_string);
}
#2 Me after Paul Dixon's improvements and Sebasgo's improvements
if (isset($_REQUEST['email'])) {
$result = pg_prepare($dbconn, "query22", "SELECT passhash_md5 FROM users
WHERE email=$1;");
$passhash_md5 = pg_execute($dbconn, "query22", array($_REQUEST['email']));
session_start();
$_SESSION['logged_in'] = false;
if ($passhash_md5 == $_REQUEST['passhash_md5']) {
$_SESSION['logged_in'] = true;
}
header('Location: index.php');
The code #2 has $_REQUEST commands because I am still trying to get it work.
You shouldn't try to manage the session ids yourself. A simple scheme like the one you propose (incrementing the session id by one for every new session) contains a serious security issue: A user with freshly generated session id can trivially guess other valid session ids by trying ids slightly smaller than its own. Thus it is very easy two acquire someone else's session.
If you let PHP manage the generation of new session ids for you, PHP uses a pseudo random generator to create the new ids, which will be hard to guess for a potential attacker. This prevents the above outlined attack scenario effectively.
Additionally, you will virtually never want to access $_SESSION before calling session_start() because before the session array will be always empty. Therefore your test of empty($_SESSION['SID']) will always raise false.
Bottom line: I strongly recommend you to stick to the simple way of managing login like PHP.net does it.
You force all new sessions to have the same ID, which means everyone will be sharing the same session! Probably not what you intended?
You can omit the call to session_id() and just let PHP take care of creating a unique id for the session.
Also, you should really call session_start before using the $_SESSION array to ensure it is populated with any current session variables.