Impact that this can cause: It is possible to steal or manipulate customer session and cookies, which might be used to impersonate a legitimate user, allowing the hacker to view or alter user records, and to perform transactions as that user.
And recommended solution to prevent session fixation attacks is to renew the session ID when a user logs in. This fix can be done at the code level or framework level, depending on where the session management functionality is implemented.
I'm trying to find a fix for this and still i'm not successful. Anyone can help how to fix this in Joomla 2.5?
I want to implement this fix at framework level. Any help will be appreciated.
I did this for Joomla 3.x version. It should be similar in 2.5.
You should modify 2 files to make this work.
libraries/cms/application/cms.php
libraries/joomla/session/session.php
in cms.php modify the function login
// Import the user plugin group.
JPluginHelper::importPlugin('user');
if ($response->status === JAuthentication::STATUS_SUCCESS)
{
$session = &JFactory::getSession();
// we fork the session to prevent session fixation issues
$session->fork();
/*
* Validate that the user should be able to login (different to being authenticated).
* This permits authentication plugins blocking the user.
*/
$authorisations = $authenticate->authorise($response, $options);
in session.php change the function fork() to include
function fork()
{
if( $this->_state !== 'active' ) {
// #TODO :: generated error here
return false;
}
// save values
$values = $_SESSION;
// keep session config
/*$trans = ini_get( 'session.use_trans_sid' );
if( $trans ) {
ini_set( 'session.use_trans_sid', 0 );
} */
$cookie = session_get_cookie_params();
// create new session id
//$id = $this->_createId( strlen( $this->getId() ) );
session_regenerate_id(true);
$id = session_id();
// first we grab the session data
$data = $this->_store->read($this->getId());
// kill session
session_destroy();
// re-register the session store after a session has been destroyed, to avoid PHP bug
$this->_store->register();
// restore config
ini_set( 'session.use_trans_sid', $trans );
session_set_cookie_params($cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure'], true);
// restart session with new id
session_id( $id );
//session_regenerate_id(true);
session_start();
$_SESSION = $values;
//now we put the session data back
$this->_store->write($id, $data);
return true;
}
Thanks a lot #ryadavalli ! It is very helpful. Using your suggested solution, I solved it for Joomla 2.5.
Only few changes; for Joomla 2.5 the code needs to be placed in
libraries/joomla/application/application.php
libraries/joomla/session/session.php
In application.php w.r.t your solution
public function login($credentials, $options = array())
{
// Get the global JAuthentication object.
jimport('joomla.user.authentication');
$authenticate = JAuthentication::getInstance();
$response = $authenticate->authenticate($credentials, $options);
// Import the user plugin group.
JPluginHelper::importPlugin('user');
if ($response->status === JAuthentication::STATUS_SUCCESS)
{
$session = &JFactory::getSession();
// we fork the session to prevent session fixation issues
$session->fork();
// validate that the user should be able to login (different to being authenticated)
// this permits authentication plugins blocking the user
$authorisations = $authenticate->authorise($response, $options);
In session.php, updated the code as following
public function fork()
{
if ($this->_state !== 'active')
{
// #TODO :: generated error here
return false;
}
// Save values
$values = $_SESSION;
// Keep session config
/*$trans = ini_get('session.use_trans_sid');
if ($trans)
{
ini_set('session.use_trans_sid', 0);
} */
$cookie = session_get_cookie_params();
// Create new session id
//$id = $this->_createId();
session_regenerate_id(true);
$id = session_id();
// first we grab the session data
$data = $this->_store->read();
// Kill session
session_destroy();
// Re-register the session store after a session has been destroyed, to avoid PHP bug
$this->_store->register();
// Restore config
ini_set('session.use_trans_sid', $trans);
session_set_cookie_params($cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure']);
// Restart session with new id
session_id($id);
session_start();
$_SESSION = $values;
//now we put the session data back
$this->_store->write($id, $data);
return true;
}
Related
I created a relatively small php/mysql based website and hosted it on a server running Red Hat 6.4, Apache/2.2.15. In order to access the site, the users are required to authenticate.
Now comes the weird problem. When I start the server, the site initially runs fine, but after about 12-24 hours, the php sessions stop working. When users try to login, their credentials a successfully retrieved from the database and the login script sets up the session variables, but when they are redirected to the home page - the session variables are lost so the home page thinks they aren't logged in and they are redirected back to the login page. The site is not heavily used, there are about 50 registered users and there are under 100 logins daily. Also, the site requires an invitation code to register, so the probability for bots to be on the site is very small.
Below are the PHP functions used to start a session, to log the users in and to check if they are authenticated (the code was inspired from http://www.wikihow.com/Create-a-Secure-Login-Script-in-PHP-and-MySQL):
function SecSessionStart() {
$session_name = 'sec_session_id'; // Set a custom session name
$secure = false;//https is not enforced
// This stops JavaScript being able to access the session id.
$httponly = true;
// Forces sessions to only use cookies.
if (ini_set('session.use_only_cookies', 1) === FALSE) {
header("Location: ../error.php?err=Could not initiate a safe session (ini_set)");
exit();
}
// Gets current cookies params.
$cookieParams = session_get_cookie_params();
session_set_cookie_params($cookieParams["lifetime"],
$cookieParams["path"],
$cookieParams["domain"],
$secure,
$httponly);
// Sets the session name to the one set above.
session_name($session_name);
session_start(); // Start the PHP session
session_regenerate_id(); // regenerated the session, delete the old one.
}
function Login($username, $password) {
$username=trim($username);
if (CheckUserCredentials($username, $password))
{
$userId=GetUserIdByUsername($username);
$hashedPass=hash("sha512",$password);
$user_browser = $_SERVER['HTTP_USER_AGENT'];
$_SESSION['user_id'] = $userId;
// XSS protection as we might print this value
$username = strtolower($username);
$_SESSION['username'] = $username;
$_SESSION['login_string'] = hash('sha512', $hashedPass.$user_browser);
// Login successful.
session_write_close();
return true;
}
else {
session_write_close();
return false;
}
}
function LoginCheck() {
// Check if all session variables are set
if (isset($_SESSION['user_id'], $_SESSION['username'], $_SESSION['login_string']))
{
$user_id = $_SESSION['user_id'];
$login_string = $_SESSION['login_string'];
$username = $_SESSION['username'];
// Get the user-agent string of the user.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
$hash=GetPasswordHashByUserId($user_id);
if ($hash==null)
{
session_destroy();
session_write_close();
return false;
}
$login_check = hash('sha512', $hash.$user_browser);
if ($login_check == $login_string) {
// Logged In
session_write_close();
return true;
} else {
// Not logged in
session_destroy();
session_write_close();
return false;
}
}
session_destroy();
session_write_close();
return false;
}
In order to test my assumption that the problem was related to the sessions, I made the following php page and I've set up monitoring on it once every 5 minutes on an uptime checker:
<?php
require_once("include/authentication.php");
SecSessionStart();
$_SESSION['sessionCheck'] = "The cookie was successfully saved and retrieved";
session_write_close();
$_SESSION['sessionCheck']="";
SecSessionStart();
if (isset($_SESSION['sessionCheck']))
{
echo($_SESSION['sessionCheck']);
}
session_destroy();
session_write_close();
?>
Just as I suspected, the page returned the right answer for about 20 hours then I got an alert that the page was not displaying the success string. After I got the alert the login part on the site didn't work, either. I've tried to restart the httpd process to bring back to life the sessions, but this did not help. Only after a complete server restart things got back to normal.
I have tried searching on SO and other sites for my problem and I found some questions that were somehow related, but they did not entirely fit my situation. The main difference between my problem and other similar ones its that the site works perfectly fine in the beginning, but after some time the sessions fail all of the time. Other posters had problems where they lost sessions from time to time or the sessions did not work at all.
Any hint on the possible source of this problem would be kindly appreciated. If you need any further information from me that could help, please let me know.
Thank you
I want to change the session id once the user has logged in to a custom random session id with my own prefix.
However, when I do the below the session data is lost;
Zend_Session:setId('my-new-id');
Zend_Session:start();
But if I do the following the session data will still be available;
Zend_Session:regenerateId();
Zend_Session:start();
Anyidea how can I fix this issue?
see in library/Zend/Session.php:316:
if ( !self::$_sessionStarted ) {
self::$_regenerateIdState = -1;
} else {
if (!self::$_unitTestEnabled) {
session_regenerate_id(true);
}
self::$_regenerateIdState = 1;
}
Best practice is to call this after session is started. If called prior to session starting, session id will be regenerated at start time.
also see: Global Session Management with ZF1:
$defaultNamespace = new Zend_Session_Namespace();
if (!isset($defaultNamespace->initialized)) {
Zend_Session::regenerateId();
$defaultNamespace->initialized = true;
}
added:
Then try this:
$commit = Zend_Session::namespaceGet('Default');
Zend_Session::writeClose(false);
// ^ session_commit(); // see Advanced Usage: http://framework.zend.com/manual/1.12/en/zend.session.advanced_usage.html#zend.session.advanced_usage.starting_a_session
.. change id with session_id("my-new-id");
// start new session ...
$namespace = new Zend_Session_Namespace('Default');
foreach($commit as $idx => $data){
$namespace->$idx = $data;
}
However! after calling Zend_Session::writeClose() we cannot restart session with Zend_Session::start() version of 22744dd
see this issue:
https://github.com/zendframework/zf1/issues/159
fixed version: https://github.com/itscaro/zf1/blob/bbebe445d8551582cba6f5e9c9c1a8396fb2b322/library/Zend/Session.php#L436
anyway, I recommend watching the documentation of the start session - Starting a Session
and/or use Zend_Session::regenerateId(); instead of Zend_Session::setId()
I'm writing a simple website which allows a user to login, fill out a form which is submitted to a database and then log out. In order to manage the session, I used the session manager which is described by TreeHouse on the following page: http://blog.teamtreehouse.com/how-to-create-bulletproof-sessions
In order to protect against hijacking, the client's IP address and user agent are stored in the session variable and compared to the server's values for these properties on each page. If they don't match, then it is assumed that the session has been hijacked and it is reset.
The implementation seems to work on my local machine without any issues, but when I uploaded it to the server, each page refresh causes the preventHijacking() function to return false (meaning it believes the session has been hijacked). However, if I echo any text within that function, the problem mysteriously disappears and the whole thing works as I expect it to (except for the bit of echoed text which is now displayed above my form :P).
I haven't a clue why this would be the case and I can't figure out how to fix it. The session manager code is below. At the start of each page, I use this to start the session and then each page simply uses or sets whatever variables it requires. If anyone could suggest why the function always returns false unless it echoes text and perhaps suggest what modification I need to make so that it will behave in the expected manner, I'd really appreciate it.
<?php
class SessionManager {
protected static $timeout = 600; // Time before automatic logout for the session
static function sessionStart($name, $limit=0, $path='/', $domain=null, $secure=null) {
// Set the cookie name before we start
session_name($name.'_Session');
// Set the domain to default to the current domain
$domain = isset($domain)? $domain : $_SERVER['SERVER_NAME'];
// Set the default secure value to whether the site is being accessed with SSL
$https = isset($secure)? $secure : isset($_SERVER['HTTPS']);
// Set the cookie settings and start the session
session_set_cookie_params($limit, $path, $domain, $secure, True);
session_start();
// Make sure the session hasn't expired and destroy it if it has
if(self::validateSession()) {
// Check to see if the session is new or a hijacking attempt
if(!self::preventHijacking()) {
// Reset session data and regenerate ID
$_SESSION=array();
$_SESSION['IPaddress'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['userAgent'] = $_SERVER['HTTP_USER_AGENT'];
self::regenerateSession();
// Give a 5% chance of the session ID changing on any request
} else if (rand(1, 100) <= 5) {
self::regenerateSession();
}
$_SESSION['LAST_ACTIVITY'] = time();
} else {
$_SESSION = array();
session_destroy();
session_start();
}
}
static function preventHijacking() {
if(!isset($_SESSION['IPaddress']) || !isset($_SESSION['userAgent'])) {
return false;
}
if($_SESSION['IPaddress'] != $_SERVER['REMOTE_ADDR']) {
return false;
}
if($_SESSION['userAgent'] != $_SERVER['HTTP_USER_AGENT']) {
return false;
}
return true;
}
static function regenerateSession() {
// If this session is obsolete, it means that there already is a new id
if(isset($_SESSION['OBSOLETE']) && $_SESSION['OBSOLETE'] === True) {
return;
}
// Set current session to expire in 10 seconds
$_SESSION['OBSOLETE'] = True;
$_SESSION['EXPIRES'] = time() + 10;
// Create new session without destroying the old one
session_regenerate_id(false);
// Grab current session ID and close both sessions to allow other scripts to use them
$newSession = session_id();
session_write_close();
// Set session ID to the new one and start it back up again
session_id($newSession);
session_start();
// Now we unset the obsolete and expiration values for the session we want to keep
unset($_SESSION['OBSOLETE']);
unset($_SESSION['EXPIRES']);
}
static protected function validateSession() {
// Check if something went wrong
if(isset($_SESSION['OBSOLETE']) && !isset($_SESSION['EXPIRES'])) {
return false;
}
// Test if this is an old session which has expired
if(isset($_SESSION['EXPIRES']) && $_SESSION['EXPIRES'] < time()) {
return false;
}
// Check if the user's login has timed out
if(isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY']) > self::$timeout) {
return false;
}
return true;
}
}
?>
I could be way out here (it's been a while) but that sounds like the buffer containing the headers isn't being flushed for some reason. Providing body would force them to be flushed, so maybe not providing the body doesn't flush?
Try putting ob_end_flush(); in there before you return. That may fix it.
I've just begun using sessions and am having some headaches, I had this working last night, now opening it today...no longer works.
In my login processor I have the following if everything is OK. This script works fine, I have echoed the session variables to ensure that the array works, and it does.
$username - > post from login script
$encrypt_password -> created from password check further up the script
{
$session_name = 'LOGIN'; // Set a custom session name
$secure = false; // Set to true if using https.
$httponly = true; // This stops javascript being able to access the session id.
$cookie_lifetime = '3600';
$cookie_path = '/';
$cookie_domain = '127.0.0.1';
session_set_cookie_params($cookie_lifetime, $cookie_path, $cookie_domain, $secure, $httponly);
session_name($session_name); // Sets the session name to the one set above.
$group = $row['group_type'];
$user_browser = $_SERVER['HTTP_USER_AGENT']; /*grabs browser info*/
$user_id = preg_replace("/[^a-zA-Z0-9_\-]+/", "", $username); /*XSS Protection*/
$group_id = preg_replace("/[^a-zA-Z0-9_\-]+/", "", $group); /*XSS Protection*/
session_start();
$_SESSION['user']=$user_id;
$_SESSION['group_name']=$group_id;
$_SESSION['login_string'] = hash('sha512', $user_browser.$encrypt_password);
session_write_close();
header("location:".$group_id."_index.php");
}
I have created an include file which gathers the info from the session, included on every protected page, this is where it fell apart. I have created custom error codes for each if statement and have found that the if statement here fails. Echoing the session variables or evening printing the session array returns nothing.
$session_name = 'LOGIN'; // Set a custom session name
$secure = false; // Set to true if using https.
$httponly = true; // This stops javascript being able to access the session id.
$cookie_lifetime = '3600';
$cookie_path = '/';
$cookie_domain = '127.0.0.1';
session_set_cookie_params($cookie_lifetime, $cookie_path, $cookie_domain, $secure, $httponly);
session_name($session_name); // Sets the session name to the one set above.
session_start(); // Start the php session
session_regenerate_id(false); // regenerated the session, delete the old one.
if(isset($_SESSION['user'],$_SESSION['group_name'], $_SESSION['login_string']))
I was changing around the way the user groups worked before this broke, however none of the variables make it through. I am learning from his tut by the way: create a secure login script in php and mysql
Also do I need to call the session parameters every time a user visits a protected page?
Thanks in advance for any pointers.
Try putting session_start(); on TOP of everything, most importantly before you're calling a session. You're calling session_name($session_name); before it even started.
it=session
your regenerating the session on every page, which causes the previous session to destroy data.
remove session_regenerate_id(false);
I've been working on the security of my site (PHP) and there's a ton of information to ingest. I've tried to implement security I've researched on OWASP, but one thing I'm a little nervous about, among other things, is how to handle SESSIONS when the user logs out.
Currently all I'm using is:
session_destroy();
But, I've read that I should change the XRSF token and start another SESSION so it forces the user to resubmit login credentials in-turn explicitly ending the users SESSION.
Is session_destroy() enough?
EDIT
I've downloaded michael-the-messenger, which I believe was created by Michael Brooks (Rook) which should be VERY secure, and I saw some code that I might want to use. Is this something that could safely replace the session_destroy() I'm using?
CODE
if($_SESSION['user']->isAuth())
{
/* if they have clicked log out */
/* this will kill the session */
if($_POST['LogMeOut'] == 'true')
{
//When the user logs out the xsrf token changes.
$tmp_xsrf = $_SESSION['user']->getXsrfToken();
$_SESSION['user']->logout();
$loginMessage = str_replace($tmp_xsrf, $_SESSION['user']->getXsrfToken(), $loginMessage);
print layout('Authorization Required', $loginMessage);
}
else
{
header("Location: inbox.php");
//user is allowed access.
}
}
else
{
// code goes on ....
LOGOUT
public function logout()
{
$_SESSION['user'] = new auth();
}
Obviously $_SESSION['user'] = new auth(); reinstantiates the object which sets a private variable $auth to false.
but one thing I'm a little nervous about, among other things, is how
to handle SESSIONS when the user logs out.
According to manual:
In order to kill the session altogether, like to log the user out, the
session id must also be unset. If a cookie is used to propagate the
session id (default behavior), then the session cookie must be
deleted. setcookie() may be used for that.
So, in order to safely destroy a session, we'd also erase it on the client-machine.
session_destroy() along with setcookie(session_name(), null, time() - 86400) will do that.
Apart from that,
What you are doing wrong and why:
Session storage merely uses data serialization internally. By storing
an object in the $_SESSION superglobal you just do
serialize/unserialize that object on demand without even knowing it.
1) By storing an object in $_SESSION you do introduce global state. $_SESSION is a superglobal array, thus can be accessed from anywhere.
2) Even by storing an object that keeps an information about logged user, you do waste system memory. The length of object representation is always greater than a length of the strings.
But why on earth should you even care about wrapping session functionality? Well,
It makes a code easy to read, maintain and test
It adheres Single-Responsibility Principle
It avoids global state (if properly used), you'll access session not as $_SESSION['foo'], but $session->read['foo']
You can easily change its behaivor (say, if you decide to use DB as session storage) without even affecting another parts of your application.
Code reuse-ability. You can use this class for another applications (or parts of it)
If you wrap all session-related functionality into a signle class, then it will turn into attractive:
$session = new SessionStorage();
$session->write( array('foo' => 'bar') );
if ( $session->isValid() === TRUE ) {
echo $session->read('foo'); // bar
} else {
// Session hijack. Handle here
}
// To totally destroy a session:
$session->destroy();
// if some part of your application requires a session, then just inject an instance of `SessionStorage`
// like this:
$user = new Profile($session);
// Take this implementation as example:
final class SessionStorage
{
public function __construct()
{
// Don't start again if session is started:
if ( session_id() != '' ) {
session_start();
}
// Keep initial values
$_SESSION['HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'];
$_SESSION['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
}
/**
* You can prevent majority of hijacks using this method
*
* #return boolean TRUE if session is valid
*/
public function isValid()
{
return $_SESSION['HTTP_USER_AGENT'] === $_SERVER['HTTP_USER_AGENT'] && $_SESSION['REMOTE_ADDR'] === $_SERVER['REMOTE_ADDR'] ;
}
public function __destruct()
{
session_write_close();
}
/**
* Fixed session_destroy()
*
* #return boolean
*/
public function destroy()
{
// Erase the session name on client side
setcookie(session_name(), null, time() - 86400);
// Erase on the server
return session_destroy();
}
public function write(array $data)
{
foreach($data as $key => $value) {
$_SESSION[$key] = $value;
}
}
public function exists()
{
foreach(func_get_args() as $arg){
if ( ! array_key_exists($arg, $_SESSION) ){
return false;
}
}
return true;
}
public function read($key)
{
if ( $this->exists($key) ){
return $_SESSION[$key];
} else {
throw new RuntimeException('Cannot access non-existing var ' .$key);
}
}
}
Maybe session_unset() is what you are looking for.