Security implications with CWebUser When $duration > 0
#1
Posted 22 September 2010 - 08:04 AM
I was not aware of this security implication.
During run-time, identity state is kept on the server-side, in session variables.
Persisting it on the client-side is a very bad idea. For one, this data can too easily get stale - suppose a subscription expires between logins? Or any other kind of account status change, for that matter.
For another, any data you keep there, is going to be exposed as plain text on the client machine.
I don't know why it was implemented this way - it seems like a really bad idea. In my own login systems in the past, all I've ever kept in a cookie is a hashed user-id and a temporary key of some sort.
Under not circumstances would I ever consider persisting e-mail addresses, physical addresses, or other sensitive information.
Is this documented? The documentation should include a warning in giant red letters and <blink>.
#2
Posted 22 September 2010 - 08:20 AM
But i agree to you that i don't fully understand why every state is saved in cookie, just to allow cookie based authentication. This is still one of the more obscure parts of the framework to me
#3
Posted 22 September 2010 - 09:37 AM
Please see my ticket regarding this issue. I think the best solution is to work with a server-side token. Means only save the token on client-side and read the actual data from a database.
#4
Posted 22 September 2010 - 08:31 PM
It was doable, but rather tricky - there is no direct way to simply make a user active, without "faking" the login process, by overriding certain methods. It seems like setting a user "active" should be a simple one-step operation provided by the framework, not something you have to hop through hoops to achieve.
Had there been a direct way to log in a user, without authentication, the "keep me logged in" function could have been implemented using simply a random key and a hashed user id, avoiding the persitence issue - that code would have looked a lot cleaner.
#5
Posted 22 September 2010 - 08:37 PM
Login, user and authentication components should probably be implemented more in terms of "units of work", more from the application's perspective, e.g. "make a user active", "generate an auto-login key", etc.
Just a thought...
#6
Posted 04 October 2010 - 12:02 AM
For non-login states, one may use CWebUser::afterLogin() to populate the session (eg read some permissions from the database and then call CWebUser::setState()).
With this solution, there's also no need to encrypt the cookie data because there's most likely no usage case were you want to store sensitive data as a login state?
Login-states I can think of:
- user settings like timezone (these settings will most-likely not change until the user does it manually)
- country-code (evaluated by the server and saved in cookie in order to save CPU time on further requests)
#7
Posted 16 October 2010 - 02:04 AM
My workaround: extend CWebUser as follows:
/**
* Populates the current user object with the information obtained from cookie.
* This method is used when automatic login ({@link allowAutoLogin}) is enabled.
* The user identity information is recovered from cookie.
* Sufficient security measures are used to prevent cookie data from being tampered.
* @see saveToCookie
*/
protected function restoreFromCookie()
{
$app=Yii::app();
$cookie=$app->getRequest()->getCookies()->itemAt($this->getStateKeyPrefix());
if($cookie && !empty($cookie->value) && ($data=$app->getSecurityManager()->validateData($cookie->value))!==false)
{
$data=unserialize($data);
if(isset($data[0],$data[1])) {
list($id,$password)=$data;
$identity = new UserIdentity;
$identity->id = $id;
$identity->password = $password;
$identity->authenticate();
if ($identity->errorCode == UserIdentity::ERROR_NONE)
$this->changeIdentity($identity->getId(),$identity->getName(),$identity->getPersistentStates());
else
throw new CHttpException(500,'Bad cookie information.');
}
}
}
/**
* Saves necessary user data into a cookie.
* This method is used when automatic login ({@link allowAutoLogin}) is enabled.
* This method saves user ID and hashed password
* These information are used to do authentication next time when user visits the application.
* @param integer number of seconds that the user can remain in logged-in status. Defaults to 0, meaning login till the user closes the browser.
* @see restoreFromCookie
*/
protected function saveToCookie($duration)
{
$app=Yii::app();
$cookie=$this->createIdentityCookie($this->getStateKeyPrefix());
$cookie->expire=time()+$duration;
$data=array(
$this->data->id,
$this->data->password,
);
$cookie->value=$app->getSecurityManager()->hashData(serialize($data));
$app->getRequest()->getCookies()->add($cookie->name,$cookie);
}Basically it only saves the user id and password to the cookie. Then on restore from cookie, it verifies the password and then calls changeIdentity
#8
Posted 09 November 2010 - 07:50 AM
jonah, on 16 October 2010 - 02:04 AM, said:
Isn't this most dangerous solution to store password in the cookie?
If the cookie will be stolen (physically or by some sniffer) then account will be compromised and hacker will have both user name and password.
Also this approach does not realy solves the problem (see below).
I thought the problem with cookie can be solved this way:
- all user states are saved to the session
- cookie contains only PHPSESSID
- when we need autologin the user we open session and check if user ID is present
- if we found user ID in the session then we automatically log user id
But I just reread some articles and manuals about PHP sessions and the problem with this approach is that session file can be destroyed before cookie is expired.
In this case we will get PHPSESSID from the cookie, but will not be able to get user state from the session.
Also this will break solution suggested by jonah - user name / password will be in the cookie, but no other saved user state will be available since session file is deleted.
PHP has two important session settings to control session lifetime:
- session.gc_maxlifetime - how long session file will exist (default 1440 seconds)
- session.save_path (by default some temp folder used by all applications)
The hidden problem here is that if we set session.gc_maxlifetime to some value, but do not change session.save_path then real session lifetime can be overriden by other application.
See details here and here.
So to keep session files as long as cookies we need to:
- set session.gc_maxlifetime to value appropriate to cookie expiration
- set session.save_path to specific folder for application
I think with such settings it should be possible to implement saving all states to the session and expose only PHPSESSID to the cookie.
But this leads to other problem: session files will exist on the server for long period of time and if we have many users then session files can take much disk space.
This way current yii's implementation is good compromise:
- if no cookie-based login is required then user data is stored in the session. This way you can notice sometimes that even without closing browser you can become logged off (because php deleted session file).
- if we enable cookie-based login then all state is saved in the cookie so application is not depends any more from session lifetime. If session is deleted then new will be created and populated from the cookie.
And regarding current solution suggested by qiang (see here and here).
Maybe this solution can be implemented as part of yii core?
#9
Posted 10 November 2010 - 09:51 PM
I give that random key to the client in the form of a cookie.
To resume a session, the current user key has to match that of the cookie.
This has the added side-effect of being able to save your login on only one machine - so that, if you left some other machine logged in, by the time you log in from your own machine, the other machine can no longer resume that session.
That may not be desirable for everyone, but that's how most of our clients prefer it.
#10
Posted 11 November 2010 - 08:25 AM
mindplay, on 22 September 2010 - 08:04 AM, said:
I want to come back to this initial topic. From my observation this is not true. Only those states set with CUserIdentity::setState() will get saved to the identity cookie! If you save a user state with Yii::app()->user->setState() it will not be part of the cookie.
It's not so easy to understand the code in CWebUser that's responsible for this. But let's try:
// in login():
$states=$identity->getPersistentStates(); // only contains states set in the identity class
...
$this->changeIdentity($id,$identity->getName(),$states);
// leads to changeIdentity():
$this->loadIdentityStates($states);
// leads to loadIdentityStates():
$this->setState(self::STATES_VAR,$names);
// so here $names will contain the list of names of persistent UserIdentity states from above
// now continue in login():
if($this->allowAutoLogin)
$this->saveToCookie($duration);
// leads to saveToCookie(), where cookie data is composed in $data, and only contains:
$this->saveIdentityStates(),
// leads to saveIdentityStates():
foreach($this->getState(self::STATES_VAR,array()) as $name=>$dummy)
$states[$name]=$this->getState($name);
// so here we read back the states saved above in loadIdentityStates() with key self::STATES_VAR
// which are the states set in identity class
Maybe someone can confirm this. I've also asked this in a ticket but couldn't get a really definitive answer to this yet.
#11
Posted 11 November 2010 - 10:27 AM
Mike, on 11 November 2010 - 08:25 AM, said:
Yes, It seems like user states are saved to the cookie during login only.
But we also have a possibility to set some states before login or during login (for example, if we override beforeLogin() method). So states set with CUserIdentity::setState() and states set with Yii::app()->user->setState() before login will be saved to the cookie.
Also this may lead to some state inconsistence - is php session is expired then only part of state will be restored from the cookie.
Update:
I was wrong.
foreach($this->getState(self::STATES_VAR,array()) as $name=>$dummy)
$states[$name]=$this->getState($name);
Here loaded only those states which previously taken from CUserIdentity and saved to separate array in the session.
So even if we use Yii::app()->user->setState() before login then such state does not saved to the cookie.
#12
Posted 11 November 2010 - 02:43 PM
#13
Posted 12 November 2010 - 03:00 AM
As i see it, we now have 3 different ways to store persistent information with a user (maybe a 4th, if you add direct access to $_SESSION):
Yii::app()->session (CHttpSession)
The wrapper for easier access to $_SESSION
Yii::app()->user->set/getState() (CWebUser):
Another interface to the user session
CUserIdentity::get/setState():
A third interface to the session with the additional feature, that data saved here will also go to the cookie if cookie based authentication is enabled.
All that's missing here are some real life examples of when to use what. There must have been some rationale behind this state stuff.
#14
Posted 12 November 2010 - 06:30 AM
Wouldn't be possible to encrypt data stored in the cookies before it is saved with a Key and Salt values configured in the main.php file?
Then, we could encrypt data (hashed-whatever), cookie is saved on the client having the encrypted data that afterwards is matched against a DB table that yii creates if necessary named (for example) yiistates. This table holds the ID of the user and the value of the encrypted data (plus other extra info). If there is a match... well you know the rest.
Just dropping ideas (sorry for my english)
www.ramirezcobos.com
www.yiianswers.com
www.2amigos.us
www.getyiistrap.com
www.github.com/tonydspaniard
www.github.com/2amigos
#15
Posted 12 November 2010 - 07:02 AM
http://www.yiiframew...tack-prevention
Also see CSecurityManager where you can configure your own keys (through core app component 'securityManager').
EDIT:
This might not encrypt the data, though. Only prefix with a hash.
This post has been edited by Mike: 12 November 2010 - 07:05 AM
#16
Posted 12 November 2010 - 07:32 AM
www.ramirezcobos.com
www.yiianswers.com
www.2amigos.us
www.getyiistrap.com
www.github.com/tonydspaniard
www.github.com/2amigos
#17
Posted 14 November 2010 - 05:25 PM
Antonio Ramirez, on 12 November 2010 - 06:30 AM, said:
The data already gets hashed - so you can't tamper with it.
But it gets stored in a legible format, so you can only store data that isn't sensitive in the first place - there isn't typically a lot of data that falls right in the middle: safe to view and store, but not safe to manipulate. Typically only "insignificant" data can be stored this way, like your user ID, or your current geographical location...
#18
Posted 20 December 2010 - 08:36 AM
I'm using 'external' server to authenticate users (implemented SSO conception, because i need SSO. For example, api - differ subdomain, 'trusted site' - differ domain, so we have differ sessions, that's why i had to implement SSO to have one session on server side). User authenticated on one site will be also authenticated on other site.
Everthing is fine with 'login', but now i puzzled with 'logout'. When i logged out at one site, i'm not automatically loggedout on all sites (if they uses Yii), cause 'restoreFromCookie' is never happen due to fact that getIsGuest in init of CWebUser is true (because it checks session). But i need check 'auth server session', not session of Yii application. So i stucked with situation when:
a) i logged out at auth server, but
and if i have 5 sites with yii, but all of them uses same auth server, i will be easily with situation: logged out at auth server, logged in at 3 sites and logged out at rest 2.
Right now i have to rework 'init' of CWebUser to call 'getIsGuest' AFTER checking server.
#19
Posted 28 March 2011 - 01:27 AM
There seem to exist some ambiguity in the opinion whether session-data gets stored in (clear text) cookies under certain circumstances or not among the "senior" developers.
I'm a newbie looking for, what seems to be, a hard-to-find answer on a very simple question:
- How to (securely) store server-side session variables in Yii?
...and how to best manage that 'session'.
#20
Posted 02 April 2011 - 03:30 AM

Help
















