Sessions and Cookies

General

The web is stateless. Some statement, and it needs to be modified, not ignored. What it really means is that the predominant web protocol, HTTP, is stateless. We need first to see what it means, then to handle it so that we can exploit it's benefits, and circumvent it's disadvantages.

Statelessness means that the server has no way of associating two HTTP requests. Each request is it's own. The client cannot identify itself, and consequently the server cannot distinguish between clients. This is inherent in HTTP.

The primary benefit is less overhead, meaning better performance, because ther's no state to maintain, it does not cost anything.

Since we cannot live with that, after all a dialog between client and server means that either side remembers what the other said before, we need to remedy the statelessness. We need state to create meaningful applications based on HTTP.

Netscape originally, a prominent browser software manufacturer, gave us the medicine called cookies, an add on to the HTTP protocol. The extension really consists of two HTTP headers, the Set-Cookie response header, and the Cookie request header.

Figure 58.1. Cookie Exchange
Cookie Exchange

When a page requests another, a URL, the server, really the requested page, may decide to return a Set-Cookie with the response. If so, the client, the browser, is thus required to issue a Cookie header in all future requests until told to drop it. Please refer to Figure 58.1.

When communicating with many clients, the server maintains data per client. The cookie holds an identifier sufficiently random to eliminate prediction, and sufficiently random to prevent two session identifiers to coincide.

Figure 58.2. Cookie Exchange
Cookie Exchange

The original Netscape spec has been copied off the net to here. For standard details on cookie management please refer to the RFC for the domain: http://www.ietf.org/rfc/rfc2965.txt

There is no built in security in this mechanism. You, as in you the programmer, must provide the security

Shifletts books says the threats are browser vulnerabilities that are not really the programmer's domain, though she may take steps to reduce the risk, and XSS that we shall deal with in subsequent lessons. General php guidelines regarding security may be found in http://phpsec.org/projects/ .

Fixation

According to Shiflett there are three ways of obtaning a valid session identifier:

  • Prediction. Hard, as mentioned.
  • Capture. Easier. Use sniffer.
  • Fixation. Easiest. Give him one ;-)

Session fixation means tricking your victim into using a PHPSESSID of your choice. Easiest done by feeding him one. Use a link:

<a href="sucker.com/index.php?PHPSESSID=1234">Click here!</a>

or do it by redirect, hands free linking:

<?php
    header("Location: http://sucker.com/index.php?PHPSESSID=1234");
?>

Are you able to trick a user into trying one of those, you may use the session for more because the session is a permission to be in dialog with the server. Now write:

Example 58.1. fixation.php
<?php
    session_start();
    $_SESSION['username'] = 'bogeyman';
?>

Clear all cookies for the current host, or just all cookies, then navigate to the page with a PHPSESSID of your choice

http://sucker.com/fixation.php?PHPSESSID=1234

If you check the server, you will find the cookie

$ cat /tmp/sess_1234
username|s:8:"bogeyman";

Now create a second program

Example 58.2. test.php
<?php
    session_start();
    if (isset($_SESSION['username'])) {
        print($_SESSION['username']);
    }
?>

Start another computer or another browser and visit

http://sucker.com/test.php?PHPSESSID=1234

What do you expect to see? You will see that you have hijacked the session started by fixation.php. This is bad, because it indactes the session system is vulnerable.

The risky part is that the url is used to create a session without an id even if one does not exist already. Attackers like that.

It may be mitigated through a standard php function:

<?php
    session_start();
    if (isset($_SESSION['initiated'])) {
        session_regenerate_id(); // refreshes PHPSESSID
        $_SESSION['initiated'] = TRUE;
    }
?>

Shiflett describes that this is good. There is, however, still a risk for getting through to a known PHPSESSID, and he recommends something like the following for each change of privilege, eg a login:

<?php
    session_start();
    $_SESSION['logged_in'] = FALSE;

    if (check_login()) {
        session_regenerate_id(); // refreshes PHPSESSID
        $_SESSION['logged_in'] = TRUE;
    }
?>

Session Hijacking

To protect yourself against session hijacking, you should be on the lookout for inconsistencies. Shiflett mentions that if your user agent since login has been one, and then suddenly changes, it could be the sign that someone is trying to impersonate you.

GET / HTTP/1.1
Host: sucker.com
User-Agent: Mozilla/5.0
Accept: text/html, application/xhtml+xml, application/xml, */*
Cookie: PHPSESSID=1234

Good check for user agent is

<?php
    session_start();
    if (isset($_SESSION['HTTP_USER_AGENT'])) {
        if ($_SESSION['HTTP_USER_AGENT'] != sha1($_SERVER['HTTP_USER_AGENT'])) {
            /* prompt for passwd / go to login */
            exit;
        }
    } else {
        $_SESSION['HTTP_USER_AGENT'] = sha1($_SERVER['HTTP_USER_AGENT']);
    }
?>

Because an attacker may have observed all the headers to and from the server under attack, you may use a home made token to make it more difficult for the attacker to impersonate a legal user. Let us assume you have created a token, then attach it to each url. The rationale being that if it is suddenly not there, or if it is wrong, then something is awry.

In the following code rawurlencode() encode the url according to http://www.faqs.org/rfcs/rfc3986.html . The other function htmlentities() converts to entities whatever is part of html.

?php
    $url = array();
    $html = array();

    $url['token'] = rawurlencode($token);   // re rfc
    $html['token'] = htmlentities($url['$token'], ENT_QUOTES, 'UTF-8');
?>

<a href="index.php?token=<?php print($html['token']); ?>"

The token must be unpredictable even if attacker knows all headers. An example of generating an unpredictable token:

?php
    $string = $_SERVER['HTTP_USER_AGENT'];
    $string .= 'Larsen';

    $token = sha1($string);
    $_SESSION['token'] = $token;
?>

Better, yes, but it can be captured. You could do better with:

?php
    $token = sha1(uniqid(rand()), TRUE);
    $_SESSION['token'] = $token;
?>