PHP Sessions + Useragent with salt

2019-02-05 09:02发布

问题:

It keeps running in my mind the last couple of days, but I read some articles about how to make your PHP sessions more secure. Almost all of these articles say that you need to save the useragent in the session WITH an additional salt. Something like this:

$fingerprint = md5('SECRET-SALT'.$_SERVER['HTTP_USER_AGENT']);

The salt would make it harder for an attacker to hijack or whatever the session. But WHY add a salt every time you would check it like this:

md5('SECRET-SALT'.$_SERVER['HTTP_USER_AGENT']) == $_SESSION [ 'fingerprint' ]

So WHY would a salt make it more secure, since the attacker still only needs the useragent (which is relativly a small set of different useragents) and the sessionid?

Probably something small I'm overlooking, but can't figure it out, drives me crazy haha

Thanks!

回答1:

The reason that it's suggested to add a salt is simple. Generally, when you're creating this "fingerprint" - if you're using only one item of data, which has a limited dataset, then it makes it easier for an outside hacker to generate this, and hijack the session.

In your example above, yes, if the attacker has both the "fingerprint" and the User agent, then they will be able to hijack the session.

Adding a salt only makes it harder for an attacker to generate the fingerprint, it's a case of "if they have all but one piece of information, then the last piece of information is rendered useless)

I'd suggest that you add some more things in, for example, within vBulletin (a project I used to work on) the session ID hash (which is basically the same as the fingerprint) is generated with the following code.

define('SESSION_IDHASH', md5($_SERVER['HTTP_USER_AGENT'] . $this->fetch_substr_ip($registry->alt_ip))); // this should *never* change during a session

Also, a session hash is generated using

md5(uniqid(microtime(), true));

These are both checked when trying to identify the session

So, to hijack the session, the person would need to know the following

  • The time (exactly) on the server when the session was created
  • The users Browser agent string
  • The user's IP address

They would also have to spoof the IP address (or at least the first 2/3 octets) to be able to do this.

If they're actually at a point where they've managed to get the above information, then they're probably likely to be able to attack in other ways than just session hijacking.

vBulletin don't actually use a "salt" per se, but, in your above example, the salt is just adding a limited amount of entropy, it's always best to find as much entropy as possible.

For example, in something I'm currently writing in python, I generate a hash for usage with XSRF protection. The following is what I use.

    self.key = sha1(
        self.user.username +
        self.user.password +
        settings.SECRET_KEY +
        strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
    ).hexdigest()

Which takes the user's username and password, the current time, and a preset salt to generate this. This would be hard for an attacker to generate due to the salt, and the time (though, do note that this is only made secure by the fact that it changes once it's used, with time, it wouldn't take much for someone to crack this for a particular user if it wasnt changing)



回答2:

If you are on your own server, encrypting session variables is pointless, because they don't get out of the server. See Linead answer to What do I need to store in the php session when user logged in? for more info. If you are in a shared server, you may need to encrypt every session variables, besides the session ID, because they are stored on temp files readable by the same web server all your neighbours are using.

Anyway, if you are really worried about security, you are better with your own (virtual or not) server, so danger will only come from outside your server.

Some examples of risk to your sessions:

  • Your server sends the session ID in the URL, and your user follows a link to badguys.com They will get in server variables the referer (complete URL, including your session ID), the browser and the IP address of your user. If you are not checking IPs, or your user uses an open proxy, they only have to install same browser version, paste the URL, and they're done.
  • User go to a public PC, logins, and leave without closing his session (hey, he's human after all). Next guy in the row opens the browser, check history and finds an open session. Yuck.

So, some measures you can take, by my usual preference:

  1. Don't send the session ID in the URL; enable session.use_only_cookies in PHP. Cons: User needs to enable cookies.
    • On dangerous actions (change password, make an order...), ask user for password again. You can do it periodically too. Cons: Annoying.
    • Timeout sessions fast. Cons: In most sites, this will force users to login often, annoying them.
    • Use SSL (only way to avoid 'man in the middle' attacks). Cons: Slow. Stupid browser messages. Need SSL on server.
    • Check the IP. Cons: Inneffective for visitors using a public proxy. Annoying for dynamic IPs.
    • Check the User Agent (browser). Cons: pretty much useless, UA is easy to get and trivial to imitate.

(I take for granted you have yet PHP configured for maximum security).

Some more extreme measures:

  • Maintain a permanent connection between server and browser, e.g. using a Java applet. No connection, no session. Cons: User needs Java, ActiveX or whatever you use. Session closes with browser (this can be good). Doesn't work on very slow connections. Higher load on server. You need to open ports, have a special server for the applet.
  • The same, but using asynchronous requests (e.g. AJAX) to refresh very frequently the session, and a very short timeout. Or refreshing a hidden IFRAME. Cons: User needs JavaScript. Doesn't work on very slow connections. Higher load on server.
  • The same, but reloading the whole page. Cons: User needs JavaScript. An automatic reload while you are reading a page is very annoying.

In some corner cases, you can forget about sessions and use Apache authentication instead. Simplest solution, but a lot of limitations.



回答3:

If I understand correctly, you want to prevent session hijacking by a remote attacker that guesses session IDs?

If this is not the case, then you are seriously out of your depth - an attacker that can snoop the traffic can also mimic the user agent, and an attacker that gains access to your session storage has you by the balls anyway.

If you store the user agent string to "lock" the session to the current user agent, then there is really no point in hashing it - string comparison on the full user agent string is faster (then hashing and then comparing) and not significantly more expensive in terms of storage.

I don't believe storing the user agent is providing enough differentiation - something better would be to generate a larger ID (with more bits) at session start time (maybe sha1 the current time stamp + user name + user agent + something), then store that in a cookie as well as in the session and match it up on each additional request. This doesn't change the attack vector much (you still need to guess some number), but its easy to significantly increase the number of bits that must be guess for a successful attack there by massively increasing the difficulty of the attack.



回答4:

I see one purpose in salting your fingerprint. If a bad guy gets hold of your session-db (god knows why) but not of your code he couldnt "guess" your fingerprinting method by trying the common user-agents against it.



回答5:

I do that as well to partially protect from session impersonation attacks. You need to include the IP address as well.

Keep in mind that when the client's browser auto updates the user agent changes and you'll think that his session has been hijacked ;)



回答6:

As the fingerprint is stored on the server side, you don’t need to use a salted hash. A “normal” hash is enough to reduce the data.



回答7:

Bear in mind that if you do that you're forcing people to login again if they upgrade their browser. This can be OK but just make sure it's your intent.

Using the user's remote address is not without problems either. Many people use the same computer from different locations. Mobile devices, laptops being used at home and work, laptops being used at Wifi hotspots and so on. IMHO it's a bad idea to use IP address in such a way that a new IP address requires a login unless you're dealing with highly sensitive information such as online banking. Is that the case?

What are you concerned about? External attack? Or in a shared host situation that someone can read your session information?

If it's the latter, the solution is simple: just don't store anything sensitive in the session. Anything sensitive should be stored in the database.

In terms of creating a secret salt, you need to use something that isn't guessable. I would go for something like a random string that's created when the user is created. If necessary recreate it each time the session is invalidated.

As for what it would make it more secure, you said it yourself: there are limited user agent strings (less than a hundred will probably cover 99.99% of users). A salt simply increases the number of possibilities. That being said, if you use the same salt for all sessions then it's only a matter of time before it's found with brute force.



回答8:

Okay, for example I'm using the following fictional code:

<?php

// The sessionid cookie is now a certain hash
if ( array_key_exists ( $_COOKIE [ 'sessionid' ] ) )
{
    // Get the session from database
    $db_sessid = $pdo -> getStuff ( 'session_database', $_COOKIE [ 'sessionid' ] );

    if ( $db_sessid !== null && $db_sessid [ 'fingerprint' ] == sha1 ( 'SOMESALT' . $_SERVER [ 'HTTP_USER_AGENT' ] ) )
    {
        set_cookie ( ... ); // New sessionid and write also to DB

        // User is now logged in, execute some user stuff
    }
    else
    {
        // Session doesn't exist, or the fingerprint does not match
    }
}

Now the attacker only still needs the sessionid, which is in the cookie (sent along HTTP headers) and the useragent. So what's still the point of the additional salt?

Checking for IP's is also in my opinion not such a good option, some providers or proxy's change them every single request.

Thanks so far (-: