Re: Reality Check: Session Hijacking

From: Adriaan (red_at_de.solidareit)
Date: 05/09/04


Date: Sun, 9 May 2004 12:38:29 +0200


"mrbog" wrote

> Adriaan I love you, and I want to have your babies, you are not an
> *** after all.

Well, _I_ never suggested I was even close to being an *** ;-)

> I thought of your solution above before- "when a page is accessed over
> ssl, set isauthenticated to true, when he leaves and goes to http set
> it to back false."
...
> Problem 2:
>
> Every page of the site would need to have php on it. For some sites
> with tons of flat data, that really sucks.

Aha, so this might be your lucky day (read on) as I assume you've made the
choice to force the visitor to accept session cookies to keep the session
alive? Is that really a choice or just something that hasn't been given
thought yet?

I'd say that people who've disabled *session* cookies don't really
understand why those cookies are not harmful (just to be sure: a session
cookie is simply a cookie that dies when the browser is closed, not a cookie
that is necessarily related to any PHP session data). But if those people
still need to access your site, then you need to be able to fall back to
session URL rewriting (where the PHP session id is automatically added as a
GET parameter to all relative URLs on your page, if the cookie is not
accepted). Yes, I know the session id will be sent in Referer headers when
navigating to another site. And yes, I know some people will send around
such URLs (even https:// URLs) including that GET parameter to friends, but
well, you just blame it on the user and make sure the disclaimer covers it.
So basically: yes, I know PHP has URL rewriting disabled by default for a
reason.

Still, as I like my sites to be available to everyone:

With Apache's rewrite engine you could easily serve any html file (or any
file for that matter, even your JPGs) through a custom PHP script and then
keep the normal session alive. In php.ini enable session.use_trans_sid.
Next, in Apache's .htaccess something like

    Options FollowSymLinks
    RewriteEngine on
    RewriteRule ^(.*\.htm|.*\.html) serve-html.php?name=$1

and in serve-html.php something like

    start_session();
    if(name indeed indicates to be html){
        // include it and have PHP do its URL rewriting magic (note
        // that the curly braces are required for include to work here):
        include($_GET['name']);
    }
    else{
        // ignore, or see http://php.net/readfile but then make sure
        // this very script cannot be called for just any file, like
        // PHP source files or a password file...
    }

Note that the visitor will not see the new URL in the browser (it still says
foo.html, not serve-html.php?name=foo.html).

The above obviously does not solve any security issues (but would allow you
to add any security related code, of course). Nevertheless it might be kind
towards your visitors to maintain the PHP session anyhow, even without
implementing "if not SSL then unset isAuthenticated". It's a bit off-topic
though.

> Even if every page has php in all of it's html, a user could
> still view an individual image in his web browser- a jpg or png. When
> he does so, the browser still sends the session cookie, thus exposing
> it, but there's no php running anywhere to unset the isauthenticated
> bit. Like I say, obscure but true.

Fair enough, but would viewing a single JPG be a reason to un-authenticate a
user? And is a non-authenticated user allowed to see any JPG (like: are
there any generated JPGs that should be protected)? By the way: note that
when right-clicking an image on a 100% SSL enabled page, then the URL for
that image would still be a https:// URL. And the session cookie might
already have been exposed before -- like I wrote: counting on "when a page
is accessed over ssl, set isauthenticated to true, when he leaves and goes
to http set it to back false" will always allow a sniffer to jump into the
SSL part a few seconds after the user has browsed from non-SSL to SSL...

> In general, tho, we still don't have a solution here- I guess maybe we
> could make our own (second) session cookie who's path is set so that
> it only is sent over applications in the secure directory of the site

I suddenly remember that IE is not cooping with multiple cookies with the
same name for different subdomains. Whereas the RFCs state that the most
specialized cookie (the one with the best match for the subdomain or path)
should be sent first, IE 6.x sends the cookies in the order in which it once
received them :-( So, I guess you should not try to use multiple cookies
with the same name (but different domains or paths), just to be sure...

> With my method, I had to make every
> "next page" or "submit" button a form field so that it would do a POST
> request, otherwise it would put name/pw into the browser address bar
> (GET). I was making seperate forms for every page number button and
> "next" and "previous", it's horrible.

Yes, that's why I wrote it's hard to maintain, let alone horrible for the
user's Back button. First of all, again, I think you should really not
encrypt username and password into the contents sent to the browser, but
only some random token that you compare to a value stored in the session!
Then at least all gets to be temporary again, and will be more secure for
those using an internet café or some office computer. Still then I agree
that using GET parameters to pass the token is quite bad: you'd need a
disclaimer for your users not to send around such URLs, you need to
gracefully handle bookmarked URLs, and above all you need to make sure
there's no external links on your page as those will expose the token in the
Referer header... By the way: the latter could be handled by creating a PHP
script for all external links, which would only redirect after removing the
GET parameters.

If above you've really decided *not* to fall back to URL rewriting and as
such: enforce the user to accept session cookies, then this is your lucky
day. You can simply use another standard (albeit: SSL-only) cookie to pass
that random token:

- on every SSL page check if a cookie named 'token' is set
- if yes: compare against the value stored in the session
- if cookie not set, or if no match:
    - prompt for credentials
    - create a (new) random token, store it in the session
    - set a cookie for that token and set it to be used for SSL only
- when limiting the lifetime: set the cookie again (same value) to keep it
alive

Above the PHPSESSID cookie is still simply allowed for both SSL and non-SSL,
so the browser will present this cookie for each and every request it makes.
So: your session will be kept alive as long as the browser is not closed.

However, the token cookie (explicitely set to be used for SSL only) will
only be sent by the browser if the request is indeed using SSL, so: not for
non-SSL pages. Thus: if the visitor browses from SSL to non-SSL, the cookie
will no longer be sent back to the server. But when coming back to SSL the
token cookie is sent along again, and even more: that token is still valid.
Therefore I can image that at some point (like when saving user data, or
when changing user password) you explicitely prompt for the credentials
again to avoid abuse when a user leaves the computer and someone else
continues using it. Bottomline is: a sniffer certainly can not get the token
value. To avoid abuse by someone who has access to the computer (or to make
your site "feel" more secure to the visitor) you might also want to limit
the lifetime of the token cookie without actually limiting the lifetime of
the PHP session. That's why above you need to set the token cookie with a
new expiry time whenever a page is requested.

If you explicitely want to enforce the user to re-authenticate whenever
browsing from non-SSL to SSL then simply check the Referer header to see
where the user is coming from.

However, when not enforcing the visitor to accept session cookies (this not
being your lucky day) it would still be very nice to be able to use
SSL_SESSION_ID rather than embedding your own token in hidden fields. Note
that Microsoft IIS is said not to expose the value at all, so only keep
reading when using Apache...

Maybe the id in my tests is not constant as I did not use a valid server
cert for my tests. Take a look at
https://cgi.ccs.neu.edu/home/cgiadmin/examples/phpinfo.php (which is not my
page, but I get different values for every refresh) and
https://sslap.wind.surfnet.nl:8889/cgi-bin/envir.pl (not my page either, but
constant if refreshed quite fast, but changing when waiting a minute; just
ignore the request for a client certificate).

New to me: http://php.net/session-regenerate-id This is not going to help us
(as then we still need to know when the user leaves SSL, to re-generate
again) but a user comment on that page also suggests using the SSL session
id. Maybe behavior for Refresh and Ctrl-Refresh are different, which makes
the id change in my tests. Some relevant discussions:

    Verifying SSL_SESSION_ID
    "It is known to work with the Apache server with mod_ssl,
    and with Microsoft Internet Explorer, Netscape, and Mozilla
    browsers. [..] Konqueror 3.1, available for KDE on Linux,
    does not generate SSL_SESSION_ID values [..]"
    http://wiki.oscommerce.com/proposalSecurityAndPrivacy

    Improvements in session management?
    "As you might guess when running with SSL you gain really
    secure session cross-checking by looking at SSL_SESSION_ID
    since you can rely on the SSL session managment of
    e.g. Apache/mod_ssl."
    http://seclists.org/lists/webappsec/2004/Apr-Jun/0002.html

    SSL Session Id Changing?
    "The session ID should not be changed for each new page"
    "The caching of a session ID on the server-side [..] is
    expected behavior."
    http://google.com/groups?threadm=3B152233.1000206@colubris.com

but also...

    HTTP state management without cookies?
    "You can't count on the SSL session ID staying constant."
    http://google.com/groups?selm=3C763007.78FEA7FF@stroeder.com

So: maybe it is indeed the way to go and simply needs some better testing!

> That zend article should be pulled down.

http://www.zend.com/zend/spotlight/sessionauth7may.php#Heading5

Well, I can't see where SSL is mentioned there? When never using SSL (not
even while authenticating), or when always using SSL, the above is not an
issue, is it? When never using SSL one accepts the risk that a sniffer will
get the full credentials rather than just an authenticated session; when
always using SSL there is no issue whatsoever. So only when using both SSL
and non-SSL one should not rely on a "isAuthenticated" thingy in the
session.

The article is very bad for another reason though: anyone who knows that
authentication is based on this example could send the $authdata array in
GET parameters, as it relies on register globals to be enabled. But well, it
dates from 2001 and meanwhile register globals is no longer enabled upon
installation... No worries.

Adriaan