148

I'm trying to understand more about PHP Session Fixation and hijacking and how to prevent these problems. I've been reading the following two articles on Chris Shiflett's website:

However, I'm not sure I'm understanding things correctly.

To help prevent session fixation, is it enough to call session_regenerate_id(true); after successfully logging someone in? I think I understand that correctly.

He also talks about using tokens passed along in urls via $_GET to prevent session hijacking. How would this be done exactly? I'm guessing when someone logs in you generate their token and store it in a session variable, then on each page you'd compare that session variable with the value of the $_GET variable?

Would this token need to be changed only once per session or on each page load?

Also is there a good way of preventing hijacking without having to pass a value along in the URLs? This would be a lot easier.

tripleee
  • 175,061
  • 34
  • 275
  • 318
me2
  • 1,481
  • 3
  • 10
  • 3

5 Answers5

228

Ok, there are two separate but related problems, and each is handled differently.

Session Fixation

This is where an attacker explicitly sets the session identifier of a session for a user. Typically in PHP it's done by giving them a url like http://www.example.com/index...?session_name=sessionid. Once the attacker gives the url to the client, the attack is the same as a session hijacking attack.

There are a few ways to prevent session fixation (do all of them):

  • Set session.use_trans_sid = 0 in your php.ini file. This will tell PHP not to include the identifier in the URL, and not to read the URL for identifiers.

  • Set session.use_only_cookies = 1 in your php.ini file. This will tell PHP to never use URLs with session identifiers.

  • Regenerate the session ID anytime the session's status changes. That means any of the following:

    • User authentication
    • Storing sensitive info in the session
    • Changing anything about the session
    • etc...

Session Hijacking

This is where an attacker gets a hold of a session identifier and is able to send requests as if they were that user. That means that since the attacker has the identifier, they are all but indistinguishable from the valid user with respect to the server.

You cannot directly prevent session hijacking. You can however put steps in to make it very difficult and harder to use.

  • Use a strong session hash identifier: session.hash_function in php.ini. If PHP < 5.3, set it to session.hash_function = 1 for SHA1. If PHP >= 5.3, set it to session.hash_function = sha256 or session.hash_function = sha512.

  • Send a strong hash: session.hash_bits_per_character in php.ini. Set this to session.hash_bits_per_character = 5. While this doesn't make it any harder to crack, it does make a difference when the attacker tries to guess the session identifier. The ID will be shorter, but uses more characters.

  • Set an additional entropy with session.entropy_file and session.entropy_length in your php.ini file. Set the former to session.entropy_file = /dev/urandom and the latter to the number of bytes that will be read from the entropy file, for example session.entropy_length = 256.

  • Change the name of the session from the default PHPSESSID. This is accomplished by calling session_name() with your own identifier name as the first parameter prior to calling session_start.

  • If you're really paranoid you could rotate the session name too, but beware that all sessions will automatically be invalidated if you change this (for example, if you make it dependent on the time). But depending on your use-case, it may be an option...

  • Rotate your session identifier often. I wouldn't do this every request (unless you really need that level of security), but at a random interval. You want to change this often since if an attacker does hijack a session you don't want them to be able to use it for too long.

  • Include the user agent from $_SERVER['HTTP_USER_AGENT'] in the session. Basically, when the session starts, store it in something like $_SESSION['user_agent']. Then, on each subsequent request check that it matches. Note that this can be faked so it's not 100% reliable, but it's better than not.

  • Include the user's IP address from $_SERVER['REMOTE_ADDR'] in the session. Basically, when the session starts, store it in something like $_SESSION['remote_ip']. This may be problematic from some ISPs that use multiple IP addresses for their users (such as AOL used to do). But if you use it, it will be much more secure. The only way for an attacker to fake the IP address is to compromise the network at some point between the real user and you. And if they compromise the network, they can do far worse than a hijacking (such as MITM attacks, etc).

  • Include a token in the session and on the browsers side that you increment and compare often. Basically, for each request do $_SESSION['counter']++ on the server side. Also do something in JS on the browsers side to do the same (using a local storage). Then, when you send a request, simply take a nonce of a token, and verify that the nonce is the same on the server. By doing this, you should be able to detect a hijacked session since the attacker won't have the exact counter, or if they do you'll have 2 systems transmitting the same count and can tell one is forged. This won't work for all applications, but is one way of combating the problem.

A note on the two

The difference between Session Fixation and Hijacking is only about how the session identifier is compromised. In fixation, the identifier is set to a value that the attacker knows before hand. In Hijacking it's either guessed or stolen from the user. Otherwise the effects of the two are the same once the identifier is compromised.

Session ID Regeneration

Whenever you regenerate the session identifier using session_regenerate_id the old session should be deleted. This happens transparently with the core session handler. However some custom session handlers using session_set_save_handler() do not do this and are open to attack on old session identifiers. Make sure that if you are using a custom session handler, that you keep track of the identifier that you open, and if it's not the same one that you save that you explicitly delete (or change) the identifier on the old one.

Using the default session handler, you're fine with just calling session_regenerate_id(true). That will remove the old session information for you. The old ID is no longer valid and will cause a new session to be created if the attacker (or anyone else for that matter) tries to use it. Be careful with custom session handlers though....

Destroying a Session

If you're going to destroy a session (on logout for example), make sure you destroy it thoroughly. This includes unsetting the cookie. Using session_destroy:

function destroySession() {
    $params = session_get_cookie_params();
    setcookie(session_name(), '', time() - 42000,
        $params["path"], $params["domain"],
        $params["secure"], $params["httponly"]
    );
    session_destroy();
}
Y. E.
  • 687
  • 1
  • 10
  • 29
ircmaxell
  • 163,128
  • 34
  • 264
  • 314
  • 4
    Using 5 instead of 4 bits per character doesn’t change the “strength” in any way (whatever “strength” means in this case). But although your points are advisable in general, they lack some important details. For example what happens to the session that is associated to the old session ID or how a session with an old session ID should be handled after it got invalid. – Gumbo Feb 22 '11 at 17:29
  • @Gumbo Couldn't get your last sentence completely but, wouldn't an old session migrate to the new one in case of an id regeneration; and what is the problem with an invalidated session? – Halil Özgür Feb 22 '11 at 17:46
  • 2
    @battal: No, that’s the point. `session_regenerate_id` does not invalidate the session that is still associated to the old ID; only if the *delete\_old\_session* parameter is set to true the session will be destroyed. But what if an attacker initiated this ID regeneration? – Gumbo Feb 22 '11 at 18:05
  • 6
    I disagree with session regeneration every time you change a session variable, it should only be done on login/logout. Also checking the user-agent is meaningless and checking REMOTE_ADDR is problematic. One thing i would like to add is `session.entropy_file = /dev/urandom`. PHP's internal entropy generation is proven to be extremely weak and the entropy pool provided by /dev/random or /dev/uranom is the best you can get on a web server without a hardware rng. – rook Feb 22 '11 at 18:30
  • 4
    Also you should add `session.cookie_httponly` and `session.cookie_secure`. The first one helps thwart xss (but its not perfect). The 2nd is the best way to stop OWASP A9... – rook Feb 22 '11 at 18:34
  • @Gumbo Ah, I see, it persists per page view. These look like important points for any non-trivial app. Interesting conversion in the comments section of [`session_regenerate_id`](http://php.net/manual/en/function.session-regenerate-id.php) manual page. What do you think of those, especially [comment 87905](http://www.php.net/manual/en/function.session-regenerate-id.php#87905)? – Halil Özgür Feb 22 '11 at 19:09
  • 1
    @ircmaxell _"While this doesn't make it any harder to crack, it does make a difference when the attacker tries to guess the session identifier"_ - what kind of difference does it make in terms of security, if the hash entropy is same? And to the start of the sentence _"Send a strong hash"_ - I think it doesn't have anything to do with the "strength" of the hash, only the readable representation of it. – Halil Özgür Feb 22 '11 at 19:15
  • @battal: I don’t see the point why the old session ID and thus the old session should be valid for further 60 seconds. It’s like having a security doors that closes automatically not until 60 seconds after you passed it and busts everyone that passed after you. This would only make sense to use it to detect an attacker (in case of Session Hijacking) or a victim (in case of Session Fixation). But you can’t quite tell who is who. – Gumbo Feb 22 '11 at 19:32
  • @Gumbo: The solution in those comments tries to hand old session back to the already running scripts (normally they can't access it until the script doing a regen id finishes). And since they started running before the session change, should we call it a vuln.? Wouldn't it be ambiguous to run half of a script with some session and other half with another session (except for push things etc)? – Halil Özgür Feb 23 '11 at 07:40
  • 1
    @battal: No, it’s the new session ID that is used after ID regeneration and the session data in `$_SESSION` remains unchanged. But frankly, calling `session_regenerate_id` doesn’t write the session data of `$_SESSION` back to the storage. So the local change of `$_SESSION['OBSOLETE']` and `$_SESSION['EXPIRES']` does only alter the data of the current session with the newly generated ID but not the old one. To set those values for the old session you would rather need to call `session_write_close` *before* `session_regenerate_id`. – Gumbo Feb 23 '11 at 11:01
  • 1
    You can also change the default session name (PHPSESSID) using: `session.name = blah` instead of calling `session_name()` – Mike Causer Aug 17 '12 at 08:16
  • 1
    @MikeCauser But using `session_name()` is more portable; imagine servers that don't provide access to `php.ini` or `.htacess`. – Ja͢ck Dec 01 '12 at 02:20
  • More portable yes, but heaps slower. I could understand php.ini not being exposed in a shared environment, but surely .htaccess would be available – Mike Causer Dec 03 '12 at 02:16
  • 1
    @MikeCauser: slower? Really? You're going to make that argument? We're talking *literally* microseconds. And since it's never going to be in a loop anyway (shouldn't, unless you're doing something horribly wrong). I would use `session_name()` simply from the standpoint that it keeps application config in the application. – ircmaxell Dec 03 '12 at 12:33
  • 4
    Don't understand such a great answer but missing the most import piece: use SSL/HTTPS. The counter increment is a source of problem with multiple request fast after each other, a user refreshes a page twice or hits a submit buttons twice. The IP address solution is a problem nowadays with all the mobile users and ever changing IP's. You could look at the first set of the IP, but still it's asking for trouble. Best is preventing the discovering of the session id at the first place and that is using SSL/HTTPS. – Sanne Mar 01 '13 at 15:15
  • @Rook: I agree with you the session should not be regenerated every time since it's an overkill, but what about regeneration of the id only, using session_regenerate_id(false), to make SID guessing more hard? – Marco Sulla Jun 05 '13 at 10:24
  • @Lucas Malor there is no attack that this prevents. The id should be large enough to prevent brute force in our life time. If you have an owasp a9 violation or HTTP Response Splitting/XSS/CSRF/Clickjakcing then regenerating the ID isn't going to help. – rook Jun 05 '13 at 16:25
  • @Rook: Of course, but if I have a session that expires after a long time, say 2 week as Google account does, an attacker have 2 weeks to try to get my SID. Is this improbable in your opinion if I have a SID generated by a strong hashing function, or someone could try? – Marco Sulla Jun 05 '13 at 17:06
  • 1
    @Lucas Malor Well that depends what you are hashing. If you are using your platform's session manager (which is what you should be doing), then you are probably using /dev/urandom, which is a sah1 hash of an entropy pool. In this case, you will probably need a few thousand years to guess this session id. – rook Jun 05 '13 at 17:15
  • Another important point about the counter and changing it on the client: the cookie should be made HttpOnly... so obviously the client JavaScript has no access to the cookie and cannot increment a counter. It would be wise to explain that 2 cookies are required in that case. – Alexis Wilke Dec 24 '13 at 11:43
  • Isn't 256 bytes of entropy overkill? 32 should be sufficient. 32 bytes = 256 bits after all. – Scott Arciszewski May 30 '15 at 17:21
  • what about `session.use_strict_mode`? – Akam Jun 02 '16 at 17:11
  • 1
    @Akam "strict mode" is a bandaid for everything else identified in the post. If you regenerate on privilege escalation as indicated in the post, strict mode literally does nothing for you. – ircmaxell Jun 02 '16 at 17:30
  • I just came across this post and no answer was marked as correct, perhaps I might as well assume that this answer with the most upvote is the right answer – Browyn Louis Mar 06 '22 at 16:29
37

Both session attacks have the same goal: Gain access to a legitimate session of another user. But the attack vectors are different:

  • In a Session Fixation attack, the attacker already has access to a valid session and tries to force the victim to use this particular session.

  • In a Session Hijacking attack, the attacker tries to get the ID of a victim’s session to use his/her session.

In both attacks the session ID is the sensitive data these attack are focused on. So it’s the session ID that needs to be protected for both a read access (Session Hijacking) and a write access (Session Fixation).

The general rule of protecting sensitive data by using HTTPS applies in this case, too. Additionally, you should to do the following:

To prevent Session Fixation attacks, make sure that:

To prevent Session Hijacking attacks, make sure that:

To prevent both session attacks, make sure that:

  • to only accept sessions that your application have initiated. You can do this by fingerprinting a session on initiation with client specific information. You can use the User-Agent ID but don’t use the remote IP address or any other information that might change from between requests.
  • to change the session ID using session_regenerate_id(true) after an authentication attempt (true only on success) or a change of privileges and destroy the old session. (Make sure to store any changes of $_SESSION using session_write_close before regenerating the ID if you want to preserved the session associated to the old ID; otherwise only the session with the new ID will be affected by those changes.)
  • to use a proper session expiration implementation (see How do I expire a PHP session after 30 minutes?).
Community
  • 1
  • 1
Gumbo
  • 643,351
  • 109
  • 780
  • 844
7

The tokens you mention are a "nonce" - number used once. They don't necessarily have to be used only once, but the longer they're used, the higher the odds that the nonce can be captured and used to hijack the session.

Another drawback to nonces is that it's very hard to build a system that uses them and allows multiple parallel windows on the same form. e.g. the user opens two windows on a forum, and starts working on two posts:

window 'A' loads first and gets nonce 'P'
window 'B' loads second and gets nonce 'Q'

If you have no way of tracking multiple windows, you'll only have stored one nonce - that of window B/Q. When the user then submits their post from window A and passes in nonce 'P', ths system will reject the post as P != Q.

Marc B
  • 356,200
  • 43
  • 426
  • 500
2

I did not read Shiflett's article, but I think you have misunderstood something.

By default PHP passes the session token in the URL whenever the client does not accept cookies. Oherwise in the most common case the session token is stored as a cookie.

This means that if you put a session token in the URL PHP will recognize it and try to use it subsequently. Session fixation happens when someone creates a session and then tricks another user to share the same session by opening a URL which contains the session token. If the user authenticates in some way, the malicious user then knows the session token of an authenticated one, who might have different privileges.

As I'm sure Shiflett explains, the usual thing to do is to regenerate a different token each time the privileges of a user change.

Andrea
  • 20,253
  • 23
  • 114
  • 183
  • To add to this, please be certain to destroy any previously opened sessions as they will still be valid with the existing user permissions. – corrodedmonkee Feb 22 '11 at 17:20
0

Yes you could prevent session fixation by regenerating the session id once upon login. This way if the attacker will not know the cookie value of the newly authenticated session. Another approach which totally stops the problem is set session.use_only_cookies=True in your runtime configuration. An attacker cannot set the value of a cookie in the context of another domain. Session fixation is relying on sending the cookie value as a GET or POST.

rook
  • 66,304
  • 38
  • 162
  • 239